C# Manipulate String List with List.ForEach - c#

I have a list as the following, and I would like to add a "!" at the end of each element of the string list.
I know that I can easily achieve this by using the traditional foreach. I'm wondering how do I do it with list.ForEach()?
List<string> list = new List<string>();
list.Add("Hi");
list.Add("Hey");
list.Add("Cool");
list.Add("Nice Day");
I have tried this but it is not working for some reason.
list.ForEach(x => x += "!");
Thanks!

First off, ForEach is a method of List, not a LINQ method.
Next, in your method there x is a copy of the item in the list, and you're mutating that copy to add a ! to it, which is why the underlying list is unaffected.
LINQ is for querying data sources, so if you wanted to create an entirely new sequence/structure that has all of the strings that your list has, but with an exclamation at the end, you can use Select for that:
var IMPORTANT = list.Select(s => s + "!");
If you want to mutate the items, just use a for loop:
for(int i = 0; i < list.Count; i++)
list[i] += "!";
Note that if you tried to use a foreach loop here, rather than a for, you'd run into the exact same problem that you have with your ForEach method. Consider this case:
foreach(var s in list)
s += "!";
Here s is a copy of the item in the list. You're mutating the copy, leaving the item in the list unchanged. If your list contained mutatable data, rather than immutable data, you could mutate the item that the reference referred to, rather than mutating the reference itself and a foreach or ForEach would work fine.

var newList = list.Select( x => x + "!" ).ToList()

Related

How to rename value in a List without creating a new one?

I have a list of strings List<string> blobsToCopy
var newList = blobsToCopy.Select(GetDiskNameWithoutFolder);
private static string GetDiskNameWithoutFolder(string path) {...}
How to change all values in the blobsToCopy without creating a new IEnumerable<string>?
You don't use LINQ, just use a simple for loop and replace each item with its modified version.
for (var i = 0; i < blobsToCopy.Count; i++)
{
blobsToCopy[i] = GetDiskNameWithoutFolder(blobsToCopy[i]);
}
Unless I'm missing something obvious, what's wrong with a good old-fashion for loop?
for(var i=0; i < blobsToCopy.Count;i++)
{
// do whatever....
}
Well, strings are immutable, so you can't edit those directly. Instead you could edit your list with blobsToCopy[i]. This will keep your list intact and let you edit which string the element of the list points to.
Maybe you could use the ForEach linq implementation. Don't have a c# compiler available at the moment, so I can't test this.
list.ForEach(c => c = GetDiskNameWithoutFolder(c));

add 3 to all integers in list without for loop c#

I want to plus 3 to all list<int> member without using for loop or foreach loop ? Can I do this in one line ?How?
Something somewhere is going to have to loop. You don't have to loop in your code, but something's going to have to.
I can't think of anything offhand which will modify all the elements in a list, but using LINQ you could create a new List<int> easily:
var plusThree = originalList.Select(x => x + 3).ToList();
or pre-LINQ (and slightly more efficient, but also more List<T>-specific):
var plusThree = originalList.ConvertAll(x => x + 3);
But both of these will be looping behind the scenes.
You could potentially create a projecting IList<T> implementation which lazily applied a projection (or possibly a bijection if you wanted to be really fancy)... but that would be significant amounts of work.
You have to change the value in the list itself, so there is no other way then to do it in a for loop, unless you are allowed to make a new list, then you can use Linq (which will loop in it's own code).
for(int i = 0; i < list.Count; i++) list[i]+=3;
Another way to do it (ofcourse it feels like writing foreach loop without actually writing it )
static void Main(string[] args)
{
List<int> list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);
list.Add(4);
list.Add(5);
int k = 0;
list.ForEach(delegate(int i) { list[k++] = i+3; });
foreach (var item in list)
{
Console.WriteLine(item.ToString());
}
Console.ReadKey();
}

Efficiently deleting item from within 'foreach'

