2016-04-24 2 views
3

Есть ли LINQ эквивалент следующего?Перерыв в функциональном составе при использовании LINQ

static T Compose<T>(T source, 
    IEnumerable<Func<T, T>> funcs, 
    Func<T, bool> cond) 
{ 
    foreach (var func in funcs) 
    { 
     source = func(source); 

     if (cond(source)) break; 
    } 

    return source; 
} 

// This ends the composition as soon as a null is returned 
var result = Compose(x, funcs, x => x == null); 

По сути, я хотел бы сочинить функции друг на друга, и сломаться, если текущее значение соответствует определенному условию.

result = f(g(h(x))) // if condition is met somewhere within this chain, 
        // it will immediately break and return 

EDIT: Я думал о чем-то вдоль линий

funcs.Aggregate(seed, (x, f) => cond(x) ? x : f(x)); 

но выглядит запутанным, а также заканчивает перечисление по всей коллекции, а не нарушение, когда условие встретил.

+0

Чтобы остановить вычисления после условия не будут выполнены вы можете агрегировать сами функции, а не результат их выполнения: 'funcs.Aggregate ((cur, next) => (i => {var val = cur (i); return cond (val)? next (val): val; })) ' – reptile

ответ

0

Один из способов сделать это является Aggregate, пока условие не будет выполнено. Итак, я определяю метод расширения:

public static TAccumulate AggregateUntil<TSource, TAccumulate>(
    this IEnumerable<TSource> source, 
    TAccumulate seed, 
    Func<TAccumulate, TSource, TAccumulate> func, 
    Func<TAccumulate, bool> predicate) 
{ 
    if (source == null) 
     throw new ArgumentNullException(nameof(source)); 

    if (func == null) 
     throw new ArgumentNullException(nameof(func)); 

    if (predicate == null) 
     throw new ArgumentNullException(nameof(func)); 

    var accumulate = seed; 
    foreach (var item in source) 
    { 
     accumulate = func(accumulate, item); 
     if (predicate(accumulate)) break; 
    } 
    return accumulate; 
} 

Теперь мы можем написать ComposeUntil как:

public static T ComposeUntil<T>(
    this IEnumerable<Func<T, T>> source, 
    T seed, 
    Func<T, bool> predicate) 
{ 
    return source.AggregateUntil(seed, (x, f) => f(x), predicate); 
} 

и использовать его следующим образом:

var funcs = new List<Func<int, int>> 
{ 
    x => x + 1, 
    x => x + 2, 
    x => x + 3, 
    x => x + 4 
}; 

var result = funcs.ComposeUntil(0, x => x >= 6); 
// result == 6 
-1
var funcs = new Func<int, int>[] {i => i + 1, i => i * 2, i => i - 3}; 
Func<int, bool> cond = i => i < 20; 
var combined = 
    funcs.Aggregate(
     (cur, next) => 
      i => 
      { 
       var val = cur(i); 
       return cond(val) ? next(val) : val; 
      }); 
Console.WriteLine(combined(10)); // 22 

Примечание, что петля заменена цепью, вызов, так что длина funcs ограниченные по размеру стека.

+0

Использование условия wro нг. Если вы запустите это через код OP, вы получите '11' в качестве ответа, а не' 22'. – Enigmativity

-3

Да, используйте .Снять (1)
Например:

var strList = new List<string> {"a","b","c","d"}; 
var str = strList.Where(x => x.Equals("c")).Take(1); 

[править]
Опять же, ключ к вашей проблеме "Break от функции состава при условии, с помощью LINQ" использует .FirstOrDefault или. Взять (1).

Вот ваш код для каждого запроса/ответа на @Enigmativity. Тем не менее, обратите внимание мой код не требует каких-либо пакетов 3rd партии, как @Enigmativity делает:

static T Compose<T>(T source, 
     IEnumerable<Func<T, T>> funcs, 
     Func<T, bool> cond) 
    { 
     var func = funcs.Select(x => 
     { 
      var ret = x; 
      source = x(source); 
      return ret; 
     }) 
     .FirstOrDefault(y => cond(source)); 

     Console.WriteLine("Here is the Func that the condition succeeded on(null if none):"); 
     Console.WriteLine(func); 

     return source; 
    } 

