Reputation: 9043
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
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
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