コード例 #1
0
ファイル: VArray.cs プロジェクト: beastofman/automerge
        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);
        }
コード例 #2
0
        /// <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);
        }
コード例 #3
0
        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));
        }