20

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.

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

I want the result to be as following lists:

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

Currently, I'm using following linq to do this but not helping me out:

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();
3
  • Having the count variable inside the LinQ, won't that cause an issue where everything is always added to the 0th list? Commented May 6, 2016 at 11:02
  • 1
    Are increasing numbers consecutive as in your example, or could they have gaps? A very nice solution exists for when the numbers are consecutive. Commented May 6, 2016 at 11:21
  • It's just for illustration. It's not necessary that there is any sort order for numbers. Commented May 6, 2016 at 11:56

9 Answers 9

10

I'd just go for something simple:

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

Which you'd use like so:

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

If you really did need to work with IEnumerable<T>, then the simplest way I can think of is like this:

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

Which you would call like this:

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));
Sign up to request clarification or add additional context in comments.

3 Comments

I don't like that this relies on a List<T>; if the results were being streamed from an IEnumerable<T>, then this wouldn't work.
@casperOne Of course it wouldn't - but the OP has a list, so this is no problem. If it needed to run on an IEnumerable<int> then you'd have to do it differently. However, this code assumes YAGNI
+1 Your answer fits the stated requirements perfectly. IMHO the best solution when using List<T> as in the OPs case.
5

This is not a typical LINQ operation, so as usual in such cases (when one insists on using LINQ) I would suggest using Aggregate method:

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

3 Comments

@sam, it sounds like you don't understand the Aggregate method then. @Ivan, this is similar to the answer I'd put together. Linq operations generally don't remember items they've already looked at, so using the accumulator to your advantage is a good idea.
@sam There is no need to be offensive, please keep the good tone. You showed your opinion - fine. Different people, different tastes. Some people find RegEx beautiful compared to XAML and some do the opposite. What about my code snippet, for me Aggregate is equivalent of a foreach loop, so I formatted it in a way that concentrates on the body code block, which is the essential part of the solution. I agree that the variable names are not descriptive, but if you take a look at the linq tag answers, you'll see that it's a common practice to use short names like x, y, z etc.
Ok with the tone. The problem for me is not Aggreagate or short names of variables. You could place the content of the if on a new line and avoid .Last().Last() and Last().Add(). As i said at first glance the code seems messy even if i understand exactly what it does. Since it is seen by 100s viewers i'd like not to see messy things the same way I tidy up my home before i receive people. Have a nice day.
3

You can use the index to get the previous item and calculate the group id out of comparing the values. Then group on the group ids and get the values out:

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 Comment

+1, just modify the calculating "Group" logic to index > 0 && item <= data[index - 1] ? ++groupId : groupId
3

I really like Matthew Watson's solution. If however you do not want to rely on List<T>, here is my simple generic approach enumerating the enumerable once at most and still retaining the capability for lazy evaluation.

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

Edit: Simplified the solution further to only use one enumerator.

Comments

2

I have modified your code, and now working fine:

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

Comments

2

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

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

Here's the implementation:

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

Of note: this uses another class ChunkWhenNextSequenceElementIsNotGreaterEnumerable<T> to handle enumerating the sub-sequences. This class will iterate each of the items from the IEnumerator<T> that is obtained from the original IEnumerable<T>.GetEnumerator() call, but store the results of the last call to IEnumerator<T>.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<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
}

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

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

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<int> { 
        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<int> { 
        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<char> { 
        '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 }

2 Comments

This is the best approach but i think your code can be simplified just by saving the last item of the subsequence before yileding and compare to that value from then on. You do something more complicated than that. Do you really need ChunkWhenNextSequenceElementIsNotGreaterEnumerable?
@sam You need the class in order to save the state of the last call to MoveNext in the subsequence. If you don't store that, then there's no way that you can determine whether or not the sequence has ended and break the outer loop. Also, you need something to yield the inner sequence. It could be a function, but you'd have to return the result of the last call to MoveNext somehow. This could be done through a Tuple<T1, T2> I suppose, but it just seemed cleaner to break it out into it's own class (IMO).
2

You can do it with Linq using the index to calculate the group:

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

3 Comments

Your solution makes some assumptions not present in OP's question. You're assuming that each number increments by one. The OP only mentions lower numbers. Your solution works with the OP's examples, but it wouldn't work with this list {1,3,1} which according to the description should generate two lists, not three.
@user2023861: It's true, I going to add the assumption.
@user2023861: Updated to OP's question.
1

This is my simple loop approach using some yields :

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

Comments

0

I use a dictionary to get 5 different list as below;

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

        }

    }

Output :

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.