2015-08-27 4 views
0

так вот интересная проблема. я пытаюсь использовать функциональный подход для решения чего-то, что было бы очень просто в императивном порядке. цель состоит в том, чтобы взять последовательность и свернуть/свести ее к одному значению, однако я хочу остановиться и выйти раньше, как только накопленное значение удовлетворяет заданному условию. вы можете сказать, что я хочу определить IEnumerable<T>.AggregateUntil. вот как я бы написать это в императивной моде:Функциональное программирование: функция AggregateUntil

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 в целом, с этой маленькой разницей в поведении. Я также хотел бы сделать это, не повторяя сбор дважды. Я все еще работаю над этим и опубликую обновление, если я это выясню, но если кто-то там захочет помочь с вызовом, который тоже приветствуется.

EDIT # 1:

вот удар, как реализовать это без Until концепции, просто для того чтобы получить соков:

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

EDIT # 2:

OK, I «Я реализовал свою целевую функцию по функциональности, но я еще не понял, как это сделать, не переосмысливая в основном всю логику foldl/reduce/aggregate + до состояния. Я чувствую, что не хватает основной трюк FP компонуемости, если я не могу понять, как повторно использовать логику в Aggregate как есть:

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; 
} 
+0

Ничего особенного, но общего совета I, который я получил при попытке выйти из складки/накопления на основе некоторого состояния, заключается в использовании recucrsion. – smk

ответ

0

Вы можете сделать это:

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

Выбор не слишком приятный, поскольку мы сохраняем состояние. Но я не думаю, что можно обойтись без перезаписи Aggregate, чтобы разбить условие или агрегировать список несколько раз.

+1

Если это не так, если 'until' возвращает' false' для всех ''? – soon

+0

@soon Да, вы правы - хорошо поймать. Будет обновляться, когда я получаю шанс – Rob

0

Это действительно лучше использовать императивные методы, потому что функциональный способ сделать это - использовать рекурсию, и вы можете легко взорвать стек, используя рекурсию на C#, это также намного менее эффективно - есть накладные расходы на рекурсивную звонки, и вам нужно получить первый элемент из перечисляемого несколько раз.

Однако, если вы хотите знать, как это его (как метод расширения для IEnumerable):

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 #, который сопрягая шаблон конструкции для разлагая голова и хвост списка, и может сделать хвостовую рекурсию, то реализация намного проще:

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#:

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); 
Смежные вопросы