【问题标题】:Split a list into multiple lists at increasing sequence broken以递增的顺序将列表拆分为多个列表
【发布时间】:2016-09-01 10:15:42
【问题描述】:

我有一个 int 列表,我想在找到较低或相同的数字时拆分原始列表后创建多个列表。 数字未按顺序排列。

 List<int> data = new List<int> { 1, 2, 1, 2, 3, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6 };

我希望结果如下:

 { 1, 2 }
 { 1, 2, 3 }
 { 3 }
 { 1, 2, 3, 4 }
 { 1, 2, 3, 4, 5, 6 }

目前,我正在使用以下 linq 来执行此操作,但没有帮助我:

List<int> data = new List<int> { 1, 2, 1, 2, 3, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6 };
List<List<int>> resultLists = new List<List<int>>();
var res = data.Where((p, i) =>
{
    int count = 0;
    resultLists.Add(new List<int>());
    if (p < data[(i + 1) >= data.Count ? i - 1 : i + 1])
    {
        resultLists[count].Add(p);
    }
    else
    {
        count++;
        resultLists.Add(new List<int>());
    }
    return true;
}).ToList();

【问题讨论】:

  • 在 LinQ 中包含 count 变量,这不会导致所有内容始终添加到第 0 个列表中吗?
  • 是否像您的示例中那样连续增加数字,或者它们可能有差距?当数字连续时,存在一个非常好的解决方案。
  • 只是为了说明。数字没有必要有任何排序顺序。

标签: c# linq list lambda


【解决方案1】:

我只想做一些简单的事情:

public static IEnumerable<List<int>> SplitWhenNotIncreasing(List<int> numbers)
{
    for (int i = 1, start = 0; i <= numbers.Count; ++i)
    {
        if (i != numbers.Count && numbers[i] > numbers[i - 1])
            continue;

        yield return numbers.GetRange(start, i - start);
        start = i;
    }
}

你会这样使用:

List<int> data = new List<int> { 1, 2, 1, 2, 3, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6 };

foreach (var subset in SplitWhenNotIncreasing(data))
    Console.WriteLine(string.Join(", ", subset));

如果你确实需要使用IEnumerable&lt;T&gt;,那么我能想到的最简单的方法是这样的:

public sealed class IncreasingSubsetFinder<T> where T: IComparable<T>
{
    public static IEnumerable<IEnumerable<T>> Find(IEnumerable<T> numbers)
    {
        return new IncreasingSubsetFinder<T>().find(numbers.GetEnumerator());
    }

    IEnumerable<IEnumerable<T>> find(IEnumerator<T> iter)
    {
        if (!iter.MoveNext())
            yield break;

        while (!done)
            yield return increasingSubset(iter);
    }

    IEnumerable<T> increasingSubset(IEnumerator<T> iter)
    {
        while (!done)
        {
            T prev = iter.Current; 
            yield return prev;

            if ((done = !iter.MoveNext()) || iter.Current.CompareTo(prev) <= 0)
                yield break;
        }
    }

    bool done;
}

你会这样称呼:

List<int> data = new List<int> { 1, 2, 1, 2, 3, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6 };

foreach (var subset in IncreasingSubsetFinder<int>.Find(data))
    Console.WriteLine(string.Join(", ", subset));

【讨论】:

  • 我不喜欢这依赖于List&lt;T&gt;;如果结果是从IEnumerable&lt;T&gt; 流式传输的,那么这将不起作用。
  • @casperOne 当然不会——但是 OP 有一个列表,所以这没问题。如果它需要在IEnumerable&lt;int&gt; 上运行,那么您必须以不同的方式进行。但是,此代码假定 YAGNI
  • +1 您的回答完全符合规定的要求。恕我直言,在 OP 案例中使用 List&lt;T&gt; 时的最佳解决方案。
【解决方案2】:

这不是典型的 LINQ 操作,因此在这种情况下(当坚持使用 LINQ 时)我建议使用Aggregate 方法:

