在增加序列中断时将列表拆分为多个列表



我有一个 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();

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

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<T>,那么我能想到的最简单的方法是这样的:

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

这不是典型的 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;
});

您可以使用索引获取上一项,并通过比较值来计算组 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();

我真的很喜欢Matthew Watson的解决方案。但是,如果您不想依赖List<T>,这是我简单的通用方法,最多枚举一次可枚举,并且仍然保留惰性求值的能力。

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

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

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

        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();

对于这样的事情,我通常不喜欢使用GroupBy或其他方法实现结果的解决方案。 原因是你永远不知道输入序列会有多长,而且这些子序列的具体化可能非常昂贵。

我更喜欢在结果被拉动时流式传输结果。 这允许流结果IEnumerable<T>的实现通过该流的转换继续流式传输。

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

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

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

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<T>上的扩展方法;这是第一个好处,因为它不需要物化序列(从技术上讲,其他解决方案也不需要,但最好像这样明确说明(。

一、切入点:

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<T>或提供Comparison<T>委托的任何内容;这允许执行比较所需的任何类型和任何类型的规则。

下面是实现:

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<T>来处理枚举子序列。 此类将迭代从原始IEnumerable<T>.GetEnumerator()调用获取的IEnumerator<T>中的每个项目,但存储上次调用的结果以IEnumerator<T>.MoveNext()

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

以下是ChunkWhenNextSequenceElementIsNotGreaterEnumerable<T>的实现:

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 }

您可以使用 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();
这是我

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

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

我使用字典获取 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

最新更新