【问题标题】:Splitting an IEnumerable into two将 IEnumerable 拆分为两个
【发布时间】:2012-10-11 19:45:03
【问题描述】:

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

输入:

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

输出:

  • “真实”列表
  • “假”列表

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

【问题讨论】:

  • 您使用IEnumerableListSet,它们在计算机科学中都有不同的含义,用于同一件事。假设您每次都指的是一个序列IEnumerable 是否正确?

标签: c# linq


【解决方案1】:

这是一种简单的方法。请注意,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();

【讨论】:

  • (nitpick) GroupBy 也急切地评估输入。
  • @Rawling GroupBy 在您第一次开始迭代 IEnumerable&lt;IGrouping&gt; 时立即评估输入。 (即,如果在var groups = ...; 之后有一些代码,则输入将不会被迭代。)ToLookup 在调用该方法后立即评估输入。本质上的区别在于,如果您在调用 groupby/lookup 之后和获取 trueListfalseList 变量之前放置修改输入序列结果的代码。如果两者之间没有任何内容(如在我的代码中),那么我将只使用 ToLookup,因为它将是更清晰的代码。
  • 我不确定您的第二种方法,我的意思是,您对 Expro 的回答是他的方法枚举了两次源,但是使用您的 GroupBy 方法,您是否也这样做?问题说这两个序列都将被使用,因此您也将进行 2 个(惰性)枚举。除此之外,我同意通常您有 2 个选项:源的 2 个实时枚举,或仅 1 个枚举 + 缓冲。
  • @Wasp 是的,这就是我强调 ToLookup 方法的原因。只有当有一些令人信服的理由推迟缓冲数据时,才应该真正使用 GroupBy;我认为可以做一些事情来获得真假枚举,同时只枚举一次源,如果你真的,真的需要两全其美。
【解决方案2】:

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

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

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),
    ...
    );

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

【讨论】:

  • 虽然一开始这看起来很酷,但它在我的嘴里留下了一点不好的味道。我认为这与这样一个事实有关,即这只会因为它产生的副作用而真正有用,而产生副作用的 LINQ 查询是......不可取的。我想到了两个可能的变化:急切地评估序列,而不是懒惰地评估序列,和/或拥有一个采用 N 个“处理”方法的方法(通过 paramsIEnumerable&lt;Func&lt;T,bool&gt;&gt;
  • 说实话,我完全可以看出你来自哪里。
  • 现在我已经考虑过了,这个方法实际上只是一个Where,它只返回函数返回false而不是true的项目。 (减去Where 急切地验证输入参数不为空的事实。)
  • 是的,我注意到在编辑时(除了null 检查)...真正理想 的做法是完全避免使用LINQ。 ..
【解决方案3】:

我同意 Servy 的回答,但是在 cmets 进行之后,我认为这种方法可能会很有趣:

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);

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

【讨论】:

    【解决方案4】:

    尽可能使用 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;
    }
    

    【讨论】:

    • 这并不完全等同。这里的一个主要问题是您要枚举source 两次。一次用于Where,一次用于Except。如果这两个枚举不一样,那将是……坏的。 Chills42 的代码没有这个问题。
    猜你喜欢
    • 2010-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多