【问题标题】:How to split a list of strings to smaller chunks of lists of strings如何将字符串列表拆分为更小的字符串列表块
【发布时间】:2019-01-20 08:23:11
【问题描述】:

我要解决的问题如下:

我有一个List<string>,我们称之为“initialList”。

我需要与该列表中的所有字符串进行交互,所以我必须枚举它。但是,问题来了,我每次交互时都必须取其中的“x”个元素。

xnew Random().Next(_minValue, _maxValue);,所以每次都是随机的。

这是我想做的一个真实例子:

假设列表initialList 包含以下元素: test, test2, test3, test4, test5, test6, test7, test8, test9, test10, test11, test12.

我想遍历所有元素,一次取 2-3 个字符串,然后保存一个包含组合字符串的新字符串。

因此,一种可能的输出(因 Random 返回的值而异)是:

{ "test",  "test1", "test2" }, 
{ "test3", "test4" }, 
{ "test5", "test6" }, 
{ "test7", "test8" }, 
{ "test9", "test10", "test11" }, 
{ "test12" }

我想做什么:

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
}

我在想,但是如果我在循环之外生成随机数,那么它总是一样的。我希望字符串的数量每次都是随机的。

另外,考虑通过选择具有索引的元素来执行 LINQ,但不知道如何从那里着手。

感谢您的帮助。

【问题讨论】:

  • 如果在所有随机抽取后最后只剩下一个元素会怎样?这不符合每组 2-3 个元素的要求。
  • @itsme86 在他提供的示例中,最后一个元素(单独的)是...单独的。
  • “我想遍历所有元素,一次取 2-3 个字符串,并保存一个包含组合字符串的新字符串”——据我所知,结果应该只是一个字符串列表?
  • @VadzimDvorak 这没什么大不了的,因为我稍后会根据我的需要调整它。但我喜欢您的解决方案,它与其他答案有些相似,但很独特。谢谢你的帮助! :)

标签: c# string list linq loops


【解决方案1】:

我知道有很多很好的答案,但如果你喜欢 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;
            }
        }
    }

这不会跳过并接受所有项目,只是铰接项目,因此使用 Enumerable 可能会表现得更好。

【讨论】:

    【解决方案2】:

    你可以直接试试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);
    

    结果:

    test1, test2
    test3, test4
    test5, test6
    test7, test8, test9
    test10, test11, test12
    test13, test14
    test15
    

    【讨论】:

    • @Filip Cordas:这在技术上是可行的(我们可以将 groupgroupLength 放入匿名类:var result = initialList.Select(item =&gt; new {item, group = 0, groupSize = _random.Next(_minMentions, _maxMentions + 1)})...)但我怀疑它是否值得实施(克服两个特别实施的本地变量)
    • 确实如此,但如果它没有副作用,您就不必在最后使用 ToList 并且只能使用 enumerable。
    【解决方案3】:

    我认为最好的解决方案是使用 LINQ Skip 跳过您已经处理过的元素并使用Take 提取随机数量的元素。

    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;
    }
    

    请注意,此代码可能会导致最后一个 mergedString 仅包含一个元素(最后一个)。

    工作示例:C# Fiddle

    【讨论】:

      【解决方案4】:

      希望这会给你一个想法。

      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;
      }
      

      【讨论】:

        【解决方案5】:

        以下是使用 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;
        }
        

        这会导致(随机):

        { test, test2, test3 }
        { test4, test5 }
        { test6, test7, test8 }
        { test9, test10, test11 }
        { test12 }
        

        请注意,Take 只会占用可用的项目,因此您无需担心 elementsToTakeinitialList 剩下的要大。

        【讨论】:

          【解决方案6】:

          您可以使用 IEnumerable.Skip 和 Take 来循环您的初始列表并创建所需大小的块,直到您有要处理的元素。

          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;
          
          }
          

          【讨论】:

          • 我试图用嵌套的for 循环做类似的事情。有点接近您的解决方案。但是,一个问题,不应该检查pos + block是否超过列表的计数?
          • Take 将忽略 block 值,如果您请求了很多元素并仅返回从 给出的起点可用的元素位置
          猜你喜欢
          • 1970-01-01
          • 2019-04-16
          • 1970-01-01
          • 2015-09-01
          • 2011-11-13
          • 1970-01-01
          • 1970-01-01
          • 2021-12-18
          相关资源
          最近更新 更多