Я пытаюсь перенести этот генетический алгоритм , и я сделал рекурсивную функцию для перехода от одного поколения к другому.

Однако, поскольку я новичок в рекурсии в C # (и вообще), я столкнулся с StackOverflowException, когда было слишком много поколений (более 4500).

Чтобы решить проблему, я заставил Generation () возвращать логическое значение, поэтому, когда генетический алгоритм достигает максимальной пригодности (цели), он возвращает значение true. Иначе он возвращает Generation ().

Если он собирается переполниться (Поколение> 4500), он возвращает ложь.

Теперь в Main (), чтобы поддерживать Generation () до тех пор, пока он не вернет true, я использую цикл while, поэтому он будет начинаться с рекурсии, пока не завершится.

Это намного эффективнее, чем выполнение Task.Run, поэтому я выбрал этот подход.

Это хорошая практика? Есть ли более элегантные способы предотвращения переполнения стека без ущерба для производительности?

< Сильный > Population.cs :

class Population
{

    public int GenerationNumber { get; private set; }
    public int TotalGenerationNumber { get; private set; }
    public const int StackGenerationLimit = 4500;

    public Population()
    {

        GenerationNumber = 0;
        TotalGenerationNumber = 0;


    }
    public bool Generation()
    {
        // Work
        // if(HasReachedGoal) return true;

        GenerationNumber++;


        if(GenerationNumber > StackGenerationLimit)
        {
            return false;
        } else
        {
            return Generation();
        }


    }

    public void ResetStack()
    {
        TotalGenerationNumber += GenerationNumber; // I store the total number of generation for information purposes
        GenerationNumber = 0; // Reset the recursion depth value
    }




}

< Сильный > Program.cs

class Program
{
    static void Main(string[] args)
    {
        Population population = new Population();

        while (!population.Generation()) // Until it reaches its goal
        {
            population.ResetStack();
        }

        Console.WriteLine("End. Generations: " + population.TotalGenerationNumber);


    }
}
1
Nico de Ory 24 Апр 2017 в 17:52

2 ответа

Лучший ответ

Лучший способ избежать переполнения стека - не использовать рекурсию. Вы уже на полпути к ответу с вашим обходным путем. Теперь вам просто нужно задать себе вопрос, что вы больше получаете от рекурсии? Если вместо этого ваш return Generation(); оператор в функции Generation был изменен на return false;, вы вернетесь в основной цикл, где он снова вызовет Generation().

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

Итак, ваши два метода:

public bool Generation()
{
    TotalGenerationNumber++;
    // Work
    return HasReachedGoal;
}

static void Main(string[] args)
{
    Population population = new Population();
    bool hasCompleted = false;
    while (!hasCompleted) // Until it reaches its goal
    {
        hasCompleted = population.Generation();
    }

    Console.WriteLine("End. Generations: " + population.TotalGenerationNumber);
}

Обратите внимание, что в tidyup я ввел переменную bool с именем hasCompleted, так как считаю более удобной для использования переменную для условия while и предпочитаю работать внутри самого цикла.

3
Chris 24 Апр 2017 в 15:03

Я думаю, что в этом случае вам лучше подготовить цикл while и отправить данные, которые вы хотите проверить, в вызов .Generation. затем, если он возвращает false, вы обновляете данные. Что-то вроде этого:

    Population population = new Population();
    var input = new InputDto() { ... };
    while (!population.Generation(input)) // Until it reaches its goal
    {
        // update input
    }

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

0
Daniel Lorenz 24 Апр 2017 в 15:02