Рекурсивное выполнение функций в списке

#c# #linq #recursion #functional-programming

#c# #linq #рекурсия #функциональное программирование

Вопрос:

Учитывая список Func<string, string> , возможно ли написать инструкцию, которая выполняет итерацию по списку и возвращает результат следующим образом:

 string result = f1(f2(f..(input));
  

У меня есть следующий код (который работает), но меня не устраивает временная переменная.

 public static string WrapEachElementWith<T>
    (   this IEnumerable<T> target, 
        params Func<string, string>[] func )
{
    string result = string.Empty;
    target.Each(s =>
                    {
                        var tmp = s.ToString();
                        func.Reverse().Each(x => tmp = x(tmp));
                        result  = tmp;
                    });
    return resu<
}
  

Как упростить / реорганизовать?

ОБНОВЛЕНИЕ: я должен был предоставить больше справочной информации. Я поигрался с функциональным программированием на c # после просмотра сеанса JavaScript более высокого порядка и оскорбительного сеанса c # Джона в Oredev.

Цель состоит в том, чтобы сгенерировать html.

 var TABLE = WrapWith("TABLE");
var TR = WrapWith("TR");
var TD = WrapWith("TD");
const string expected = "<TABLE><TR><TD>1</TD></TR><TR><TD>2</TD></TR></TABLE>";

var result = TABLE(stringArray.WrapEachWith(TR, TD));
result.ShouldEqual(expected);

static Func<String, String> WrapWith(string element)
{
    var startTag = '<'   element   '>';
    var endTag = "</"   element   '>';
    return s => startTag   s   endTag;
}
  

Комментарии:

1. Рекурсия? Что-то вроде applyMany s [] = s ; applyMany s (f:fs) = applyMany (f s) fs в C #?

2. Просто из любопытства, каковы ваши доводы в пользу использования рекурсии для этого?

3. смотрите обновление в моем вопросе, я пытаюсь обернуть элементы в HTML, используя список элементов, с помощью которых я хочу его обернуть

4. Я допустил ошибку в обновлении вопроса, теперь у него есть допустимый код.

5. Вы не генерируете HTML таким образом. Точно так же, как вы не разбираете XML вручную, не говоря уже о регулярных выражениях. Проблема слишком сложна и уже решена, чтобы вы могли возиться с ней вручную.

Ответ №1:

Мне кажется, что вы делаете четыре вещи:

  • Преобразование каждого элемента в строку
  • Применение функций по очереди
  • Применение этой составной функции к каждой строке в последовательности
  • Объединение результатов вместе (неэффективно)

Я бы выделил эти четыре аспекта — в частности, string.Join работает достаточно хорошо для четвертой части и Enumerable.Select выполняет третью.

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

Итак, я бы переписал этот метод, чтобы возвращать a Func<string, string> , который затем можно было бы использовать с Select и Join . Например:

 public static Func<string, string> Compose(params Func<string, string> funcs)
{
    return input => {
        string current = input;
        foreach (var func in funcs)
        {
            current = func(current);
        }
        return current;
    };
}
  

Вы могли бы, конечно, сделать это само по себе универсальным, с подписью:

 public static Func<T, T> Compose(params Func<T, T> funcs)
  

Затем вы могли бы вызвать его с помощью чего-то вроде:

 var composite = Compose<string>(FirstFunction, SecondFunction, ThirdFunction);

var query = string.Join("", items.Select(x => x.ToString())
                                 .Select(composite));
  

Ответ №2:

 public static string WrapEachElementWith
    (   string input, 
        params Func<string, string>[] func )
{
    foreach (var f in func.Reverse())
        input = f(input);
    return input;
}
  

Не уверен, зачем вам нужен параметр шаблона, все функции отображают строку в строку, верно?

Обратите внимание, что Each расширения IEnumerable нет, поэтому вам придется прибегнуть к foreach или написать свое собственное Each .

Редактировать:
ваш код фактически применяет эту функцию ко всем значениям из списка, поэтому фактический код будет выглядеть примерно так:

 public static string F<T>
    (   this IEnumerable<T> target, 
        params Func<string, string>[] func )
{
    target.Select(item => WrapEachElementWith(item.ToString(), func))
          .Aggregate((sum, cur) => sum   cur);
}
  

Как уже упоминал @Jon, суммирование таким образом довольно неэффективно, поэтому вы, возможно, хотели бы сформулировать это так:

 string.Join("", target.Select(
                  item => WrapEachElementWith(item.ToString(), func)));
  

Комментарии:

1. Каждый входной сигнал равен T, и toString вызывается один раз для начала.

2. @Jon: Я предположил, что это вызывается по одному для каждого target элемента.

Ответ №3:

Этот парень написал целый трассировщик лучей, используя LINQ. Я не очень внимательно изучал его код, но он описывает использование техники, называемой «Y-combinator», для создания рекурсии в операторе LINQ. Он ссылается на это сообщение в блоге, в котором дается подробное описание этих рекурсивных лямбда-выражений.

Я не знаю, действительно ли это то, что вы ищете, но это может помочь вам встать на правильную почву.