首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >函数式编程: AggregateUntil函数

函数式编程: AggregateUntil函数
EN

Stack Overflow用户
提问于 2015-08-27 11:17:15
回答 2查看 99关注 0票数 0

所以这里有一个有趣的问题。我正在尝试使用函数式方法来解决一些在命令式方式下非常容易的事情。我们的目标是获取一个序列,并将其折叠/还原为单个值,但是,我希望一旦累加值满足给定条件,就停止并提前退出。你可能会说我想定义IEnumerable<T>.AggregateUntil。下面是我将如何以命令式的方式编写它:

代码语言:javascript
复制
public static TAccumulate AggregateUntil<TSource, TAccumulate>(
    this IEnumerable<TSource> source,
    TAccumulate seed,
    Func<TSource, TAccumulate, TAccumulate> accumulate,
    Func<TAccumulate, bool> until)
{
    var result = seed;

    foreach (var s in source)
    {
        result = accumulate(s, result);

        if (until(result))
        {
            break;
        }
    }

    return result;
}

如果没有foreach循环,你将如何用函数式的方式编写代码呢?我特别想找到一种方法,这种方法不会导致我不得不重新实现整个Aggregate,只有一个小小的行为差异。我也希望在不对集合进行两次迭代的情况下这样做。我仍然在做这件事,如果我弄清楚了,我会更新的,但如果有人想要帮助挑战,那也是受欢迎的。

编辑#1:

以下是如何在没有Until概念的情况下实现它的尝试,只是为了让果汁流动:

代码语言:javascript
复制
private static TAccumulate AggregateUntil<TSource, TAccumulate>(
    IEnumerable<TSource> source,
    TAccumulate seed,
    Func<TSource, TAccumulate, TAccumulate> accumulate)
{
    using (var enumerator = source.GetEnumerator())
    {
        return AggregateUntil(enumerator, seed, accumulate);
    }
}

private static TAccumulate AggregateUntil<TSource, TAccumulate>(
    IEnumerator<TSource> source,
    TAccumulate seed,
    Func<TSource, TAccumulate, TAccumulate> accumulate)
{
    return source.MoveNext()
        ? AggregateUntil(source, accumulate(source.Current, seed), accumulate, until)
        : seed;
}

编辑#2:

好了,就功能而言,我已经实现了我的目标函数,但我还没有想好如何做到这一点,而不是基本上重新实现所有的foldl/reduce/聚合逻辑+ until条件。如果我不知道如何按原样重用Aggregate中的逻辑,我觉得我错过了FP可组合性的一个基本技巧:

代码语言:javascript
复制
private static TAccumulate AggregateUntil<TSource, TAccumulate>(
    IEnumerable<TSource> source,
    TAccumulate seed,
    Func<TSource, TAccumulate, TAccumulate> accumulate,
    Func<TAccumulate, bool> until)
{
    using (var enumerator = source.GetEnumerator())
    {
        return AggregateUntil(enumerator, seed, accumulate, until);
    }
}

private static TAccumulate AggregateUntil<TSource, TAccumulate>(
    IEnumerator<TSource> source,
    TAccumulate seed,
    Func<TSource, TAccumulate, TAccumulate> accumulate,
    Func<TAccumulate, bool> until)
{
    TAccumulate result;

    return source.MoveNext()
        ? until(result = accumulate(source.Current, seed))
            ? result
            : AggregateUntil(source, result, accumulate, until)
        : seed;
}
EN

回答 2

Stack Overflow用户

发布于 2015-10-07 08:36:46

最好使用命令式技术,因为函数式的方法是使用递归,而且您可以在C#中使用递归轻松地炸毁堆栈,而且效率也要低得多-递归调用存在开销,并且您需要多次从枚举数中获取第一项。

然而,如果你想知道如何做到这一点,这就是它(作为IEnumerable的一个扩展方法):

代码语言:javascript
复制
    public static TAccumulate AggregateUntil<TSource, TAccumulate>(
        this IEnumerable<TSource> source,
        TAccumulate seed,
        Func<TSource, bool> predicate,
        Func<TSource, TAccumulate, TAccumulate> accumulate)
    {
        return source.Any()
            ? predicate(source.First())
                ? source.Skip(1).AggregateUntil(accumulate(source.First(), seed), predicate, accumulate)
                : seed
            : seed;
    }

如果您使用像F#这样的函数式第一语言,它具有用于分解列表头部和尾部的模式匹配构造,并且可以进行尾部递归,那么实现就会简单得多:

代码语言:javascript
复制
let rec foldUntil state folder pred = function
    | []      -> state
    | x :: xs -> if pred x
                 then foldUntil (folder state x) folder pred xs
                 else state

如果您要使用我的Language-Ext库,那么您可以使用C#来接近这种风格:

代码语言:javascript
复制
S FoldUntil<S,T>(IEnumerable<T> list, S state, Func<S, T, S> folder, Func<T, bool> pred) =>
    match(list,
        ()      => state,
        (x, xs) => pred(x)
                      ? FoldUntil(xs, folder(state, x), folder, pred)
                      : state);
票数 1
EN

Stack Overflow用户

发布于 2015-08-27 11:38:36

您可以这样做:

代码语言:javascript
复制
public static TAccumulate AggregateUntil<TSource, TAccumulate>(
    this IEnumerable<TSource> source,
    TAccumulate seed,
    Func<TSource, TAccumulate, TAccumulate> accumulate,
    Func<TAccumulate, bool> until)
{
    return source.Select(s => seed = accumulate(s, seed))
                 .SkipWhile(s => !until(s))
                 .First();
}

select不是很好,因为我们要保持状态。但我认为,如果不重写Aggregate以在某个条件下中断或多次聚合列表,就不可能做到这一点。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32240107

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档