简体   繁体   中英

how to count continuous values in a list with linq

I've a list like this:

var query = Enumerable.Range(0, 999).Select((n, index) =>
        {
            if (index <= 333 || index >=777)
                return 0;
            else if (index <= 666)
                return 1;
            else
                return 2;
        });

So, Can I find how much indexes have same value continuously? For example;

query[0]=query[1]=query[2]=query[3]... = 0, query[334] = 1, query[777]=query[778]... = 0.

First 334 indexes have 0, so first answer is 333. Also Last 223 indexes have 0, so second answer is 223..

How can I find these and their indexes?

Thanks in advance.

Using the GroupConsecutive extension method from here you can just get the counts of each group:

query.GroupConsecutive((n1, n2) => n1 == n2)
     .Select(g => new {Number = g.Key, Count = g.Count()})

You can create extension for consecutive grouping of items by some key:

public static IEnumerable<IGrouping<TKey, T>> GroupConsecutive<T, TKey>(
    this IEnumerable<T> source, Func<T, TKey> keySelector)
{
    using (var iterator = source.GetEnumerator())
    {
        if (!iterator.MoveNext()) 
            yield break;            
        else 
        {
            List<T> list = new List<T>();
            var comparer = Comparer<TKey>.Default;
            list.Add(iterator.Current);
            TKey groupKey = keySelector(iterator.Current);

            while (iterator.MoveNext())
            {
                var key = keySelector(iterator.Current);
                if (!list.Any() || comparer.Compare(groupKey, key) == 0)
                {
                    list.Add(iterator.Current);
                    continue;
                }

                yield return new Group<TKey, T>(groupKey, list);
                list = new List<T> { iterator.Current };
                groupKey = key;
            }

            if (list.Any())
                yield return new Group<TKey, T>(groupKey, list);
        }
    }
}

Of course you can return IEnumerable<IList<T>> but that will be a little different from concept of group, which you want to have, because you also want to know which value was used to group sequence of items. Unfortunately there is no public implementation of IGrouping<TKey, TElement> interface, and we should create our own:

public class Group<TKey, TElement> : IGrouping<TKey, TElement>
{
    private TKey _key;
    private IEnumerable<TElement> _group;

    public Group(TKey key, IEnumerable<TElement> group)
    {
        _key = key;
        _group = group;
    }

    public TKey Key
    {
        get { return _key; }
    }

    public IEnumerator<TElement> GetEnumerator()
    {
        return _group.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Now usage is very simple:

var groups =  query.GroupConsecutive(i => i) // produces groups
                   .Select(g => new { g.Key, Count = g.Count() }); // projection

Result:

[
  { Key: 0, Count: 334 },
  { Key: 1, Count: 333 },
  { Key: 2, Count: 110 },
  { Key: 0, Count: 222 }
]
    public static IEnumerable<int> GetContiguousCounts<T>(this IEnumerable<T> l, IEqualityComparer<T> cmp)
    {
        var last = default(T);
        var count = 0;
        foreach (var e in l)
        {
            if (count > 0 && !cmp.Equals(e, last))
            {
                yield return count;
                count = 0;
            }
            count++;
            last = e;
        }
        if (count > 0)
            yield return count;
    }

    public static IEnumerable<int> GetContiguousCounts<T>(this IEnumerable<T> l)
    {
        return GetContiguousCounts(l, EqualityComparer<T>.Default);
    }

    static void Main(string[] args)
    {
        var a = new[] { 1, 2, 2, 3, 3, 3 };
        var b = a.GetContiguousCounts();
        foreach (var x in b)
            Console.WriteLine(x);
    }

For the simple test case, it outputs 1, 2, 3. For your case 334, 333, 110, 222 (the last value is not 223 as you asked in your question, because you only have 999 elements, not 1000).

erm, how about this, most efficient implementation I can think of.

 IEnuemrable<KeyValuePair<T, int>> RepeatCounter<T>(
         IEnumerable<T> source,
         IEqualityComparer<T> comparer = null)
{
    var e = source.GetEnumerator();
    if (!e.MoveNext())
    {
        yield break;
    }

    comparer = comparer ?? EqualityComparer<T>.Default;

    var last = e.Current;
    var count = 1;
    while (e.MoveNext())
    {
        if (comparer.Equals(last, e.Current))
        {
            count++;
            continue;
        }

        yield return new KeyValuePair<T, int>(last, count);
        last = e.Current;
        count = 1;
    }

    yield return new KeyValuePair<T, int>(last, count);
}

enumerates the sequence exactly once and only allocates variables when necessary.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM