将IEnumerable拆分为两个

时间:2012-10-11 19:45:03

标签: c# linq

我想将一个列表分成两个列表,一个可以直接处理,另一个是余数,它将通过链传递给其他处理程序。

输入:

  • 一个项目列表
  • 一种过滤方法,用于确定将项目包含在哪个列表中。

输出:

  • “真实”清单
  • “虚假”名单

这已经存在了吗?也许是我目前没想到的Linq方法?否则,是否有人有一个很好的C#示例?

4 个答案:

答案 0 :(得分:19)

这是一个简单的方法。请注意,ToLookup急切地评估输入序列。

List<int> list = new List<int> { 1, 2, 3, 4, 5, 6 };

var lookup = list.ToLookup(num => num % 2 == 0);

IEnumerable<int> trueList = lookup[true];
IEnumerable<int> falseList = lookup[false];

您可以使用GroupBy对输入序列进行延迟评估,但它不是相当漂亮:

var groups = list.GroupBy(num => num % 2 == 0);

IEnumerable<int> trueList = groups.Where(group => group.Key).FirstOrDefault();
IEnumerable<int> falseList = groups.Where(group => !group.Key).FirstOrDefault();

答案 1 :(得分:3)

经过一些考虑和一些相当垃圾的想法,我得出结论:不要试图让LINQ为你做这件事。

有一个简单的循环消耗你的输入序列,将每个元素传递给第一个处理它的“处理程序”,并确保你的最后一个处理程序捕获所有内容或者最坏的情况返回List而不是IEnumerable

public static void Handle(
    IEnumerable<T> source,
    Action<T> catchAll,
    params Func<T, bool>[] handlers)
{
    foreach (T t in source)
    {
        int i = 0; bool handled = false;
        while (i < handlers.Length && !handled)
            handled = handlers[i++](t);
        if (!handled) catchAll(t);
    }
}

// e.g.
public bool handleP(int input, int p)
{
    if (input % p == 0)
    {
        Console.WriteLine("{0} is a multiple of {1}", input, p);
        return true;
    }
    return false;
}


Handle(
    source,
    i => { Console.WriteLine("{0} has no small prime factor"); },
    i => handleP(i, 2),
    i => handleP(i, 3),
    ...
    );

这样做的好处是可以处理输入顺序中的每个元素,而不是将它们分成组,并在随后执行任何操作之前丢失顺序。

答案 2 :(得分:2)

我同意Servy的回答,但在发表评论之后,我认为这种方法很有意思:

static class EnumerableExtensions
{
    public static IEnumerable<TSource> Fork<TSource>(
        this IEnumerable<TSource> source,
        Func<TSource, bool> filter,
        Action<TSource> secondary)
    {
        if (source == null) throw new ArgumentNullException("source");
        //...

        return ForkImpl(source, filter, secondary);
    }

    private static IEnumerable<TSource> ForkImpl<TSource>(
        this IEnumerable<TSource> source,
        Func<TSource, bool> filter,
        Action<TSource> secondary)
    {
        foreach(var e in source)
            if (filter(e))
                yield return e;
            else
                secondary(e);
    }
}

这可以这样使用:

var ints = new [] { 1,2,3,4,5,6,7,8,9 };

// one possible use of the secondary sequence: accumulation
var acc = new List<int>();

foreach (var i in ints.Fork(x => x % 2 == 0, t => acc.Add(t)))
{
    //...
}

// later on we can process the accumulated secondary sequence
process(acc);

这里我们对二级序列进行累积(“假”值),但是这个二级序列的实时处理也是可能的,因此只需要一次枚举源。

答案 3 :(得分:0)

尽可能使用LINQ:

public IEnumerable<T> Filter(IEnumerable<T> source, Func<T, bool> criterium, out IEnumerable<T> remaining)
{
    IEnumerable<T> matching = source.Where(criterium);
    remaining = source.Except(matching);

    return matching;
}