For now, the best I could think of is:
bool oneMoreTime = true;
while (oneMoreTime)
{
ItemType toDelete=null;
oneMoreTime=false;
foreach (ItemType item in collection)
{
if (ShouldBeDeleted(item))
{
toDelete=item;
break;
}
}
if (toDelete!=null)
{
collection.Remove(toDelete);
oneMoreTime=true;
}
}
I know that I have at least one extra variable here, but I included it to improve the readability of the algorithm.
The "RemoveAll" method is best.
Another common technique is:
var itemsToBeDeleted = collection.Where(i=>ShouldBeDeleted(i)).ToList();
foreach(var itemToBeDeleted in itemsToBeDeleted)
collection.Remove(itemToBeDeleted);
Another common technique is to use a "for" loop, but make sure you go backwards:
for (int i = collection.Count - 1; i >= 0; --i)
if (ShouldBeDeleted(collection[i]))
collection.RemoveAt(i);
Another common technique is to add the items that are not being removed to a new collection:
var newCollection = new List<whatever>();
foreach(var item in collection.Where(i=>!ShouldBeDeleted(i))
newCollection.Add(item);
And now you have two collections. A technique I particularly like if you want to end up with two collections is to use immutable data structures. With an immutable data structure, "removing" an item does not change the data structure; it gives you back a new data structure (that re-uses bits from the old one, if possible) that does not have the item you removed. With immutable data structures you are not modifying the thing you're iterating over, so there's no problem:
var newCollection = oldCollection;
foreach(var item in oldCollection.Where(i=>ShouldBeDeleted(i))
newCollection = newCollection.Remove(item);
or
var newCollection = ImmutableCollection<whatever>.Empty;
foreach(var item in oldCollection.Where(i=>!ShouldBeDeleted(i))
newCollection = newCollection.Add(item);
And when you're done, you have two collections. The new one has the items removed, the old one is the same as it ever was.
Just as I finished typing I remembered that there is lambda-way to do it.
collection.RemoveAll(i=>ShouldBeDeleted(i));
Better way?
A forward variation on the backward for loop:
for (int i = 0; i < collection.Count; )
if (ShouldBeDeleted(collection[i]))
collection.RemoveAt(i)
else
i++;
You cannot delete from a collection inside a foreach loop (unless it is a very special collection having a special enumerator). The BCL collections will throw exceptions if the collection is modified while it is being enumerated.
You could use a for loop to delete individual elements and adjust the index accordingly. However, doing that can be error prone. Depending on the implementation of the underlying collection it may also be expensive to delete individual elements. For instance deleting the first element of a List<T> will copy all the remaning elements in the list.
The best solution is often to create a new collection based on the old:
var newCollection = collection.Where(item => !ShouldBeDeleted(item)).ToList();
Use ToList() or ToArray() to create the new collection or initialize your specific collection type from the IEnumerable returned by the Where() clause.
The lambda way is good. You could also use a regular for loop, you can iterate lists that a for loop uses within the loop itself, unlike a foreach loop.
for (int i = collection.Count-1; i >= 0; i--)
{
if(ShouldBeDeleted(collection[i])
collection.RemoveAt(i);
}
I am assuming that collection is an arraylist here, the code might be a bit different if you are using a different data structure.

Don't understand why the ForEach command isn't working

I am wanting to trim any white space off a collection of strings. I used the following code but it doesn't seem to work. Could anyone explain why?
result.ForEach(f => f = f.Trim());
This won't work because you are assigning a new string reference to a local variable. This is probably what you are looking for:
result = result.Select(f => f.Trim()).ToList();
You are re-assigning the argument variable inside the scope of the lambda. It's a collapsed form of:
foreach(string value in myList)
{
Lambda(value);
}
void Lambda(string input)
{
input = input.Trim();
}
The simplest way would probably be to use a projection:
myList = myList.Select(str => str.Trim()).ToList();
foreach doesn't give you write access to the underlying collection, it only iterates through it, which means your change isn't stored back into the collection.
You can do two things:
Produce a new collection
var newResult = result.Select(f => f.Trim()).ToList();
Use a normal for-loop and change the original collection
for (int index = 0; index < result.Count; index++)
result[index] = result[index].Trim();

Explain this LINQ code?

I asked a question in which one of the response contained the following LINQ code:
var selected = lstAvailableColors.Cast<ListItem>().Where(i => i.Selected).ToList();
selected.ForEach( x => { lstSelectedColors.Items.Add(x); });
selected.ForEach( x => { lstAvailableColors.Items.Remove(x);});
Can someone explain the above LINQ to a total newbie?
The LINQ operators use what's called a fluent interface, so you can read the first line as a series of function calls. Assuming that lstAvailableColors is IEnumerable<T>, the idea is that each available color flows through the LINQ operators.
Let's break it down:
var selected = lstAvailableColors
// each item is cast to ListItem type
.Cast<ListItem>()
// items that don't pass the test (Selected == true) are dropped
.Where(i => i.Selected)
// turn the stream into a List<ListItem> object
.ToList();
EDIT: As JaredPar pointed out, the last line above (ToList()) is very important. If you didn't do this, then each of the two selected.ForEach calls would re-run the query. This is called deferred execution and is an important part of LINQ.
You could rewrite this first line like this:
var selected = new List<ListItem>();
foreach (var item in lstAvailableColors)
{
var listItem = (ListItem)item;
if (!listItem.Selected)
continue;
selected.Add(listItem);
}
The last two lines are just another way to write a foreach loop and could be rewritten as:
foreach (var x in selected)
{
lstSelectedColors.Items.Add(x);
}
foreach (var x in selected)
{
lstAvailableColors.Items.Remove(X);
}
Probably the hardest part of learning LINQ is learning the flow of data and the syntax of lambda expressions.
Explanation from original question.
The LINQ version works in two parts. The first part is the first line which finds the currently selected items and stores the value in a List. It's very important that the line contain the .ToList() call because that forces the query to execute immediately vs. being delayed executed.
The next two lines iterate through each value which is selected and remove or add it to the appropriate list. Because the selected list is already stored we are no longer enumerating the collection when we modify it.
It casts each item in the list to type ListItem, then selects only those whose Selected property is true. It then creates a new list containing just these items. For each item in the resulting list, it adds that item to the selected colors list and removes it from the available colors list.
Maybe some translations would help
var selected = lstAvailableColors.Cast<ListItem>().Where(i => i.Selected).ToList();
could be written as:
List<ListItem> selected = new List<ListItem>();
foreach (ListItem item in lstAvailableColors)
{
if (item.Selected)
selected.Add(item);
}
Note that foreach implicitly casts the items on the list to whatever type the loop variable is, in this case ListItem, so that takes care of the Cast<ListItem> on the list. Where filters out any items for which the expression is false, so I do the same thing with an if statement. Finally, ToList turns the sequence into a list, so I just build up a list as I go. The end result is the same.
And:
selected.ForEach( x => { lstSelectedColors.Items.Add(x); });
selected.ForEach( x => { lstAvailableColors.Items.Remove(x); });
could be written as:
foreach (ListItem item in selected)
{
lstSelectedColors.Items.Add(item);
lstAvailableColors.Items.Remove(item);
}
I doubt if there's a good reason for writing it the more obscure way in that case.

Categories

Resources