Split a list into multiple lists at increasing sequence broken

前端 未结 9 1172
不知归路
不知归路 2021-02-18 19:11

I\'ve a List of int and I want to create multiple List after splitting the original list when a lower or same number is found. Numbers are not in sorted order.<

9条回答
  •  时光取名叫无心
    2021-02-18 19:49

    For things like this, I'm generally not a fan of solutions that use GroupBy or other methods that materialize the results. The reason is that you never know how long the input sequence will be, and materializations of these sub-sequences can be very costly.

    I prefer to stream the results as they are pulled. This allows implementations of IEnumerable that stream results to continue streaming through your transformation of that stream.

    Note, this solution won't work if you break out of iterating through the sub-sequence and want to continue to the next sequence; if this is an issue, then one of the solutions that materialize the sub-sequences would probably be better.

    However, for forward-only iterations of the entire sequence (which is the most typical use case), this will work just fine.

    First, let's set up some helpers for our test classes:

    private static IEnumerable CreateEnumerable(IEnumerable 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(IEnumerable data,
        [CallerMemberName] string name = "") where T : IComparable
    {
        // Write the name.
        Debug.WriteLine("Case: " + name);
    
        // Cycle through the chunks.
        foreach (IEnumerable 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 is used for creating a streaming implementation, and EnumerateAndPrintResults will take the sequence, call ChunkWhenNextSequenceElementIsNotGreater (this is coming up and does the work) and output the results.

    Here's the implementation. Note, I've chosen to implement them as extension methods on IEnumerable; this is the first benefit, as it doesn't require a materialized sequence (technically, none of the other solutions do either, but it's better to explicitly state it like this).

    First, the entry points:

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

    Note that this works on anything that implements IComparable or where you provide a Comparison delegate; this allows for any type and any kind of rules you want for performing the comparison.

    Here's the implementation:

    private static IEnumerable>
        ChunkWhenNextSequenceElementIsNotGreaterImplementation(
            this IEnumerable source, Comparison comparer)
    {
        // Validate parameters.
        Debug.Assert(source != null);
        Debug.Assert(comparer != null);
    
        // Get the enumerator.
        using (IEnumerator 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(
                        enumerator, comparer);
    
                // Yield.
                yield return chunkEnumerator;
    
                // If the last move next returned false, then get out.
                if (!chunkEnumerator.LastMoveNext) yield break;
            }
        }
    }
    

    Of note: this uses another class ChunkWhenNextSequenceElementIsNotGreaterEnumerable to handle enumerating the sub-sequences. This class will iterate each of the items from the IEnumerator that is obtained from the original IEnumerable.GetEnumerator() call, but store the results of the last call to IEnumerator.MoveNext().

    This sub-sequence generator is stored, and the value of the last call to MoveNext is checked to see if the end of the sequence has or hasn't been hit. If it has, then it simply breaks, otherwise, it moves to the next chunk.

    Here's the implementation of ChunkWhenNextSequenceElementIsNotGreaterEnumerable:

    internal class 
        ChunkWhenNextSequenceElementIsNotGreaterEnumerable : 
            IEnumerable
    {
        #region Constructor.
    
        internal ChunkWhenNextSequenceElementIsNotGreaterEnumerable(
            IEnumerator enumerator, Comparison 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 _enumerator;
    
        private readonly Comparison _comparer;
    
        internal bool LastMoveNext { get; private set; }
    
        #endregion
    
        #region IEnumerable implementation.
    
        public IEnumerator 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
    }
    

    Here's the test for the original condition in the question, along with the output:

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

    Output:

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

    Here's the same input, but streamed as an enumerable:

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

    Output:

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

    Here's a test with non-sequential elements:

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

    Output:

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

    Finally, here's a test with characters instead of numbers:

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

    Output:

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

提交回复
热议问题