void Main() 
{ 
    var funcs = new Func<int, int>[] 
    { 
     a => a + 1, 
     a => a + 2, 
     a => a + 3, 
    }; 

    // Set this "cond = b => b == ?" value to want your 
    // func will succeed on. Since we are continuing to add 
    // to the source, the three Func's above will succeed on 
    // any of the following values: 1, 3, 6 and immediately exit 
    // ending any further enumeration via the ".FirstOrDefault" 

    Func<int, bool> cond = b => b == 3; 

    Console.WriteLine("Here are the list of Funcs:"); 
    Console.WriteLine(funcs); 

    var result = Compose(0, funcs, cond); 

    Console.WriteLine(); 
    Console.WriteLine("Below is the actual result of the source after being "); 
    Console.WriteLine("acted on by the Funcs. If no Func matched the condition,"); 
    Console.WriteLine("all Funcs acted on the source so the source will return 6."); 
    Console.WriteLine("That means;"); 
    Console.WriteLine("If the first Func matched (aka cond = b => b == 1), the source will return 1."); 
    Console.WriteLine("If the second Func matched (aka cond = b => b == 3), the source will return 3."); 
    Console.WriteLine("If the third Func matched (aka cond = b => b == 6), the source will return 6. Same "); 
    Console.WriteLine("as matching no Func because all Funcs were applied. However, see above that 'null'"); 
    Console.WriteLine("was displayed for the 'Here is the Func that the condition succeeded on' when "); 
    Console.WriteLine("you set the condition value to an int other than 1, 3, or 6."); 
    Console.WriteLine(); 
    Console.WriteLine("Actual result of the source after being acted on by the Funcs retuned:"); 
    Console.WriteLine(result); 
} 
+0

Почему голос? В этом примере изучается то, как вы выходите из цикла foreach, используя linq, если текущее значение соответствует определенному условию без перечисления по всей коллекции. Это по существу его вопрос. –

+0

'var str = strList.FirstOrDefault (x => x.Equals (" c "));' Также срабатывает, когда текущее значение соответствует определенному условию без перечисления по всей коллекции. см. [Https://msdn.microsoft.com/en-us/library/bb503062(v=vs.110).aspx](https://msdn.microsoft.com/en-us/library/bb503062(v = vs.110) .aspx) и [https://msdn.microsoft.com/en-us/library/bb340482(v=vs.110).aspx](https://msdn.microsoft.com/ru -us/library/bb340482 (v = vs.110) .aspx) –

+1

Можете ли вы показать, как ваш код может заменить тело метода 'Compose' OP? – Enigmativity

1

Это прекрасно работает:

var funcs = new Func<string, string>[] 
{ 
    t => t + "1", 
    t => t + "2", 
    t => t + "3", 
}; 

Func<string, bool> cond = t => t.Length == 3; 

Func<string, string> func = 
    t => funcs.Aggregate(t, (a, f) => !cond(a) ? f(a) : a); 

Console.WriteLine(func("A")); 

"A12" Он производит строку длины 3.

Чтобы избежать повторения коллекции, вам необходимо ввести новый оператор, .Scan(...), который я получаю от интерактивных расширений команды Reactive Extensions (NuGet «Ix-Main»).

.Scan(...) работает как .Aggregate(...), но он производит значение на каждом шаге.

Func<string, string> func = 
    t => funcs 
     .Scan(t, (a, f) => f(a)) 
     .SkipWhile(x => !cond(x)) 
     .FirstOrDefault(); 

Это производит тот же результат, что и выше, но не выполняет итерацию всей коллекции.

Здесь оба варианта вставляется в Compose метод:

(1)

static T Compose<T>(T source, 
    IEnumerable<Func<T, T>> funcs, 
    Func<T, bool> cond) 
{ 
    return funcs.Aggregate(source, (a, f) => !cond(a) ? f(a) : a); 
} 

(2)

static T Compose<T>(T source, 
    IEnumerable<Func<T, T>> funcs, 
    Func<T, bool> cond) 
{ 
    return 
     funcs 
      .Scan(source, (a, f) => f(a)) 
      .SkipWhile(t => !cond(t)) 
      .FirstOrDefault(); 
} 
+0

Он по-прежнему будет перечислять всю коллекцию 'funcs', даже после выполнения условия. – MarcinJuraszek

+0

@MarcinJuraszek - работает над этим прямо сейчас. – Enigmativity

+0

@MarcinJuraszek - сделано. – Enigmativity

-1

Если вы действительно хотите такой вспомогательной функции часто, то я предложит вам посмотреть this question, чтобы создать его как метод расширения и использовать его везде, где вы хотите (похоже на намек, который вы предложили, но с ограниченным агрегат.)

Однако, если вам это нужно только редко раз, то я бы с чем-то вроде:

string value = "Tes"; 

var funcs = new Func<string, string>[] 
{ 
    x => x + "t", 
    x => x + "s", 
    x => x + "s" 
}; 

funcs.TakeWhile(f => 
{ 
    if (value == "Tests") // your condition 
    { 
     return false; 
    } 

    value = f(value); // reassignment of value 
    return true; 
}).ToList(); 

Console.WriteLine(value); 

Edit: для более краткой версии:

string value = "Tes"; 

var funcs = new Func<string, string>[] 
{ 
    x => x + "t", 
    x => x + "s", 
    x => x + "s" 
}; 

funcs.TakeWhile(f => value != "Tests" && (value = f(value)) is string).ToList(); 

Console.WriteLine(value); 
+0

Почему это проголосовало? Вы хотите объяснить? –

0

@Mateen Ulhaq - I Я не уверен, что вы можете увидеть мой обновленный ответ (и комментарии намного ниже), так как они стали серыми, поэтому я переписываю свой ответ. Чтобы увидеть этот запуск кода и посмотреть, что он делает при изменении значения int переменной cond, просто:
1) Открыть LinqPad4.
2) Установите язык в «C# program».
3) Удалите весь код, который вы видите там void main().. и т. Д.
4) Скопируйте мой код ниже и вставьте его в LinqPad.
5) Нажмите F5.