var result = data.Aggregate(new List<List<int>>(), (r, n) =>
{
    if (r.Count == 0 || n <= r.Last().Last()) r.Add(new List<int>());
    r.Last().Add(n);
    return r;
});

【讨论】:

  • @sam,那听起来你不明白Aggregate 方法。 @Ivan,这与我汇总的答案相似。 Linq 操作通常不会记住他们已经查看过的项目,因此使用累加器是一个好主意。
  • @sam 无需冒犯,请保持好语气。你表达了你的意见——很好。不同的人,不同的口味。与 XAML 相比,有些人觉得 RegEx 更漂亮,而有些人则相反。我的代码 sn-p 怎么样,对我来说 Aggregate 相当于一个 foreach 循环,所以我以一种专注于主体代码块的方式对其进行格式化,这是解决方案的重要部分。我同意变量名称不是描述性的,但是如果您查看linq 标签的答案,您会发现使用短名称是一种常见的做法,例如xyz 等.
  • 语气不错。对我来说,问题不是变量的聚合或短名称。您可以将 if 的内容放在新行上并避免使用 .Last().Last() 和 Last().Add()。正如我乍看之下所说,即使我完全理解它的作用,代码似乎也很混乱。因为它被 100 多位观众看到,所以我不想看到乱七八糟的东西,就像我在接待人之前整理我的家一样。祝你有美好的一天。
【解决方案3】:

您可以使用索引来获取上一项并通过比较值来计算组 id。然后按组 ID 分组并取出值:

List<int> data = new List<int> { 1, 2, 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6 };

int groupId = 0;
var groups = data.Select
                 ( (item, index)
                   => new
                      { Item = item
                      , Group = index > 0 && item <= data[index - 1] ? ++groupId : groupId
                      }
                 );

List<List<int>> list = groups.GroupBy(g => g.Group)
                             .Select(x => x.Select(y => y.Item).ToList())
                             .ToList();

【讨论】:

  • +1,只是将计算“Group”的逻辑修改为index > 0 && item data[index - 1]? ++groupId : groupId
【解决方案4】:

我真的很喜欢Matthew Watson's solution。但是,如果您不想依赖List&lt;T&gt;,这是我的简单通用方法,枚举可枚举最多一次,并且仍然保留惰性评估的能力。 p>

public static IEnumerable<IEnumerable<T>> AscendingSubsets<T>(this IEnumerable<T> superset) where T :IComparable<T>
{     
    var supersetEnumerator = superset.GetEnumerator();

    if (!supersetEnumerator.MoveNext())
    {
        yield break;
    }    

    T oldItem = supersetEnumerator.Current;
    List<T> subset = new List<T>() { oldItem };

    while (supersetEnumerator.MoveNext())
    {
        T currentItem = supersetEnumerator.Current;

        if (currentItem.CompareTo(oldItem) > 0)
        {
            subset.Add(currentItem);
        }
        else
        {
            yield return subset;
            subset = new List<T>() { currentItem };
        }

        oldItem = supersetEnumerator.Current;
    }

    yield return subset;
}

编辑:进一步简化了解决方案,只使用一个枚举器。

