1

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?

Arianule
  • 8,811
  • 45
  • 116
  • 174
  • The value in `Value` is what was originally the group's key. It is the nature of `GroupBy` that you are not going to get the same key appearing twice in one enumeration. – Abion47 Dec 19 '16 at 14:41
  • 1
    Not sure I follow. What is the expected output from `{1, 1, 1, 1, 1, 1, 1 }` (seven ones)? – Ivan Stoev Dec 19 '16 at 14:53

2 Answers2

2

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}
Community
  • 1
  • 1
Arturo Menchaca
  • 15,783
  • 1
  • 29
  • 53
  • I don't think this is what OP is going for. This arbitrarily segments the result into groups of 3 objects (at most), but OP seems to want to use a variant of `GroupBy` that only groups adjacent objects in the source list. – Abion47 Dec 19 '16 at 15:02
2

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}
Abion47
  • 22,211
  • 4
  • 65
  • 88