Как заменить циклы for функциональным оператором в C #? - PullRequest
31 голосов
/ 15 апреля 2010

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

Когда его спросили, как избежать циклов for, он ответил, что использует функциональный язык. Однако, если вы застряли на нефункциональном языке, скажем C #, какие существуют методы, чтобы избежать циклов for или избавиться от них путем рефакторинга? С лямбда-выражениями и LINQ возможно? Если да, то как?

Вопросы

Итак, вопрос сводится к:

  1. Почему плохие циклы for? Или в каком контексте избегать циклов for и почему?
  2. Можете ли вы привести примеры кода на C #, как он выглядит раньше, то есть с циклом, а затем без цикла?

Ответы [ 18 ]

1 голос
/ 16 апреля 2010

Ваш коллега может предложить при определенных обстоятельствах, когда задействованы данные базы данных, что лучше использовать агрегированную функцию SQL, такую ​​как Average () или Sum () во время запроса, а не обрабатывать данные на стороне C # внутри Приложение ADO .NET.

В противном случае циклы очень эффективны при правильном использовании, но имейте в виду, что если вы обнаружите, что вкладываете их в три или более порядка, вам может потребоваться более эффективный алгоритм, такой как алгоритм с рекурсией, подпрограммами или обоими. Например, пузырьковая сортировка имеет время выполнения O (n ^ 2) в сценарии наихудшего (обратного порядка), но алгоритм рекурсивной сортировки - только O (n log n), что намного лучше.

Надеюсь, это поможет.

  • Jim
1 голос
/ 15 апреля 2010

Цикл for всегда можно заменить рекурсивной функцией, которая не предполагает использование цикла. Рекурсивная функция - более функциональный стиль программирования.

Но если вы слепо замените петли рекурсивными функциями, то и котята, и щенки умрут миллионами, и вас заменит врач.

ОК, вот пример. Но имейте в виду, что я не сторонник внесения этих изменений!

Цикл для

for (int index = 0; index < args.Length; ++index)
    Console.WriteLine(args[index]);

Может быть изменено на этот рекурсивный вызов функции

WriteValuesToTheConsole(args, 0);


static void WriteValuesToTheConsole<T>(T[] values, int startingIndex)
{
    if (startingIndex < values.Length)
    {
        Console.WriteLine(values[startingIndex]);
        WriteValuesToTheConsole<T>(values, startingIndex + 1);
    }
}

Это должно работать точно так же для большинства значений, но оно гораздо менее понятно, менее эффективно и может исчерпать стек, если массив слишком большой.

1 голос
/ 15 апреля 2010

Иногда цикл for плох, если существует более эффективная альтернатива. Например, поиск, где может быть более эффективно отсортировать список, а затем использовать быструю сортировку или двоичную сортировку. Или когда вы перебираете элементы в базе данных. Как правило, гораздо эффективнее использовать операции с базой данных в базе данных, чем перебирать элементы.

В противном случае, если цикл for, особенно for-each, имеет больше смысла и удобочитаем, я бы предпочел использовать это вместо того, чтобы превратить его в нечто не столь интуитивное. Я лично не верю в это религиозное звучание «всегда делай так, потому что это единственный путь». Скорее, лучше иметь рекомендации и понимать, в каких сценариях целесообразно применять эти рекомендации. Это хорошо, что вы спрашиваете, почему!

1 голос
/ 15 апреля 2010

Цикл for, скажем, «плохой», поскольку подразумевает прогнозирование ветвлений в ЦП и, возможно, снижение производительности при пропадании прогнозирования ветвлений.

Но процессор (с точностью предсказания ветвления 97%) и компилятор с такими методами, как развертывание цикла, делают снижение производительности цикла незначительным.

0 голосов
/ 15 апреля 2010

Вопрос в том, будет ли цикл изменяться или вызывать побочные эффекты. Если это так, используйте цикл foreach. Если нет, рассмотрите возможность использования LINQ или других функциональных конструкций.

См. "foreach" против "ForEach" в блоге Эрика Липперта.

0 голосов
/ 15 апреля 2010

Если вы абстрагируете цикл for напрямую, вы получите:

void For<T>(T initial, Func<T,bool> whilePredicate, Func<T,T> step, Action<T> action)
{
    for (T t = initial; whilePredicate(t); step(t))
    {
        action(t);
    }
}

Проблема, с которой я сталкиваюсь с точки зрения функционального программирования, - это тип возвращаемого значения void. По сути это означает, что циклы for не сочетаются ни с чем. Таким образом, цель состоит не в том, чтобы преобразовать 1-1 из цикла for в какую-то функцию, а в том, чтобы мыслить функционально и избегать действий, которые не сочетаются. Вместо того, чтобы думать о цикле и действии, подумайте о всей проблеме и о том, что вы отображаете, и куда.

0 голосов
/ 15 апреля 2010

Это зависит от того, что находится в цикле, но он / она может ссылаться на рекурсивную функцию

    //this is the recursive function
    public static void getDirsFiles(DirectoryInfo d)
    {
        //create an array of files using FileInfo object
        FileInfo [] files;
        //get all files for the current directory
        files = d.GetFiles("*.*");

        //iterate through the directory and print the files
        foreach (FileInfo file in files)
        {
            //get details of each file using file object
            String fileName = file.FullName;
            String fileSize = file.Length.ToString();
            String fileExtension =file.Extension;
            String fileCreated = file.LastWriteTime.ToString();

            io.WriteLine(fileName + " " + fileSize + 
               " " + fileExtension + " " + fileCreated);
        }

        //get sub-folders for the current directory
        DirectoryInfo [] dirs = d.GetDirectories("*.*");

        //This is the code that calls 
        //the getDirsFiles (calls itself recursively)
        //This is also the stopping point 
        //(End Condition) for this recursion function 
        //as it loops through until 
        //reaches the child folder and then stops.
        foreach (DirectoryInfo dir in dirs)
        {
            io.WriteLine("--------->> {0} ", dir.Name);
            getDirsFiles(dir);
        }

    }
0 голосов
/ 15 апреля 2010

Любая конструкция на любом языке существует по причине. Это инструмент, который будет использоваться для выполнения задачи. Значит до конца. В любом случае, существуют способы, которыми можно использовать его надлежащим образом, то есть ясным и кратким образом и в духе языка, и способы злоупотреблять им. Это относится к сильно смещенному выражению goto, а также к загадке for цикла, а также while, do-while, switch/case, if-then-else и т. Д. ИСПОЛЬЗУЙТЕ ИТ, и ваш коллега должен будет смириться с вашим дизайнерским решением.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...