static T Compose<T>(T source, 
     IEnumerable<Func<T, T>> funcs, 
     Func<T, bool> cond) 
    { 
     var func = funcs.Select(x => 
     { 
      var ret = x; 
      source = x(source); 
      return ret; 
     }) 
     .FirstOrDefault(y => cond(source)); 

     Console.WriteLine("Here is the Func that the condition succeeded on(null if none):"); 
     Console.WriteLine(func); 

     return source; 
    } 

void Main() 
{ 
    var funcs = new Func<int, int>[] 
    { 
     a => a + 1, 
     a => a + 2, 
     a => a + 3, 
    }; 

    // Set this "cond = b => b == ?" value to want your 
    // func will succeed on. Since we are continuing to add 
    // to the source, the three Func's above will succeed on 
    // any of the following values: 1, 3, 6 and immediately exit 
    // ending any further enumeration via the ".FirstOrDefault" 

    Func<int, bool> cond = b => b == 3; 

    Console.WriteLine("Here are the list of Funcs:"); 
    Console.WriteLine(funcs); 

    var result = Compose(0, funcs, cond); 

    Console.WriteLine(); 
    Console.WriteLine("Below is the actual result of the source after being "); 
    Console.WriteLine("acted on by the Funcs. If no Func matched the condition,"); 
    Console.WriteLine("all Funcs acted on the source so the source will return 6."); 
    Console.WriteLine("That means;"); 
    Console.WriteLine("If the first Func matched (aka cond = b => b == 1), the source will return 1."); 
    Console.WriteLine("If the second Func matched (aka cond = b => b == 3), the source will return 3."); 
    Console.WriteLine("If the third Func matched (aka cond = b => b == 6), the source will return 6. Same "); 
    Console.WriteLine("as matching no Func because all Funcs were applied. However, see above that 'null'"); 
    Console.WriteLine("was displayed for the 'Here is the Func that the condition succeeded on' when "); 
    Console.WriteLine("you set the condition value to an int other than 1, 3, or 6."); 
    Console.WriteLine(); 
    Console.WriteLine("Actual result of the source after being acted on by the Funcs retuned:"); 
    Console.WriteLine(result); 
} 
+0

Спасибо за ваше время. К сожалению, я хотел составлять функции друг над другом. Для 'funcs', которые вы дали:' Compose (4, funcs, x => false) 'должен производить' (((4 + 1) +2) +3) = 10'. –

+0

Hi @Mateen Ulhaq. Да. В приведенном выше коде, если я изменю «var result = Compose (0, funcs, cond)», «быть» var result = Compose (4, funcs, cond); »и запустить этот код в LinqPad, он вернет 10. Если Я ничего не понимаю, пожалуйста, выберите это как правильный ответ. –

+0

@Mateen Ulhaq. Мне нравится синтаксис 'FirstOrDefault (', который я использовал здесь лучше, чем '.Take (1)', поскольку он чище. Однако моя оригинальная мысль '.Take (1)' тоже работает. Чтобы увидеть '.Take (1) 'work, замените' .FirstOrDefault (y => cond (source)), 'code with' .Where (y => cond (source)). Take (1); 'Это был мой первый ответ на SO, и один из них был опущен 3 раза, хотя он содержит те же методы ответа. Я только что опубликовал его, потому что я не знал, можно ли его увидеть, так как он стал серым, поэтому, пожалуйста, выберите оба моих ответа как ответы принимаются, поэтому моя репутация будет исправлена. –

Смежные вопросы