【讨论】:

    【解决方案5】:

    我已经修改了你的代码,现在可以正常工作了:

            List<int> data = new List<int> { 1, 2, 1, 2, 3,3, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6 };
            List<List<int>> resultLists = new List<List<int>>();
            int last = 0;
            int count = 0;
    
            var res = data.Where((p, i) =>
            {
                if (i > 0)
                {
                    if (p > last && p!=last)
                    {
                        resultLists[count].Add(p);
                    }
                    else
                    {
                        count++;
                        resultLists.Add(new List<int>());
                        resultLists[count].Add(p);
                    }
                }
                else
                {
                    resultLists.Add(new List<int>());
                    resultLists[count].Add(p);
                }
    
    
    
                last = p;
                return true;
            }).ToList();
    

    【讨论】:

      【解决方案6】:

      对于这样的事情,我通常不喜欢使用GroupBy 或其他实现结果的方法的解决方案。原因是您永远不知道输入序列将有多长,而实现这些子序列的成本可能非常高。

      我更喜欢在拉取结果时流式传输结果。这允许流结果的IEnumerable&lt;T&gt; 的实现通过您对该流的转换继续流式传输。

      请注意,如果您跳出对子序列的迭代并希望继续下一个序列,则此解决方案将不起作用;如果这是一个问题,那么实现子序列的解决方案之一可能会更好。

      但是,对于整个序列的仅向前迭代(这是最典型的用例),这将工作得很好。

      首先,让我们为我们的测试类设置一些帮助器:

      private static IEnumerable<T> CreateEnumerable<T>(IEnumerable<T> enumerable)
      {
          // Validate parameters.
          if (enumerable == null) throw new ArgumentNullException("enumerable");
      
          // Cycle through and yield.
          foreach (T t in enumerable)
              yield return t;
      }
      
      private static void EnumerateAndPrintResults<T>(IEnumerable<T> data,
          [CallerMemberName] string name = "") where T : IComparable<T>
      {
          // Write the name.
          Debug.WriteLine("Case: " + name);
      
          // Cycle through the chunks.
          foreach (IEnumerable<T> chunk in data.
              ChunkWhenNextSequenceElementIsNotGreater())
          {
              // Print opening brackets.
              Debug.Write("{ ");
      
              // Is this the first iteration?
              bool firstIteration = true;
      
              // Print the items.
              foreach (T t in chunk)
              {
                  // If not the first iteration, write a comma.
                  if (!firstIteration)
                  {
                      // Write the comma.
                      Debug.Write(", ");
                  }
      
                  // Write the item.
                  Debug.Write(t);
      
                  // Flip the flag.
                  firstIteration = false;
              }
      
              // Write the closing bracket.
              Debug.WriteLine(" }");
          }
      }
      

      CreateEnumerable 用于创建流式实现,EnumerateAndPrintResults 将获取序列,调用ChunkWhenNextSequenceElementIsNotGreater(即将出现并完成工作)并输出结果。

      这里是实现。注意,我选择在IEnumerable&lt;T&gt; 上将它们实现为扩展方法;这是第一个好处,因为它不需要物化序列(从技术上讲,其他解决方案也没有,但最好像这样明确声明)。

      一、入口点:

      public static IEnumerable<IEnumerable<T>>
          ChunkWhenNextSequenceElementIsNotGreater<T>(
              this IEnumerable<T> source)
          where T : IComparable<T>
      {
          // Validate parameters.
          if (source == null) throw new ArgumentNullException("source");
      
          // Call the overload.
          return source.
              ChunkWhenNextSequenceElementIsNotGreater(
                  Comparer<T>.Default.Compare);
      }
      
      public static IEnumerable<IEnumerable<T>>
          ChunkWhenNextSequenceElementIsNotGreater<T>(
              this IEnumerable<T> source,
                  Comparison<T> comparer)
      {
          // Validate parameters.
          if (source == null) throw new ArgumentNullException("source");
          if (comparer == null) throw new ArgumentNullException("comparer");
      
          // Call the implementation.
          return source.
              ChunkWhenNextSequenceElementIsNotGreaterImplementation(
                  comparer);
      }
      

      请注意,这适用于任何实现 IComparable&lt;T&gt; 或您提供 Comparison&lt;T&gt; 委托的地方;这允许您使用任何类型和任何类型的规则来执行比较。

      下面是实现:

      private static IEnumerable<IEnumerable<T>>
          ChunkWhenNextSequenceElementIsNotGreaterImplementation<T>(
              this IEnumerable<T> source, Comparison<T> comparer)
      {
          // Validate parameters.
          Debug.Assert(source != null);
          Debug.Assert(comparer != null);
      
          // Get the enumerator.
          using (IEnumerator<T> enumerator = source.GetEnumerator())
          {
              // Move to the first element.  If one can't, then get out.
              if (!enumerator.MoveNext()) yield break;
      
              // While true.
              while (true)
              {
                  // The new enumerator.
                  var chunkEnumerator = new 
                      ChunkWhenNextSequenceElementIsNotGreaterEnumerable<T>(
                          enumerator, comparer);
      
                  // Yield.
                  yield return chunkEnumerator;
      
                  // If the last move next returned false, then get out.
                  if (!chunkEnumerator.LastMoveNext) yield break;
              }
          }
      }
      

      注意:这使用另一个类ChunkWhenNextSequenceElementIsNotGreaterEnumerable&lt;T&gt; 来处理枚举子序列。此类将迭代从原始IEnumerable&lt;T&gt;.GetEnumerator() 调用获得的IEnumerator&lt;T&gt; 中的每个项目,但将最后一次调用的结果存储到IEnumerator&lt;T&gt;.MoveNext()

      存储这个子序列生成器,并检查最后一次调用MoveNext 的值以查看是否命中了序列的末尾。如果有,那么它就简单地中断,否则,它会移动到下一个块。

      这里是ChunkWhenNextSequenceElementIsNotGreaterEnumerable&lt;T&gt;的实现:

      internal class 
          ChunkWhenNextSequenceElementIsNotGreaterEnumerable<T> : 
              IEnumerable<T>
      {
          #region Constructor.
      
          internal ChunkWhenNextSequenceElementIsNotGreaterEnumerable(
              IEnumerator<T> enumerator, Comparison<T> comparer)
          {
              // Validate parameters.
              if (enumerator == null) 
                  throw new ArgumentNullException("enumerator");
              if (comparer == null) 
                  throw new ArgumentNullException("comparer");
      
              // Assign values.
              _enumerator = enumerator;
              _comparer = comparer;
          }
      
          #endregion
      
          #region Instance state.
      
          private readonly IEnumerator<T> _enumerator;
      
          private readonly Comparison<T> _comparer;
      
          internal bool LastMoveNext { get; private set; }
      
          #endregion
      
          #region IEnumerable implementation.
      
          public IEnumerator<T> GetEnumerator()
          {
              // The assumption is that a call to MoveNext 
              // that returned true has already
              // occured.  Store as the previous value.
              T previous = _enumerator.Current;
      
              // Yield it.
              yield return previous;
      
              // While can move to the next item, and the previous 
              // item is less than or equal to the current item.
              while ((LastMoveNext = _enumerator.MoveNext()) && 
                  _comparer(previous, _enumerator.Current) < 0)
              {
                  // Yield.
                  yield return _enumerator.Current;
      
                  // Store the previous.
                  previous = _enumerator.Current;
              }
          }
      
          IEnumerator IEnumerable.GetEnumerator()
          {
              return GetEnumerator();
          }
      
          #endregion
      }
      

      这是问题中原始条件的测试以及输出:

      [TestMethod]
      public void TestStackOverflowCondition()
      {
          var data = new List<int> { 
              1, 2, 1, 2, 3, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6 
          };
          EnumerateAndPrintResults(data);
      }
      

      输出:

      Case: TestStackOverflowCondition
      { 1, 2 }
      { 1, 2, 3 }
      { 3 }
      { 1, 2, 3, 4 }
      { 1, 2, 3, 4, 5, 6 }
      

      这是相同的输入,但以可枚举的形式流式传输:

      [TestMethod]
      public void TestStackOverflowConditionEnumerable()
      {
          var data = new List<int> { 
              1, 2, 1, 2, 3, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6 
          };
          EnumerateAndPrintResults(CreateEnumerable(data));
      }
      

      输出:

      Case: TestStackOverflowConditionEnumerable
      { 1, 2 }
      { 1, 2, 3 }
      { 3 }
      { 1, 2, 3, 4 }
      { 1, 2, 3, 4, 5, 6 }
      

      这是一个非顺序元素的测试:

      [TestMethod]
      public void TestNonSequentialElements()
      {
          var data = new List<int> { 
              1, 3, 5, 7, 6, 8, 10, 2, 5, 8, 11, 11, 13 
          };
          EnumerateAndPrintResults(data);
      }
      

      输出:

      Case: TestNonSequentialElements
      { 1, 3, 5, 7 }
      { 6, 8, 10 }
      { 2, 5, 8, 11 }
      { 11, 13 }
      

      最后,这是一个用字符代替数字的测试:

      [TestMethod]
      public void TestNonSequentialCharacters()
      {
          var data = new List<char> { 
              '1', '3', '5', '7', '6', '8', 'a', '2', '5', '8', 'b', 'c', 'a' 
          };
          EnumerateAndPrintResults(data);
      }
      

      输出:

      Case: TestNonSequentialCharacters
      { 1, 3, 5, 7 }
      { 6, 8, a }
      { 2, 5, 8, b, c }
      { a }
      

      【讨论】:

      • 这是最好的方法,但我认为您的代码可以通过在 yiling 之前保存子序列的最后一项并从那时起与该值进行比较来简化。你做的事情比这更复杂。你真的需要 ChunkWhenNextSequenceElementIsNotGreaterEnumerable 吗?
      • @sam 您需要该类才能在子序列中保存对MoveNext 的最后一次调用的状态。如果你不存储它,那么你就无法确定序列是否已经结束并打破外循环。此外,您需要 something 来产生内部序列。它可能是一个函数,但您必须以某种方式将最后一次调用的结果返回给MoveNext。我想这可以通过Tuple&lt;T1, T2&gt; 来完成,但是将它分解成它自己的类(IMO)似乎更干净。
      【解决方案7】:

      您可以使用 Linq 使用索引来计算组:

      var result = data.Select((n, i) => new { N = n, G = (i > 0 && n > data[i - 1] ? data[i - 1] + 1 : n) - i })
                       .GroupBy(a => a.G)
                       .Select(g => g.Select(n => n.N).ToArray())
                       .ToArray();
      

      【讨论】:

      • 您的解决方案做了一些假设在 OP 的问题中不存在。您假设每个数字都增加一。 OP只提到较低的数字。您的解决方案适用于 OP 的示例,但不适用于此列表 {1,3,1} 根据描述应该生成两个列表,而不是三个。
      • @user2023861:是的,我要添加假设。
      • @user2023861:更新为 OP 的问题。
      【解决方案8】:

      这是我使用一些产量的简单循环方法:

      static IEnumerable<IList<int>> Split(IList<int> data) { if (data.Count == 0) yield break; List<int> curr = new List<int>(); curr.Add(data[0]); int last = data[0]; for (int i = 1; i < data.Count; i++) { if (data[i] <= last) { yield return curr; curr = new List<int>(); } curr.Add(data[i]); last = data[i]; } yield return curr; }

      【讨论】:

        【解决方案9】:

        我使用字典来获取 5 个不同的列表,如下所示;

        static void Main(string[] args)
            {
                List<int> data = new List<int> { 1, 2, 1, 2, 3, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6 };
                Dictionary<int, List<int>> listDict = new Dictionary<int, List<int>>();
        
                int listCnt = 1;
                //as initial value get first value from list
                listDict.Add(listCnt, new List<int>());
                listDict[listCnt].Add(data[0]);
        
        
                for (int i = 1; i < data.Count; i++)
                {
                     if (data[i] > listDict[listCnt].Last())
                    {
                        listDict[listCnt].Add(data[i]);
                    }
                     else
                    {
                        //increase list count and add a new list to dictionary
                        listCnt++;
                        listDict.Add(listCnt, new List<int>());
                        listDict[listCnt].Add(data[i]);
                    }
                }
        
                //to use new lists
                foreach (var dic in listDict)
                {
                    Console.WriteLine( $"List {dic.Key} : " + string.Join(",", dic.Value.Select(x => x.ToString()).ToArray()));
        
                }
        
            }
        

        输出:

        List 1 : 1,2
        List 2 : 1,2,3
        List 3 : 3
        List 4 : 1,2,3,4
        List 5 : 1,2,3,4,5,6
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-09-22
          • 1970-01-01
          • 2018-11-25
          • 2019-06-05
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多