user9248102
user9248102

Reputation: 299

How to split a list of strings to smaller chunks of lists of strings

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

Answers (6)

Filip Cordas
Filip Cordas

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

Dmitrii Bychenko
Dmitrii Bychenko

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

Georg Patscheider
Georg Patscheider

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

vadzim dvorak
vadzim dvorak

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

Haytam
Haytam

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

Steve
Steve

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

Related Questions