Список изменяется при рекурсивных вызовах при передаче клонов списка в качестве аргумента (C#)

Я создаю алгоритм для решения судоку с использованием распространения ограничений и локального поиска (аналогично Norvig). Для этого я отслеживаю списки возможных значений для каждого из квадратов в судоку. Для каждой попытки присвоить новое значение квадрату я клонирую массив и рекурсивно передаю его методу поиска алгоритма. Однако каким-то образом этот список все еще изменяется. Речь идет о методе:

List<int>[,] search(List<int>[,] vals)
{
    if (vals == null)
        return null;
    if (isSolved(vals))
        return vals;
    //get square with lowest amt of possible values
    int min = Int32.MaxValue;
    Point minP = new Point();
    for (int y = 0; y < n * n; y++)
    {
        for (int x = 0; x < n * n; x++)
        {
            if (vals[y, x].Count < min && vals[y, x].Count > 1)
            {
                min = vals[y, x].Count;
                minP = new Point(x, y);
            }
        }
    }
    for(int i=vals[minP.Y, minP.X].Count-1; i >= 0; i--)
    {
        //<Here> The list vals[minP.Y, minP.X] is altered within this for loop somehow, even though the only thing done with it is it being cloned and passed to the assign method for another (altered) search afterwards
        Console.WriteLine(vals[minP.Y, minP.X].Count + "-" + min);
        int v = vals[minP.Y, minP.X][i];
        List<int>[,] newVals = (List<int>[,])vals.Clone();
        List<int>[,] l = search(assign(minP, v, newVals));
        if (l != null)
            return l;
    }
    return null;
}

Список vals[minP.Y, minP.X] каким-то образом изменяется внутри цикла for, из-за чего он в конечном итоге пытается передать методу assign квадраты, имеющие 1 (или даже 0) возможных значений. Оператор Console.Writeline показывает, что vals[minP.Y, minP.X].Count в конечном итоге будет отличаться от переменной 'min' (которая определена как то же самое над циклом for).

Если бы кто-нибудь мог помочь мне в том, как список изменяется в этом цикле for и как это исправить, я был бы очень признателен!

С наилучшими пожеланиями.

РЕДАКТИРОВАТЬ: методы, в которых редактируются эти списки (однако в клонированной версии):

List<int>[,] assign(Point p, int v, List<int>[,] vals)
{
    int y = p.Y, x = p.X;
    for (int i = vals[y, x].Count - 1; i >= 0; i--)
    {
        int v_ = vals[y, x][i];
        if (v_ != v && !eliminate(p, v_, vals))
            return null;
    }
    return vals;
}

bool eliminate(Point p, int v, List<int>[,] vals)
{
    if (!vals[p.Y, p.X].Remove(v))
        return true; //al ge-elimineerd

    // Update peers when only 1 possible value left
    if (vals[p.Y, p.X].Count == 1)
        foreach (Point peer in peers[p.Y, p.X])
            if(!eliminate(peer, vals[p.Y, p.X][0], vals))
                return false;
    else if (vals[p.Y, p.X].Count == 0)
        return false;

    // Update units
    List<Point> vplaces = new List<Point>();
    foreach (Point unit in units[p.Y, p.X])
    {
        if (vals[unit.Y, unit.X].Contains(v))
        {
            vplaces.Add(unit);
            if (vplaces.Count > 1)
                continue;
        }
    }

    if (vplaces.Count == 0)
        return false;
    else if (vplaces.Count == 1)
    {
        Console.WriteLine("test");
        if (assign(vplaces[0], v, vals) == null)
            return false;
    }

    return true;
}

person user2273652    schedule 27.06.2016    source источник
comment
Пожалуйста, предоставьте отдельный пример кода (см. минимальный воспроизводимый пример для руководства) - в вашем образце используются переменные/методы, которые не показывают.   -  person Alexei Levenkov    schedule 27.06.2016
comment
@AlexeiLevenkov Я попытаюсь обновить свой вопрос, однако проблема должна быть в показанном цикле for, поскольку нигде больше конкретный список не редактируется.   -  person user2273652    schedule 27.06.2016
comment
Обратите внимание, что вместо того, чтобы уменьшить пример кода, вы просто вставили целую кучу кода... :(   -  person Alexei Levenkov    schedule 28.06.2016


Ответы (1)


Ваша проблема с

List<int>[,] newVals = (List<int>[,])vals.Clone();

Array.Clone() делает не то, что вы думаете. List<int>[,] представляет собой двумерный массив из List<int> объектов — фактически трехмерный массив. Поскольку List<int> не является базовым типом значения, .Clone() создает неглубокую копию массива.

Другими словами, он создает совершенно новый двумерный массив, который для каждого значения имеет указатель на тот же List<int>, что и старый. Если бы C# позволял вам манипулировать указателями напрямую, вы могли бы начать их изменять, но поскольку это не так, каждый раз, когда вы обращаетесь к базовому List<int>, вы получаете один и тот же указатель, независимо от того, находится ли он до Clone() или после него.

См. документацию по нему здесь, а некоторые решения находятся здесь и здесь.

По сути, вам нужно переписать эту строку так, чтобы вместо копирования самого массива все значения копировались в новые List<int>.

person Bobson    schedule 27.06.2016
comment
Ааа я не знал, что спасибо! Я попытался создать отдельный метод deepCopy, который просматривает все списки массива и воссоздает их, например, newArray[y, x] = new List‹int›(oldArray[y, x]), но каким-то образом это приводит к исключению переполнения стека. Но должно ли это работать? РЕДАКТИРОВАТЬ: Неважно, исправил эту проблему, теперь она работает! Спасибо. - person user2273652; 27.06.2016
comment
@user2273652 user2273652 - Рад, что это сработало для вас. Это определенно неочевидная проблема. - person Bobson; 28.06.2016