寻找特定的Enumerable运算符序列:TakeWhile(!)+ Concat Single

时间:2011-03-29 21:51:19

标签: c# linq

给定一个Enumerable,我想Take()所有元素,包括一个终结符(如果找不到终结符则抛出异常)。类似的东西:

list.TakeWhile(v => !condition(v)).Concat(list.Single(condition))

..除了不蹩脚。只想走一次。

对于.NET 4和Rx中的当前运算符,这是否可以简化,或者我是否需要编写新的运算符?

编写运算符会比写这个问题花费更少的时间(尽管我认为有一半的时间会弄清楚这个函数的名称),但我只是不想复制已存在的东西。

更新

好的,这是运营商。非常令人兴奋,我知道。无论如何,可以从内置运算符构建它吗?

    public static IEnumerable<T> TakeThroughTerminator<T>([NotNull] this IEnumerable<T> @this, Func<T, bool> isTerminatorTester)
    {
        foreach (var item in @this)
        {
            yield return item;
            if (isTerminatorTester(item))
            {
                yield break;
            }
        }

        throw new InvalidOperationException("Terminator not found in list");
    }

3 个答案:

答案 0 :(得分:2)

如果您不想编写自己的运算符,这里有一些硬核:

var input = Enumerable.Range(1, 10);

var condition = new Func<int, bool>(i => i < 5);

bool terminatorPassed = false;
var condition2 = new Func<int, bool>(i =>
        {
            try { return !terminatorPassed; }
            finally { terminatorPassed = !condition(i); }
        });

var result = input.TakeWhile(condition2).ToArray();
if (!terminatorPassed) throw new FutureException("John Connor survived");

答案 1 :(得分:2)

没有内置功能可以有效地进行这样的操作。人们通常不需要获得满足条件的物品,而不需要更多的物品。你必须自己写。

但是你可以使用现有方法构建它,它只是效率不高,因为你需要保持状态某种程度只会使你的代码复杂化。我不会宽恕这种查询,因为它违背了LINQ的哲学并且会自己编写。但既然你问:

var list = Enumerable.Range(0, 10);
Func<int, bool> condition = i => i != 5;
int needed = 1;
var query = list.Where(item => condition(item)
                                   ? needed > 0
                                   : needed-- > 0)
                .ToList(); // this might cause problems
if (needed != 0)
    throw new InvalidOperationException("Sequence is not properly terminated");

然而,这有其自身的问题,无法很好地解决。处理这个的正确方法是手动编写这个(没有LINQ)。这将给你完全相同的行为。

public static IEnumerable<TSource> TakeWhileSingleTerminated<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate)
{
    var hasTerminator = false;
    var terminator = default(TSource);
    foreach (var item in source)
    {
        if (!hasFailed)
        {
            if (predicate(item))
                yield return item;
            else
            {
                hasTerminator = true;
                terminator = item;
            }
        }
        else if (!predicate(item))
            throw new InvalidOperationException("Sequence contains more than one terminator");
    }
    if (!hasTerminator)
        throw new InvalidOperationException("Sequence is not terminated");
    yield return terminator;
}

经过深思熟虑之后,我会说很难获得最有效的原始查询实现,因为它有相互冲突的要求。您将TakeWhile()Single()提前终止,而{{1}}则不能。有可能复制最终结果(正如我们在这里所尝试的那样),但行为不能在不对代码进行重大更改的情况下进行。如果目标只是采取第一个失败的项目,那么这将是完全可能和可复制的,但是因为它不是,所以你只需要处理这个查询所遇到的问题。

p.s。,我认为很明显,仅通过我对此答案进行了多少次编辑就可以做到这一点非常重要。希望这是我的最后一次编辑。

答案 2 :(得分:0)

int? j = null;
var result = list.TakeWhile((o, i) =>
                        {
                          if (j == null && cond(o)) { j = i + 1; }
                          return (j ?? -1) != i;
                        });
if (j == null) { throw new InvalidOperationException(); }

我会选择运营商,但是如何才能确定真的没有内置方式? ; - )

UPDATE1:好的,我的代码没用。我敢打赌它会抛出异常,如果在检查异常之前没有发生Enumerable的执行......