Reputation: 2391
I try to take 3 rows at a time from a List of string but its not working as expected. Consider this code...
var listOfStrings = new List<string>
{
"String 1",
"String 2",
"String 3",
"String 4",
"String 5",
"String 6"
};
foreach (var x in listOfStrings.Take(3).ToList())
{
var currRows = x.ToList();
// currRows should have 3 items
foreach (var itm in currRows)
{
}
}
The first run I expect currRows to have 3 items (String 1, 2 and 3), the second time I expect to have these 3 items (String 4, 5 and 6). But when I run this currRows only contains for example "String 1" and this is split up character by character?!
What am I missing here?
Upvotes: 3
Views: 102
Reputation: 149538
But when I run this currRows only contains for example "String 1" and this is split up character by character?!
That's because Enumerable.Take
will take the requested amount of items from the IEnumerable<T>
. This makes your x
variable be of type string
, which you later call ToList()
on, effectively creating a List<char>
, which isn't what you want.
You can use MoreLINQ which has a Batch
extension method which does exactly what you want. It returns an IEnumerable<IEnumerable<T>>
:
foreach (var batch in listOfStrings.Batch(3))
{
// batch is an IEnumerable<T>, and will have 3 items.
foreach (var item in batch)
{
}
}
Another possibility is to create that extension method yourself. This is taken from this answer:
public static class EnumerableExtensions
{
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items,
int maxItems)
{
return items.Select((item, inx) => new { item, inx })
.GroupBy(x => x.inx / maxItems)
.Select(g => g.Select(x => x.item));
}
}
Upvotes: 2
Reputation: 2024
Starting with your code:
var listOfStrings = new List<string>
{
"String 1",
"String 2",
"String 3",
"String 4",
"String 5",
"String 6"
};
foreach (var x in listOfStrings.Take(3).ToList())
{
var currRows = x.ToList();
// currRows should have 3 items
foreach (var itm in currRows)
{
}
}
Lets simplify your code a little bit by creating an extra variable for what you are looping over:
var listOfStrings = new List<string>
{
"String 1",
"String 2",
"String 3",
"String 4",
"String 5",
"String 6"
};
var thingsYouAreLoopingOver = listOfStrings.Take(3).ToList();
foreach (var x in thingsYouAreLoopingOver)
{
var currRows = x.ToList();
// currRows should have 3 items
foreach (var itm in currRows)
{
}
}
but surely this can be simplified again as thingsYouAreLoopingOver is just the first three things of listOfStrings, so we have:
var thingsYouAreLoopingOver = new List<string>
{
"String 1",
"String 2",
"String 3"
};
foreach (var x in thingsYouAreLoopingOver)
{
var currRows = x.ToList();
// currRows should have 3 items
foreach (var itm in currRows)
{
}
}
It should be clear now why you get the behaviour you are seeing.
Upvotes: 0
Reputation: 27357
x.ToList();
is calling the method on a string
. A string
is an IEnumerable<char>
, so the result of currRows
is a list of characters. You can remove that line completely, and use x
in your second loop.ToList()
is usually[1] a pointless operation if you're doing it on a collection used in a foreach
loop. It'll iterate your collection and construct a new list, which is then iterated. You can remove ToList
from your first loop and it'll perform better, but have the same result.Take
simply takes n
number of items, it does not group by n
items.Try this:
var listOfStrings = new List<string>
{
"String 1",
"String 2",
"String 3",
"String 4",
"String 5",
"String 6"
};
int step = 0;
const int numTake = 3;
for(var i = 0; i < listOfStrings.Count / numTake; i++)
{
Console.WriteLine("Starting a group!");
foreach (var x in listOfStrings.Skip(3 * step++).Take(3))
{
Console.WriteLine(x);
}
}
A huge amount of room to improve, but it should give you an idea of how you should approach this task.
Upvotes: 2