среда, 6 октября 2010 г.

Задание на освоение работы с рекурсией

На занятии в будущую субботу, разбирая внутреннюю жизнь стека вызовов, мы затронем тему рекурсии. Я люблю рекурсию - на мой взгляд, она делает программу красивее, а местами - понятнее. Поэтому предлагаю для закрепления материала сделать небольшую программку.

Когда я искал аналогии для объяснения смысла рекурсии, мне в голову пришел пример расшифровки названия PHP (кто не знает - погуглите). Соответственно, напишите мне консольную программку на C#, которая печатает следующий текст:


10 раз. Сделайте это не циклом, а рекурсивным вызовом функции (а отдельные красавцы и красавицы могут сделать и циклом, и рекурсией). Листинги публикуйте тут в камментах или приносите распечатанными на следующее занятие, обсудим.

29 комментариев:

  1. Я бы предложил ещё красавцам и отдельно предложил бы красавицам присылать свои проекты на progelectivecource@gmail.com! Так быстрее проверим и дадим ответ с описанием проблем (если они там будут) :)

    ОтветитьУдалить
  2. Мне кажется, что классический пример факториала - это одно из самых наглядных объяснений рекурсии. И правда, 3! = 3 * 2! = 3 * 2 * 1! = 3 * 2 * 1 * 0!, а 0! - это базовый случай. Таким образом, эта функция может быть определена через саму себя естественным образом. Вообще, здесь интересно поговорить и о таких фундаментальных вещах, как ряды и производяющие функции... или перебор? но ведь жутко интересно :)

    ОтветитьУдалить
  3. Миша, мы это оставим до твоего курса по прикладным алгоритмам. Я согласен, что рекурсивные алгоритмы и все, что вокруг них - очень сладкая тема. Но пока я хотел бы что-то не слишком математическое, чтобы пощупать было проще. Через полгодика раскопаем эту темку поглубже с теми, кто выживет :)

    ОтветитьУдалить
  4. #include
    void answer(int i);
    void question(int i){
    if(i==0)
    std::cout << "Yes, but...\n";
    else
    {
    std::cout << "What' is PHP? \n";
    answer(--i);
    }
    }

    void answer(int i){
    if(i==0)
    std::cout << "Ok, ok! Hipertext Preprocessor!!! \n";
    else
    {
    std::cout << "Parents Help Parents! \n";
    question(i);
    }
    }

    void main()
    {
    question(10);
    }

    ОтветитьУдалить
  5. #include "iostream"

    void main();
    void _recursive(int i);

    void _recursive(int i)
    {
    if (i >= 1)
    {
    std:: cout << "\n" << i << " What is it GNU?";
    std:: cout << "\n GNU's Not Unix";
    i --;
    _recursive(i);
    }
    else
    {
    std:: cout << "\n Recursive is complete";
    }
    }

    void _cycle(int i)
    {
    for (int k = 1; k <= i; k++)
    {
    std:: cout << "\n" << k << " What is it GNU?";
    std:: cout << "\n GNU's Not Unix";
    }
    }

    void main()
    {
    int i, j;
    std:: cout << "Choose the type of recursion: ";
    std:: cin >> j;
    std:: cout << "Enter the depth of recursion: ";
    std:: cin >> i;
    switch (j)
    {
    case 1:
    _recursive(i);
    break;
    case 2:
    _cycle(i);
    }
    }

    ОтветитьУдалить
  6. #include "iostream"
    void talk(int n){
    std::cout << "What Abbreviation PHP mean?\n";
    if(--n == 0)
    std::cout << "Ok, It's means Hypertext Preprocessor\n";
    else
    {
    std::cout << "Parents Help Parents\n";
    talk(n);
    }
    }

    void main(){
    talk(10);
    }

    ОтветитьУдалить
  7. Wow! Все сделали и даже правильно. Коля отличился и сделал сразу 2 варианта! Жаль что остальные не отреагировали, или отреагировали? :)

    ОтветитьУдалить
  8. Мне понравились реализации Нико и второй вариант от HedG (первый вариант был очень своебразным, и никак не делал программу читаемой). Только вы себе несколько упростили задачу - если вы посмотрите, в моем примере еще идет игра с формулировками в тексте: они разные на первом, последнем и остальных шагах. Сможете это реализовать?

    ОтветитьУдалить
  9. Второй вариант от HedG нужно, конечно, исправлять. Во-первых, он зацикливается при параметре n <= 0. Во-вторых, уменьшение параметра в условии проверки - это плохой стиль, мягко говоря. Намного логичнее уменьшать его при рекурсивном вызове - например n--; talk(n) или, если уж сильно хочется, talk(--n)

    ОтветитьУдалить
  10. // Ребят, Вы меня конечно ж, простите за мои глупые выдумки..
    #include

    int rec(int m)
    {
    int d;
    if (m >= 0)
    {
    printf("%s\n", "My rec function...");
    d = rec(--m);
    }
    return d;
    }

    int main()
    {
    int s;
    s = rec(10);
    scanf("%d", s);
    }

    ОтветитьУдалить
  11. //Извиняюсь, сначала не то скопировал )
    #include

    int a;

    int rec(int m)
    {
    int d;
    if (m >= 0)
    {
    if (a) {
    printf("%s\n", "What is it??");
    }
    else { printf("%s\n", "It is HARD PROGRAMMING!!!"); }
    if (a) { a = 0; } else { a = 1; }
    d = rec(--m);
    }
    return d;
    }


    int main()
    {
    a = 1;
    int s;
    s = rec(10);
    scanf("%d", s);
    }

    ОтветитьУдалить
  12. Согласен, код ужасен -- появится время, попробую подправить.. я хотел лишь добиться возможности возвращать функцией 0 при успешном прохождении всех стадий рекурсии, и другого значения при противоположном варианте. Это очень удобно, особенно когда нужно знать результат выполнения, не используя глобальных переменных и вывода на экран. Что бы вызвать функцию, и лишь проверить возвращенное значение... Тем не менее -- до конца пока не довел.. как только будет время -- доделаю ;)

    ОтветитьУдалить
  13. Дорогой Blook, я бы не совмещал рекурсию с использованием глобальных переменных. Вернее, даже не так: я бы не передавал данные из одного вызова функции в другую через глобальную переменную. Это т.н. "плохой стиль", т.к. чревато неожиданными побочными эффектами - в любом месте программы есть доступ к этой переменной на изменение, и если кто-то случайно ее тронет, то код "посыпется". Особенно это важно в больших программах. Поэтому лучше не привыкать так делать. Передавайте параметры как аргументы функции, задумываясь при этом о наболее обоснованном в данном случае способе передачи (по ссылке или по значению), учитывая как соображения производительности, так и защиты переменных от случайной правки.

    ОтветитьУдалить
  14. Денис, именно этого я хотел первоначально избежать (глобальных переменных). Т.е. что бы результат всех стадий рекурсии возвращался после вызова рекурсивной функции.. Это черновой вариант.. Придумаю как только найду время...

    очень удобно было бы:
    if (!(rec(x))) //вызов рекурсивной функции
    {
    printf("%s\n", "YES! rec is complete")
    }
    else { ... }

    ОтветитьУдалить
  15. Уважаемый Blook. Какова цель в том, чтобы возвращать в качестве результата работы рекурсивной функции факт ее завершения? Сегодня, для того, чтобы указать программе, что успешно вызов не завершился - возбуждается исключение. А, скажем, если нужно иметь функцию суммирования всех чисел до n (например, при n = 5 вернется 1+2+3+4+5=15), то ее естественно написать в виде рекурсивной функции с сигнатурой int sum(int n), где возвращаемое значение int - есть искомая сумма. Если сильно нужно в вашем примере - передавайте а в качестве второго параметра.

    ОтветитьУдалить
  16. Михаил, дело привычки (не спорю, возможно вредной) -- в большинстве программ на Си, что я встречал успех выполнения какой-либо задачи проверялся именно таким образом... например в библиотеке WinPcap:

    if(pcap_setfilter(adhandle, &fcode)<0){
    fprintf(stderr,"\nError setting the filter.\n");

    Как Вы считайте, считается ли такой стиль "правильным"?
    (Он очень часто встречается)

    ОтветитьУдалить
  17. Blook, это не то, чтобы стиль, - в случае Си - это один из немногих способов передачи информации об ошибке, т.к. в Си нет стандартного механизма исключений (хотя можно найти реализации). Важно, что функция должна сообщать об ошибке, когда это спроектировано, но далеко не в каждом случае. Если есть возможность использовать исключения, то стиль возврата кодов ошибок неправильный и может быть оправдан в случае крайних требований к производительности, когда на счету каждый такт, либо в каких-то случаях, связанных с совместимостью. В данном примере я не вижу никакой необходимости сообщать об ошибке, тем более, что это нигде не обрабатывается. И, конечно, все современные программы используют исключения и важно уметь с ними работать.
    Плюс, это не имеет отношения к рекурсии.

    ОтветитьУдалить
  18. // мой вариант

    #include

    void recursion(int count){

    if(count >= 10){
    std::cout << "What does PHP mean?\n";
    }
    else
    if(count <= 0){
    std::cout << "Ok, but...\n";
    return;
    }
    else
    {
    std::cout << "Ok, but what does PHP mean?\n";
    }
    std::cout << "PHP: Hypertext Preprocessor\n";
    recursion(--count);
    }

    void main(){
    recursion(10);
    return;
    }

    ОтветитьУдалить
  19. #include

    void Rec(int num){
    std::cout << "What is PHP? \n";
    std::cout << "PHP: is PHP! \n";
    if (num>=1)
    Rec(--num);
    }

    void Cycle(int num){
    for (int i = num; i>=1; i--){
    std::cout << "What is PHP? \n";
    std::cout << "PHP: is PHP! \n";
    }
    }

    void main(){
    Rec(10);
    std::cout << "\n\t End Rec \n\n";
    Cycle(10);
    std::cout << "\n\t End Cycle \n\n";
    }

    ОтветитьУдалить
  20. \\Мой вариант

    #include

    void rec(int recur){
    if (recur<10){
    std::cout << "What is PHP?\n";
    std::cout << "PHP: is Hypertext preprocessor!\n";
    }
    if (recur=10){
    std:: cout << "Yes, but \n";
    }
    recur++;
    return;
    }

    ОтветитьУдалить
  21. Я тоже решил написать свой вариант, если не подзно.

    #include "stdafx.h"
    #include "iostream"

    void whatdoesphpmean(int n)
    {
    if (n > 0) {
    std::cout << "What does PHP mean?\nPHP: Hypertext preprocessor.\n";
    whatdoesphpmean(--n);
    } else {
    std::cout << "Ok, but...";
    }
    }

    int main()
    {
    whatdoesphpmean(10);
    return 0;
    }

    ОтветитьУдалить
  22. А, и кстати вот такая реализация с разными первыми и последними фразами.
    Хотелось бы узнать насколько она правильная.

    #include "stdafx.h"
    #include "iostream"

    void whatdoesphpmean(int n)
    {
    std::cout << "PHP is Hypertext preprocessor.\n";
    if (n > 1) {
    std::cout << "Yes, but what does PHP mean?\n";
    whatdoesphpmean(--n);
    } else {
    std::cout << "Yes, but...";
    }
    }

    int main()
    {
    std::cout << "What does PHP mean?\n";
    whatdoesphpmean(10);
    return 0;
    }

    ОтветитьУдалить
  23. Если она работает - значит, она правильная :) Я могу свою показать, правда, она на C# - другого под рукой не было.


    static string CreateText(string FirstLetter, int Count)
    {
    if (Count > 10)
    return "...";
    else
    return FirstLetter + "то такое PHP?\nPHP: Hypertext Preprocessor\nДа, но " + CreateText("ч", ++Count);
    }

    static void Main(string[] args)
    {
    string Text = CreateText("Ч", 1);
    Console.WriteLine(Text);
    Console.ReadKey();
    }

    ОтветитьУдалить
  24. Этот комментарий был удален автором.

    ОтветитьУдалить
  25. Ну вот как-то так (и рекурсия, и цикл):

    #include "iostream"
    void recurs (int cnt)
    {
    if (cnt<=10)
    {
    std::cout << "What is PHP?\n";
    std::cout << "PHP: Hypertext Preprocessor\n";
    cnt++;
    recurs(cnt);
    }
    else
    {
    std::cout << "Ok! Hypertext Preprocessor!";
    }
    }

    void cikl (int cnt)
    {
    for (int k=10; k>=cnt; k--)
    {
    std::cout << "What is PHP?\n";
    std::cout << "PHP: Hypertext Preprocessor\n";
    }
    std::cout << "Ok! Hypertext Preprocessor!";

    }

    void main()
    {
    int key;
    std::cout << "Viberite tip povtoreniya: [1] - Recursiya, [2]- Cikl: ";
    std::cin >> key;
    switch (key){
    case 1: recurs(1);
    break;
    case 2: cikl(1);
    break;
    default: std::cout << "ERROR!" ;
    break;
    }

    }

    ОтветитьУдалить
  26. Кстати у Егора есть такая вот загвоздка, там она как мне кажется вообще не должна была откомпилироваться. Там main - void а он возвращает int - должо было ругнутся. Егор, ты проверял?

    ОтветитьУдалить
  27. У меня там int main. Вроде всё ок :) По-крайней мере, всё отлично работет.

    ОтветитьУдалить
  28. Стандарт вовсе запрещает использование конструкции void main (насколько я знаю) - тем не менее, VS без проблем компилирует код на С++, даже встречая ее. Компилятор же gcc всегда ругается и отказывается компилировать такой код.

    ОтветитьУдалить
  29. Ок, ещё раз пробежался взглядом - да всё ok. Стандарт не запрещает - да, это я знаю, однако мне на глаза вроде бы как попался return в void ф-ции тока вот перечитав я понял что надо проспаться :)

    ОтветитьУдалить