如何通过保留IGroupings来过滤IEnumerable IGrouping的元素

时间:2018-08-24 08:55:54

标签: c# linq

我有一个IGrouping个序列

IEnumerable<IGrouping<Key, Element>> sequence = ...;

和谓词

Func<Element, bool> predicate = ...;

如何通过将结果保持为Element而不使序列变平并完全重新创建分组来过滤IEnumerable<IGrouping<Key, Element>>

我可以做类似的事情

IEnumerable<IGrouping<Key, Element>> filtered =
    from grouping in sequence
    from element in grouping
    where predicate(element)
    select new { grouping.Key, element } into keyElem
    group keyElem.element by keyElem.Key into g
    select g;

但是,这取决于序列的大小和所涉及的类型,将需要一些时间和/或内存。

我想“简单地”过滤IGrouping内的元素。我会在结果中接受空分组。

(我认为可以通过回答如何过滤IEnumerable<IEnumerable<Element>>的元素同时将结果保留为IEnumerable<IEnumerable<Element>>的问题来回答这个问题。)

2 个答案:

答案 0 :(得分:2)

您无法修改现有的IGrouping,但可以轻松创建自己的实现和帮助程序方法:

public static class Groupings
{
    public static IGrouping<TKey, TElement> FilterElements(
        this IGrouping<TKey, TValue> grouping,
        Func<TValue, bool> predicate) =>
        new LateGrouping<TKey, TValue>(grouping.Key, grouping.Where(predicate));
}

internal class LateGrouping<TKey, TElement> : IGrouping<TKey, TElement>
{
    public TKey Key { get; }
    private readonly IEnumerable<TElement> elements;

    public LateGrouping(TKey key, IEnumerable<TElement> elements)
    {
        Key = key;
        this.elements = elements;
    }

    public IEnumerator<TElement> GetEnumerator() => elements.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

然后,您只需拿起IEnumerable<IGrouping<...>>并使用常规选择,在内部进行过滤:

groupings = groupings.Select(g => g.FilterElements(predicate));

答案 1 :(得分:0)

无法从IGrouping删除对这个想法(How to remove an element from an IGrouping)至关重要的项目。

最漂亮的方法是在此之后删除Enities并删除空的IGroupings:

IEnumerable<IGrouping<string, object>> sequence = null;
Func<object, bool> predicate = null;
// Would be nice if this would work
//Parallel.ForEach(sequence, y => y.Remove(y.Where(z => predicate(z))); });
sequence = sequence.Where(s => s.Count() == 0);

因此我们需要为每个IGrouping

组成一个新对象
IEnumerable<IGrouping<string, object>> sequence = null;
Func<object, bool> predicate = null;

IEnumerable<IGrouping<string, object>> result2 = sequence.Select(s =>
{
    var t = new MyGrouping<string, object>() { Key = s.Key };
    t.AddRange(s.Where(w => predicate(w)));
    return t;
}).Where(s => s.Count > 0);

但是您将需要此类:

public class MyGrouping<TKey, TElement> : List<TElement>, IGrouping<TKey, TElement>
{
    public TKey Key
    {
        get;
        set;
    }
}

我喜欢简单的一个:

IEnumerable<IGrouping<string, object>> sequence = null;
Func<object, bool> predicate = null;

// Make it a List again
var tmpList = sequence.SelectMany(s => s.ToList().Select(l => new { key = s.Key, val = l }));
// Filter your List
var tmpFilteredList = tmpList.Where(l => predicate(l.val));
// Group it back again
IEnumerable<IGrouping<string, object>> result = tmpFilteredList.GroupBy(g => g.key, g => g.val);