Arianule
Arianule

Reputation: 9043

GroupBy returning Count and Value on condition

The title of this question is a bit strange so thanks for that.

What I want to achieve is is to use a groupby on numbers but only when the count of the value is 3.

To clarify...as an example I have the following array of values.

int[] nums= new int[] { 1, 1, 1, 3, 1 };

if I do a group by on nums like this

var vals = dice.GroupBy(t => t).Select(p => new { Value = p.Key, Count = p.Count() }).ToList();

it will return will be the following

{Value = 1, Count = 4},
{Value = 3, Count = 1}

and what I actually want is this.

{Value = 1, Count = 3},
{Value  = 1, Count = 1}
{Value = 3, Count = 1}

Can one use the GroupBy method to achieve this or does the approach need to be different?

Upvotes: 1

Views: 70

Answers (2)

Abion47
Abion47

Reputation: 24661

I don't think there is a native LINQ solution for what you are looking for. (Or if there is, it will probably be somewhat large and unwieldy.) However, you could write an extension method that should do the job:

public static class IEnumerableExtensions
{
    public class Grouping<TKey, TElement> : IGrouping<TKey, TElement>
    {
        readonly List<TElement> elements;

        public Grouping(TKey key, List<TElement> elems)
        {
            Key = key;
            elements = elems;
        }

        public TKey Key { get; private set; }

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

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

    public static IEnumerable<IGrouping<TKey, TElement>> GroupByConcurrent<TSource, TKey, TElement>(
        this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector,
        Func<TSource, TElement> elementSelector)
    where TKey : IEquatable<TKey>
    {
        if (source == null)
            throw new ArgumentNullException("source");

        TKey currentKey = default(TKey);
        List<TElement> currentList = null;

        foreach (var s in source)
        {
            var key = keySelector.Invoke(s);
            var elem = elementSelector.Invoke(s);

            if (!key.Equals(currentKey) || currentList == null)
            {
                if (currentList != null && currentList.Count > 0)
                    yield return new Grouping<TKey, TElement>(currentKey, currentList);

                currentKey = key;
                currentList = new List<TElement>();
            }

            currentList.Add(elem);
        }

        if (currentList != null && currentList.Count > 0)
             yield return new Grouping<TKey, TElement>(currentKey, currentList);
    }
}

You can call it like so:

int[] nums = new int[] { 1, 1, 1, 3, 1 };

var concurGrouped = nums.GroupByConcurrent(t => t, t => t)
                        .Select(p => new { Value = p.Key, Count = p.Count() })
                        .ToList();

// Contents of concurGrouped: 
// 
// { Value = 1, Count = 3},
// { Value = 1, Count = 1},
// { Value = 3, Count = 1}

Upvotes: 2

Arturo Menchaca
Arturo Menchaca

Reputation: 15982

You can use this Chunkify modified version of this answer to make chunks each group:

static class ChunkExtension
{
    public static IEnumerable<IEnumerable<T>> Chunkify<T>(
        this IEnumerable<T> source, int size)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (size < 1) throw new ArgumentOutOfRangeException("size");
        using (var iter = source.GetEnumerator())
        {
            while (iter.MoveNext())
            {
                var chunk = new List<T>();
                chunk.Add(iter.Current);
                for (int i = 1; i < size && iter.MoveNext(); i++)
                    chunk.Add(iter.Current);

                yield return chunk;
            }
        }
    }
}

int[] nums = new int[] { 1, 1, 1, 3, 1 };

var groups = nums.GroupBy(n => n)
                 .Select(g => g.Chunkify(3)
                               .Select(x => new { Value = g.Key, Count = x.Count() }))
                 .SelectMany(g => g);

You get something like this:

{Value = 1, Count = 3},
{Value  = 1, Count = 1}
{Value = 3, Count = 1}

Upvotes: 2

Related Questions