вернуть не выход рекурсивный метод, как ожидалось - C # - PullRequest
0 голосов
/ 10 сентября 2018

Это должно быть довольно просто. Я чувствую, что упускаю что-то явно очевидное, но я уже давно смотрю на это. У меня есть рекурсивный метод создания последовательности Коллатца (используя в качестве практики рекурсии) Однако, как только мой метод достигает требований для выхода, у меня появляется «возвращаемое значение», которое затем возвращается к рекурсивному вызову внутри метода. Если кто-нибудь может сказать мне, что мне не хватает, я был бы очень признателен! Спасибо, код ниже!

public int sequenceCreator(int currentVal, int numOfSteps)
    {
        int nextVal;
        while (currentVal != 1)
        { 
            if (currentVal % 2 == 0)
            {
                nextVal = currentVal / 2;
                numOfSteps++;
            }
            else // (currentVal % 2 > 0)
            {
                nextVal = (currentVal * 3) + 1;
                numOfSteps++;
            }
            return sequenceCreator(nextVal, numOfSteps);
        }

        return numOfSteps;
    }

Ответы [ 3 ]

0 голосов
/ 10 сентября 2018

return выходит только из этого конкретного вызова метода. Это не прерывает все времена, когда sequenceCreator() был вызван.

В этом случае это должно быть в порядке, потому что он возвращается к этой строке:

return sequenceCreator(nextVal, numOfSteps);

, который, в свою очередь, снова вернется к своему вызывающему абоненту и т. Д., Пока вы окончательно не решите все рекурсивные вызовы.

Но я мог бы написать такой метод вместо этого:

public int sequenceCreator(int currentVal)
{
    if (currentVal == 1) return 1;

    if (currentVal % 2 == 0)
    {
        return 1 + sequenceCreator(currentVal / 2);
    }
    else // (currentVal % 2 > 0)
    {
        return 1 + sequenceCreator(currentVal * 3 + 1);
    }
}

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

Ради интереса, мы можем еще больше сократить код с помощью троичного оператора (но я не рекомендую использовать эту версию, поскольку ухудшается читаемость imo):

public int sequenceCreator(int currentVal)
{
    if (currentVal == 1) return 1;
    return 1 + sequenceCreatetor(currentValue % 2 == 0? currentVal / 2 : currentVal * 3 + 1);
}

Я показываю эту версию для иллюстрации , почему используется рекурсия. В основе их лежат рекурсивные проблемы. Для каждого рекурсивного решения существует соответствующее решение, которое опирается только на традиционные методы, используя вместо этого стек (рекурсивное решение просто «скрывает» стек, полагаясь на стек вызовов программы). Однако для некоторых типов проблем рекурсия позволяет радикально уменьшить количество кода, необходимого для ее решения. И поэтому у нас есть и обратное; если вы обнаружите, что используете стек, может быть способом использовать рекурсию, чтобы значительно упростить проблему. В этом случае тело метода состоит всего из двух строк, и, если бы я действительно этого хотел, я мог бы поместить его в одну строку кода.

Вы также должны понимать, что не создает последовательность. Он создает ровно одно значение в тех случаях, когда он вообще сходится. Если вы действительно хотите создать последовательность, вам нужно вернуть IEnumerable, в идеале используя ключевое слово yield:

public IEnumerable<int> sequenceCreator(int currentVal)
{
    if (currentVal == 1) yield return 1;

    if (currentVal % 2 == 0)
    {
        yield return 1 + sequenceCreator(currentVal / 2);
    }
    else // (currentVal % 2 > 0)
    {
        yield return 1 + sequenceCreator(currentVal * 3 + 1);
    }
}

По логике вещей, я думаю, вы здесь в безопасности, и он сойдет. «Финальный» или базовый вариант этого метода - это ввод 1. Вызовы, где ввод даже уменьшается к этому базовому случаю. Вызовы, где ввод нечетный, увеличиваются по сравнению с базовым регистром, но таким образом, когда следующий ввод всегда четный и всегда отличается четным значением, чем мы пытались ранее. В конечном итоге мы надеемся получить в два или три раза больше, чем два, которые останутся даже при уменьшении вплоть до 3 или 2, а затем 1 и затем выйдут.

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

0 голосов
/ 10 сентября 2018

Я думаю, что не должно быть никакой петли.Используйте условие if вместо while.

If (currentVal! = 1)

0 голосов
/ 10 сентября 2018

Избегайте рекурсии, если это возможно:

        int sequenceNumber = Convert.ToInt32(Console.ReadLine());
        List<int> list = new List<int>();

        while (sequenceNumber>=1)
        {
            if (sequenceNumber == 1)
            {
                Console.WriteLine(1);
                sequenceNumber = Convert.ToInt32(Console.ReadLine());
            }

            else if(sequenceNumber>1)
            {
                while (sequenceNumber>=1)
                {
                    if (sequenceNumber == 1)
                    {
                        list.Add(sequenceNumber);
                    }

                    else if (sequenceNumber % 2 == 0)
                    {
                        list.Add(sequenceNumber);
                        sequenceNumber = sequenceNumber / 2;

                    }
                    else if (sequenceNumber % 2 != 0)
                    {
                        list.Add(sequenceNumber);
                        sequenceNumber = sequenceNumber * 3 + 1;

                    }
                }

                list.ForEach(Console.WriteLine);
                foreach (int i in list)
                {
                    Console.Write(i + " ");

                }
            }

            sequenceNumber = Convert.ToInt32(Console.ReadLine());
        } 
    }
...