001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.collections4.sequence;
018
019import java.util.List;
020
021import org.apache.commons.collections4.Equator;
022import org.apache.commons.collections4.functors.DefaultEquator;
023
024/**
025 * This class allows to compare two objects sequences.
026 * <p>
027 * The two sequences can hold any object type, as only the {@code equals}
028 * method is used to compare the elements of the sequences. It is guaranteed
029 * that the comparisons will always be done as {@code o1.equals(o2)} where
030 * {@code o1} belongs to the first sequence and {@code o2} belongs to
031 * the second sequence. This can be important if subclassing is used for some
032 * elements in the first sequence and the {@code equals} method is
033 * specialized.
034 * </p>
035 * <p>
036 * Comparison can be seen from two points of view: either as giving the smallest
037 * modification allowing to transform the first sequence into the second one, or
038 * as giving the longest sequence which is a subsequence of both initial
039 * sequences. The {@code equals} method is used to compare objects, so any
040 * object can be put into sequences. Modifications include deleting, inserting
041 * or keeping one object, starting from the beginning of the first sequence.
042 * </p>
043 * <p>
044 * This class implements the comparison algorithm, which is the very efficient
045 * algorithm from Eugene W. Myers
046 * <a href="https://www.cis.upenn.edu/~bcpierce/courses/dd/papers/diff.ps">
047 * An O(ND) Difference Algorithm and Its Variations</a>. This algorithm produces
048 * the shortest possible
049 * {@link EditScript edit script}
050 * containing all the
051 * {@link EditCommand commands}
052 * needed to transform the first sequence into the second one.
053 * </p>
054 *
055 * @param <T> The type of elements in the lists.
056 * @see EditScript
057 * @see EditCommand
058 * @see CommandVisitor
059 * @since 4.0
060 */
061public class SequencesComparator<T> {
062
063    /**
064     * This class is a simple placeholder to hold the end part of a path
065     * under construction in a {@link SequencesComparator SequencesComparator}.
066     */
067    private static final class Snake {
068
069        /** Start index. */
070        private final int start;
071
072        /** End index. */
073        private final int end;
074
075        /** Diagonal number. */
076        private final int diag;
077
078        /**
079         * Simple constructor. Creates a new instance of Snake with specified indices.
080         *
081         * @param start  start index of the snake
082         * @param end  end index of the snake
083         * @param diag  diagonal number
084         */
085        Snake(final int start, final int end, final int diag) {
086            this.start = start;
087            this.end   = end;
088            this.diag  = diag;
089        }
090
091        /**
092         * Gets the diagonal number of the snake.
093         *
094         * @return diagonal number of the snake
095         */
096        public int getDiag() {
097            return diag;
098        }
099
100        /**
101         * Gets the end index of the snake.
102         *
103         * @return end index of the snake
104         */
105        public int getEnd() {
106            return end;
107        }
108
109        /**
110         * Gets the start index of the snake.
111         *
112         * @return start index of the snake
113         */
114        public int getStart() {
115            return start;
116        }
117    }
118
119    /** First sequence. */
120    private final List<T> sequence1;
121
122    /** Second sequence. */
123    private final List<T> sequence2;
124
125    /** The equator used for testing object equality. */
126    private final Equator<? super T> equator;
127
128    /** Temporary variables. */
129    private final int[] vDown;
130
131    private final int[] vUp;
132
133    /**
134     * Simple constructor.
135     * <p>
136     * Creates a new instance of SequencesComparator using a {@link DefaultEquator}.
137     * <p>
138     * It is <em>guaranteed</em> that the comparisons will always be done as
139     * {@code o1.equals(o2)} where {@code o1} belongs to the first
140     * sequence and {@code o2} belongs to the second sequence. This can be
141     * important if subclassing is used for some elements in the first sequence
142     * and the {@code equals} method is specialized.
143     *
144     * @param sequence1  first sequence to be compared
145     * @param sequence2  second sequence to be compared
146     */
147    public SequencesComparator(final List<T> sequence1, final List<T> sequence2) {
148        this(sequence1, sequence2, DefaultEquator.defaultEquator());
149    }
150
151    /**
152     * Simple constructor.
153     * <p>
154     * Creates a new instance of SequencesComparator with a custom {@link Equator}.
155     * <p>
156     * It is <em>guaranteed</em> that the comparisons will always be done as
157     * {@code Equator.equate(o1, o2)} where {@code o1} belongs to the first
158     * sequence and {@code o2} belongs to the second sequence.
159     *
160     * @param sequence1  first sequence to be compared
161     * @param sequence2  second sequence to be compared
162     * @param equator  The equator to use for testing object equality
163     */
164    public SequencesComparator(final List<T> sequence1, final List<T> sequence2, final Equator<? super T> equator) {
165        this.sequence1 = sequence1;
166        this.sequence2 = sequence2;
167        this.equator = equator;
168
169        final int size = sequence1.size() + sequence2.size() + 2;
170        vDown = new int[size];
171        vUp   = new int[size];
172    }
173
174    /**
175     * Build an edit script.
176     *
177     * @param start1  The start of the first sequence to be compared
178     * @param end1  The end of the first sequence to be compared
179     * @param start2  The start of the second sequence to be compared
180     * @param end2  The end of the second sequence to be compared
181     * @param script The edited script
182     */
183    private void buildScript(final int start1, final int end1, final int start2, final int end2,
184                             final EditScript<T> script) {
185
186        final Snake middle = getMiddleSnake(start1, end1, start2, end2);
187
188        if (middle == null
189                || middle.getStart() == end1 && middle.getDiag() == end1 - end2
190                || middle.getEnd() == start1 && middle.getDiag() == start1 - start2) {
191
192            int i = start1;
193            int j = start2;
194            while (i < end1 || j < end2) {
195                if (i < end1 && j < end2 && equator.equate(sequence1.get(i), sequence2.get(j))) {
196                    script.append(new KeepCommand<>(sequence1.get(i)));
197                    ++i;
198                    ++j;
199                } else if (end1 - start1 > end2 - start2) {
200                    script.append(new DeleteCommand<>(sequence1.get(i)));
201                    ++i;
202                } else {
203                    script.append(new InsertCommand<>(sequence2.get(j)));
204                    ++j;
205                }
206            }
207
208        } else {
209
210            buildScript(start1, middle.getStart(),
211                        start2, middle.getStart() - middle.getDiag(),
212                        script);
213            for (int i = middle.getStart(); i < middle.getEnd(); ++i) {
214                script.append(new KeepCommand<>(sequence1.get(i)));
215            }
216            buildScript(middle.getEnd(), end1,
217                        middle.getEnd() - middle.getDiag(), end2,
218                        script);
219        }
220    }
221
222    /**
223     * Build a snake.
224     *
225     * @param start  The value of the start of the snake
226     * @param diag  The value of the diagonal of the snake
227     * @param end1  The value of the end of the first sequence to be compared
228     * @param end2  The value of the end of the second sequence to be compared
229     * @return The snake built
230     */
231    private Snake buildSnake(final int start, final int diag, final int end1, final int end2) {
232        int end = start;
233        while (end - diag < end2
234                && end < end1
235                && equator.equate(sequence1.get(end), sequence2.get(end - diag))) {
236            ++end;
237        }
238        return new Snake(start, end, diag);
239    }
240
241    /**
242     * Gets the middle snake corresponding to two subsequences of the
243     * main sequences.
244     * <p>
245     * The snake is found using the MYERS Algorithm (this algorithm has
246     * also been implemented in the GNU diff program). This algorithm is
247     * explained in Eugene Myers article:
248     * <a href="https://web.archive.org/web/20040719035900/http%3A//www.cs.arizona.edu/people/gene/PAPERS/diff.ps">
249     * An O(ND) Difference Algorithm and Its Variations</a>.
250     *
251     * @param start1  The start of the first sequence to be compared
252     * @param end1  The end of the first sequence to be compared
253     * @param start2  The start of the second sequence to be compared
254     * @param end2  The end of the second sequence to be compared
255     * @return The middle snake
256     */
257    private Snake getMiddleSnake(final int start1, final int end1, final int start2, final int end2) {
258        // Myers Algorithm
259        // Initializations
260        final int m = end1 - start1;
261        final int n = end2 - start2;
262        if (m == 0 || n == 0) {
263            return null;
264        }
265
266        final int delta = m - n;
267        final int sum = n + m;
268        final int offset = (sum % 2 == 0 ? sum : sum + 1) / 2;
269        vDown[1 + offset] = start1;
270        vUp[1 + offset] = end1 + 1;
271
272        for (int d = 0; d <= offset; ++d) {
273            // Down
274            for (int k = -d; k <= d; k += 2) {
275                // First step
276
277                final int i = k + offset;
278                if (k == -d || k != d && vDown[i - 1] < vDown[i + 1]) {
279                    vDown[i] = vDown[i + 1];
280                } else {
281                    vDown[i] = vDown[i - 1] + 1;
282                }
283
284                int x = vDown[i];
285                int y = x - start1 + start2 - k;
286
287                while (x < end1 && y < end2 && equator.equate(sequence1.get(x), sequence2.get(y))) {
288                    vDown[i] = ++x;
289                    ++y;
290                }
291                // Second step
292                if (delta % 2 != 0 && delta - d <= k && k <= delta + d && vUp[i - delta] <= vDown[i]) { // NOPMD
293                    return buildSnake(vUp[i - delta], k + start1 - start2, end1, end2);
294                }
295            }
296
297            // Up
298            for (int k = delta - d; k <= delta + d; k += 2) {
299                // First step
300                final int i = k + offset - delta;
301                if (k == delta - d || k != delta + d && vUp[i + 1] <= vUp[i - 1]) {
302                    vUp[i] = vUp[i + 1] - 1;
303                } else {
304                    vUp[i] = vUp[i - 1];
305                }
306
307                int x = vUp[i] - 1;
308                int y = x - start1 + start2 - k;
309                while (x >= start1 && y >= start2 && equator.equate(sequence1.get(x), sequence2.get(y))) {
310                    vUp[i] = x--;
311                    y--;
312                }
313                // Second step
314                if (delta % 2 == 0 && -d <= k && k <= d && vUp[i] <= vDown[i + delta]) { // NOPMD
315                    return buildSnake(vUp[i], k + start1 - start2, end1, end2);
316                }
317            }
318        }
319
320        // this should not happen
321        throw new IllegalStateException("Internal Error");
322    }
323
324    /**
325     * Gets the {@link EditScript} object.
326     * <p>
327     * It is guaranteed that the objects embedded in the {@link InsertCommand
328     * insert commands} come from the second sequence and that the objects
329     * embedded in either the {@link DeleteCommand delete commands} or
330     * {@link KeepCommand keep commands} come from the first sequence. This can
331     * be important if subclassing is used for some elements in the first
332     * sequence and the {@code equals} method is specialized.
333     *
334     * @return The edit script resulting from the comparison of the two
335     *         sequences
336     */
337    public EditScript<T> getScript() {
338        final EditScript<T> script = new EditScript<>();
339        buildScript(0, sequence1.size(), 0, sequence2.size(), script);
340        return script;
341    }
342}