public VArray Copy(int d) { if (d == 0) { d++; } var v = new VArray { m_max = d, m_array = new int[2 * d + 1] }; if (d <= this.m_max) { Array.Copy(this.m_array, this.m_max - v.m_max, v.m_array, 0, v.m_array.Length); } return(v); }
/// <summary> /// Вычисление змейки /// </summary> /// <param name="v">Массив V</param> /// <param name="k">Диагональ, относительно которой ищем максимально протяженный D-путь</param> /// <param name="d">Длина D-пути</param> /// <param name="a">Оригинал</param> /// <param name="n">Длина оригинала</param> /// <param name="b">Источник с изменениями</param> /// <param name="m">Длина источника с изменениями</param> /// <returns></returns> public static Snake CalculateSnake(VArray v, int k, int d, ISource a, int n, ISource b, int m) { var down = (k == -d || (k != d && v[k - 1] < v[k + 1])); var xStart = down ? v[k + 1] : v[k - 1]; var yStart = xStart - (down ? k + 1 : k - 1); var xEnd = down ? xStart : xStart + 1; var yEnd = xEnd - k; var snake = 0; while (xEnd < n && yEnd < m && Equals(a[xEnd], b[yEnd])) { xEnd++; yEnd++; snake++; } var snk = new Snake(yStart < 0 ? OperationKind.Equal : (down ? OperationKind.Insert : OperationKind.Delete), xStart, yStart, xEnd, yEnd, d, snake, down ? (yStart < 0 ? ' ' : b[yStart]) : a[xStart]); return(snk); }
public IEnumerable <Operation> GetDiff(ISource original, ISource target) { var a = original; var b = target; /* * Если один из источников пуст, то * построим список операций вставки для другого. * * Если оба пусты, то обработку такой ситуации оставим на усмотрение вызывающего сервиса, вернув null. */ if (a == null || b == null) { return(BuildOperationsForSingleSource(a) ?? BuildOperationsForSingleSource(b)); } var n = a.Length; var m = b.Length; var vs = new List <VArray>(); var v = new VArray(n, m) { [1] = 0 }; var hasSolution = false; /* * d - длина наименьшего списка изменений, необходимого для превращения А в В. * Не зная решения заранее, мы подразумеваем, что при самом неблагоприятном исходе * d будет равно сумме длин А и В, т.е. В полностью будет отличаться от А. * * Соответственно, D-путь - путь с началом в точке (0;0), включающий ровно D * недиагональных (операция вставки или удаления) шагов, в резльтате которого А можно превратить в В. */ for (var d = 0; d < n + m; d++) { /* * Согласно Майерсу, каждый D-путь должен оканчиваться на диагонали k (k = x - y), k ∈ {-D ,-D+2, ..., D-2, D} */ for (var k = -d; k <= d; k += 2) { /* * Найдем направление движения в матрице. * Если мы оказались на диагонали k = -d или ушли с текущей диагонали и следующий шаг вниз (вставка нового элемента) будет выгоднее и в итоге мы дальше продвинемся по диагонали k. */ var down = (k == -d || (k != d && v[k - 1] < v[k + 1])); //Начало змейки, координата Х var xStart = down ? v[k + 1] : v[k - 1]; /* * Конец змейки, координата Х. * Если мы идем вправо (удаление элемента), то смещаемся по оси Х на 1 шаг относительно начала. */ var xEnd = xStart + (down ? 0 : 1); //Координату Y вычисляем по формуле y = x - k var yEnd = xEnd - k; /* * Мы определили операцию (вставку или удаление), * далее найдем количество совпадающих элементов - диагональных шагов в змейке. * Делаем это до тех пор, пока не дойдем в нижнюю правую точку матрицы или не закончатся совпадения элементов. */ while (xEnd < n && yEnd < m && Equals(a[xEnd], b[yEnd])) { xEnd++; yEnd++; } //Сохраним максимально длинный путь по диагонали k в массив V v[k] = xEnd; //Продолжаем, если мы не дошли до нижней правой точки матрицы if (xEnd < n || yEnd < m) { continue; } //Есть решение! hasSolution = true; break; } //Добавим текущий массив V в список для дальнейшего построения змеек. vs.Add(v.Copy(d)); /* * Остановим поиски, если решение найдено. * Особенность алгоритма в том, что он останавливает работу когда будет найдено первое решение */ if (hasSolution) { break; } } if (!hasSolution) { throw new NoDiffSolutionException(); } var snakes = new List <Snake>(); var p = new intPoint(n, m); /* * Переберем все возможные варианты движения из сохраненных на этапе работы алгоритма. * Т.к. змейки строятся из конечной точки в начальную (из правого нижнего угла матрицы в верхний левый), * то мы либо переберем все возможные массивы V, либо дойдем до точки начала и на том остановимся. */ for (var d = vs.Count - 1; p.X > 0 || p.Y > 0; d--) { //Построим змейку на основании текущего массива V var snake = Snake.CalculateSnake(vs[d], p.X - p.Y, d, a, n, b, m); //Сохраним змейку snakes.Add(snake); //Переместимся в точку, где оканчивается текущая змейка p.X = snake.Start.X; p.Y = snake.Start.Y; } snakes.Reverse(); return(SnakesToOperations(a, b, snakes)); }