Reputation: 299
The problem I am trying to solve is the following:
I have a List<string>
, let's call it "initialList
".
I need to interact with all the strings in that list, so I have to enumerate it. However, the problem comes that I have to take "x" elements of it every time I interact.
x
is a new Random().Next(_minValue, _maxValue);
so that will be random every time.
Here is a real example of what I want to do:
Suppose the list initialList
contains the following elements:
test, test2, test3, test4, test5, test6, test7, test8, test9, test10, test11, test12
.
I want to loop through all elements and take 2-3 strings at a time and save a new string with combined ones.
So, one possible output (varies depending which values will the Random return) is:
{ "test", "test1", "test2" },
{ "test3", "test4" },
{ "test5", "test6" },
{ "test7", "test8" },
{ "test9", "test10", "test11" },
{ "test12" }
What I thought of doing:
for (var i = 0; i < _users.Count; i += _random.Next(_minMentions, _maxMentions + 1))
{
var mergedString = ?? // get the values of the next _random.Next(_minMentions, _maxMentions + 1) strings
}
I was thinking, but if I generate the random number outside the loop, then it will always be the same. I want the amount of strings to be random each time.
Also, thought of doing LINQ by selecting the elements with index, but don't know how to proceed from there.
Any help is appreciated, thanks.
Upvotes: 1
Views: 282
Reputation: 2561
I know there are plenty of good answers but here is something if you like IEnumerable
public static class EnumerableGroupOf
{
public static IEnumerable<IEnumerable<TSource>> RandomGroupOf<TSource>(this IEnumerable<TSource> source, int[] groupLengths)
{
Random random = new Random();
var itemsLeft = source;
while (itemsLeft.Any())
{
var count = groupLengths[random.Next(0, groupLengths.Length)];
var items = itemsLeft.Take(count);
itemsLeft = itemsLeft.Skip(count);
yield return items;
}
}
}
This wont be doing skip and take on all the items just the reaming ones so might perform a bit better with Enumerable.
Upvotes: 0
Reputation: 186668
You can try direct GroupBy
:
List<string> initialList = Enumerable
.Range(1, 15)
.Select(i => $"test{i}")
.ToList();
int _minMentions = 2;
int _maxMentions = 3;
// Random(1): to make outcome reproducible
// In real life should be new Random();
Random _random = new Random(1);
int group = 0;
int groupLength = 0;
var result = initialList
.Select((item) => {
if (--groupLength <= 0) {
group += 1;
groupLength = _random.Next(_minMentions, _maxMentions + 1);
}
return new { item, group };
})
.GroupBy(item => item.group)
.Select(chunk => chunk
.Select(item => item.item)
.ToList())
.ToList();
string test = string.Join(Environment.NewLine, result
.Select(items => string.Join(", ", items)));
Console.Write(test);
Outcome:
test1, test2
test3, test4
test5, test6
test7, test8, test9
test10, test11, test12
test13, test14
test15
Upvotes: 2
Reputation: 9463
I think the best solution is to use LINQ Skip
to skip over the elements you have already processed and Take
to extract the random number of elements.
const int _minValue = 2;
const int _maxValue = 4; // note max of random is exclusive, so this config takes 2 or 3 elements
var _random = new Random(Guid.NewGuid().GetHashCode());
List<string> initialList = new List<string>{"test", "test2", "test3", "test4", "test5", "test6", "test7", "test8", "test9", "test10", "test11", "test12"};
var currentPosition = 0;
while (currentPosition < initialList.Count()) {
var toTake = _random.Next(_minValue, _maxValue);
var mergedString = string.Join(", ", initialList.Skip(currentPosition).Take(toTake));
currentPosition += toTake;
}
Note that this code might result in the last mergedString
only containing one element (the last one).
Working example: C# Fiddle.
Upvotes: 2
Reputation: 959
Hope this will give you an idea.
var mergedList = new List<string>();
for(int i = 0; i < initialList.Count; ){
var n = _random.Next(2,4);
mergedList.Add(initialList.Skip(i).Take(n).Aggregate((x,y) => x + y));
i += n;
}
Upvotes: 3
Reputation: 4733
Here's how you can achieve it using Linq:
var initialList = new[]
{
"test", "test2", "test3", "test4", "test5", "test6", "test7", "test8", "test9", "test10", "test11",
"test12"
};
var newLists = new List<IEnumerable<string>>();
var rnd = new Random();
int minMentions = 2;
int maxMentions = 3;
int c = 0;
while (c < initialList.Length)
{
int elementsToTake = rnd.Next(minMentions, maxMentions + 1);
newLists.Add(initialList.Skip(c).Take(elementsToTake));
c += elementsToTake;
}
Which results in (randomly):
{ test, test2, test3 }
{ test4, test5 }
{ test6, test7, test8 }
{ test9, test10, test11 }
{ test12 }
Note that Take
will only take available items, so you don't need to worry about the elementsToTake
being bigger than what initialList
has left.
Upvotes: 1
Reputation: 216293
You can use the IEnumerable.Skip and Take to loop over your initial list and create blocks of the desidered size until you have elements to process.
List<string> initiallist = new List<string>
{"test", "test2", "test3", "test4", "test5", "test6", "test7", "test8", "test9", "test10", "test11", "test12"};
// We store the results here
List<List<string>> result = new List<List<string>>();
Random rnd = new Random();
int pos = 0;
while (pos < initiallist.Count())
{
// Define a block size of 2/3 elements
int block = rnd.Next(2,4);
// Extract the block size from the previous position
List<string> temp = initiallist.Skip(pos).Take(block).ToList();
// Add the sublist to our results
result.Add(temp);
// Point to the beginning of the next block
pos += block;
}
Upvotes: 1