| // Copyright 2018 The diff-match-patch Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| // This code (especially GetBisectSplitPoints) derive in part from | |
| // diff_match_patch.cpp under the Apache 2.0 license and attribution: | |
| // | |
| // Author: fraser@google.com (Neil Fraser) | |
| // Author: mikeslemmer@gmail.com (Mike Slemmer) | |
| // | |
| // Diff Match and Patch | |
| // https://github.com/google/diff-match-patch | |
| // Returns the minimal number of edit operations (copy, delete, insert) | |
| // needed to tranform one container of tokens into another (cf. `diff -d -e`). | |
| // Requires random-access iterators of token streams. | |
| #ifndef EDITSCRIPT_H_ | |
| #define EDITSCRIPT_H_ | |
| #include <algorithm> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <iterator> | |
| #include <map> | |
| #include <tuple> | |
| #include <utility> | |
| #include <vector> | |
| namespace diff { | |
| enum class Operation { EQUALS, DELETE, INSERT }; | |
| /** | |
| * The data structure of edit operations to transform tokens1 into tokens2. | |
| * Indices for EQUALS and DELETE point into tokens1, INSERT into tokens2. | |
| * Concatenating EQUALS and DELETE tokens will return tokens1. | |
| * Concatenating EQUALS and INSERT tokens will return tokens2. | |
| * [start, end) is a semiopen interval. | |
| */ | |
| struct Edit { | |
| Operation operation; // One of: EQUALS, DELETE, or INSERT. | |
| int64_t start; // Start offset into tokens1 (tokens2 for INSERT). | |
| int64_t end; // End offset into tokens1 (tokens2 for INSERT). | |
| bool operator==(const Edit &rhs) const { | |
| return (std::tie(operation, start, end) == | |
| std::tie(rhs.operation, rhs.start, rhs.end)); | |
| } | |
| bool operator<(const Edit &rhs) const { | |
| return (std::tie(operation, start, end) < | |
| std::tie(rhs.operation, rhs.start, rhs.end)); | |
| } | |
| }; | |
| using Edits = std::vector<Edit>; | |
| /** | |
| * Finds the differences between two vectors of tokens, returning edits | |
| * required to transform tokens1 into tokens2. | |
| * Every token in the combined document belongs to exactly one edit. | |
| * | |
| * For example, for tokens1 = "Hello world." and tokens2 = "Goodbye world.": | |
| * {Edit(Operation::DELETE, 0, 5), // token1[0,5) == "Hello" | |
| * Edit(Operation::INSERT, 0, 7), // token2[0,7) == "Goodbye" | |
| * Edit(Operation::EQUALS, 5, 12)} // token1[5, 12) == " world." | |
| * | |
| * @param tokens1_begin Beginning index into tokens1. | |
| * @param tokens1_end Ending index into tokens1. | |
| * @param tokens2_begin Beginning index into tokens2. | |
| * @param tokens2_end Ending index into tokens2. | |
| * @return Cumulative edits to transform tokens1 into tokens2. | |
| */ | |
| template <typename TokenIter> | |
| Edits GetTokenDiffs(TokenIter tokens1_begin, TokenIter tokens1_end, | |
| TokenIter tokens2_begin, TokenIter tokens2_end); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| // IMPLEMENTATION | |
| ////////////////////////////////////////////////////////////////////////////// | |
| namespace diff_impl { | |
| /** | |
| * Appends an edit operation to the cumulative edits. | |
| * Fuses with previous edit if possible. | |
| * @param op Edit operation. | |
| * @param start Start offset into tokens1 (tokens2 for INSERT). | |
| * @param end End offset into tokens1 (tokens2 for INSERT). | |
| * @param edits Cumulative vector of edits (inout). | |
| */ | |
| inline void AppendEdit(Operation op, int64_t start, int64_t end, Edits *edits) { | |
| if (!edits->empty() && edits->back().operation == op && | |
| edits->back().end == start) { | |
| // Merge into previous edit operation. | |
| edits->back().end = end; | |
| } else { | |
| // Add a new edit operation. | |
| edits->emplace_back(Edit{op, start, end}); | |
| } | |
| } | |
| /** | |
| * Inserts an edit operation into the cumulative edits at given index. | |
| * Fuses with neighboring edit if possible. | |
| * @param index Index of insertion point. | |
| * @param op Edit operation. | |
| * @param start Start offset into tokens1 (tokens2 for INSERT). | |
| * @param end End offset into tokens1 (tokens2 for INSERT). | |
| * @param edits Cumulative vector of edits (inout). | |
| */ | |
| inline void InsertEditAt(int64_t index, Operation op, int64_t start, | |
| int64_t end, Edits *edits) { | |
| if (index > 0) { | |
| Edit &prev_edit = (*edits)[index - 1]; | |
| if (prev_edit.operation == op && prev_edit.end == start) { | |
| // Merge into previous edit operation. | |
| prev_edit.end = end; | |
| return; | |
| } | |
| } | |
| if (index < static_cast<int64_t>(edits->size())) { | |
| Edit &next_edit = (*edits)[index]; | |
| if (next_edit.operation == op && next_edit.end == start) { | |
| // Merge into subsequent edit operation. | |
| next_edit.end = end; | |
| return; | |
| } | |
| } | |
| // Cannot merge into existing? Insert a new edit operation. | |
| edits->emplace(edits->begin() + index, Edit{op, start, end}); | |
| } | |
| /** | |
| * Reverse direction of iterator (without moving its location). | |
| * Returned iter will now point to one elem to left of input iter. | |
| * @param iter Original bidirectional or random iterator. | |
| */ | |
| template <typename TokenIter> | |
| inline std::reverse_iterator<TokenIter> Reverse(TokenIter iter) { | |
| return std::reverse_iterator<TokenIter>(iter); | |
| } | |
| /** | |
| * Determine the common affix of two tokens. | |
| * For common prefix, pass normal iterators. | |
| * For common suffix, pass reverse iterators. | |
| * @param span1_begin Beginning iterator into tokens1. | |
| * @param span1_end Ending iterator into tokens1. | |
| * @param span2_begin Beginning iterator into tokens2. | |
| * @param span2_end Ending iterator into tokens2. | |
| * @return The number of tokens common to the start of spans. | |
| */ | |
| template <typename TokenIter> | |
| inline int64_t CommonAffix(TokenIter span1_begin, TokenIter span1_end, | |
| TokenIter span2_begin, TokenIter span2_end) { | |
| // std::mismatch wants the shorter container first. | |
| if (span1_end - span1_begin > span2_end - span2_begin) { | |
| using std::swap; | |
| swap(span1_begin, span2_begin); | |
| swap(span1_end, span2_end); | |
| } | |
| auto affix_ends = std::mismatch(span1_begin, span1_end, span2_begin); | |
| const int64_t affix_length = affix_ends.first - span1_begin; | |
| return affix_length; | |
| } | |
| /** | |
| * Finds the differences between two vectors of tokens. | |
| * Invoke using GetTokenDiffs. | |
| */ | |
| template <typename TokenIter> | |
| class Diff { | |
| private: | |
| friend Edits GetTokenDiffs<>(TokenIter tokens1_begin, TokenIter tokens1_end, | |
| TokenIter tokens2_begin, TokenIter tokens2_end); | |
| /** | |
| * Finds the differences between two vectors of tokens, returning edits | |
| * required to transform tokens1 into tokens2. | |
| * Every token in the combined document belongs to exactly one edit. | |
| * @param tokens1_begin Iterator pointing to start of tokens1. | |
| * @param tokens2_begin Iterator pointing to start of tokens2. | |
| * @param edits Cumulative edits to transform tokens1 into tokens2 (inout). | |
| */ | |
| Diff(TokenIter tokens1_begin, TokenIter tokens2_begin) | |
| : tokens1_begin_(tokens1_begin), tokens2_begin_(tokens2_begin) {} | |
| /** | |
| * Find the differences between two vectors of tokens. | |
| * @param b1 Beginning index into tokens1. | |
| * @param e1 Ending index into tokens1. | |
| * @param b2 Beginning index into tokens2. | |
| * @param e2 Ending index into tokens2. | |
| * @param edits Cumulative edits to transform tokens1 into tokens2 (inout). | |
| * @param edits Vector of Edit objects | |
| */ | |
| void Generate(int64_t b1, int64_t e1, int64_t b2, int64_t e2, | |
| Edits *edits) const { | |
| // Avoid growing stack through recursion more than necessary. | |
| int64_t prefix_size = 0; | |
| int64_t suffix_size = 0; | |
| int64_t edits_size = 0; | |
| { | |
| // The diff strategy is to recursively: | |
| // - Peel off common prefix and suffix | |
| // - Split what's left in two parts | |
| // - Generate diffs for each and combine. | |
| // The diffs could be done in parallel (with a fresh edits) and | |
| // later combined if speed up is needed. | |
| auto span1_begin = tokens1_begin_ + b1; | |
| auto span2_begin = tokens2_begin_ + b2; | |
| auto span1_end = tokens1_begin_ + e1; | |
| auto span2_end = tokens2_begin_ + e2; | |
| // Check for equality (speedup). | |
| if ((e1 - b1) == (e2 - b2) && | |
| std::equal(span1_begin, span1_end, span2_begin)) { | |
| if (span1_begin != span1_end) { | |
| AppendEdit(Operation::EQUALS, b1, e1, edits); | |
| } | |
| return; | |
| } | |
| // Find longest common prefix and suffix. | |
| prefix_size = CommonAffix(span1_begin, span1_end, span2_begin, span2_end); | |
| suffix_size = | |
| CommonAffix(Reverse(span1_end), Reverse(span1_begin + prefix_size), | |
| Reverse(span2_end), Reverse(span2_begin + prefix_size)); | |
| // Remember the current location so we can insert a common prefix. | |
| edits_size = edits->size(); | |
| } | |
| // Compute the diff on the middle block. | |
| Compute(b1 + prefix_size, e1 - suffix_size, b2 + prefix_size, | |
| e2 - suffix_size, edits); | |
| // Restore the prefix and suffix. | |
| if (prefix_size != 0) { | |
| InsertEditAt(edits_size, Operation::EQUALS, b1, b1 + prefix_size, edits); | |
| } | |
| if (suffix_size != 0) { | |
| AppendEdit(Operation::EQUALS, e1 - suffix_size, e1, edits); | |
| } | |
| } | |
| /** | |
| * Find the differences between two vectors of tokens. | |
| * @pre Tokens are not equal nor share a common prefix or suffix. | |
| * @param b1 Beginning index into tokens1. | |
| * @param e1 Ending index into tokens1. | |
| * @param b2 Beginning index into tokens2. | |
| * @param e2 Ending index into tokens2. | |
| * @param edits Cumulative edits to transform tokens1 into tokens2 (inout). | |
| */ | |
| void Compute(int64_t b1, int64_t e1, int64_t b2, int64_t e2, | |
| Edits *edits) const { | |
| // Try various speedups first. | |
| // Enclose in a scope to save stack space before recursing. | |
| { | |
| const int64_t length1 = e1 - b1; | |
| const int64_t length2 = e2 - b2; | |
| if (length1 == 0 && length2 != 0) { | |
| // tokens1 empty, but tokens2 isn't: Just insert tokens2 | |
| AppendEdit(Operation::INSERT, b2, e2, edits); | |
| return; | |
| } | |
| if (length2 == 0 && length1 != 0) { | |
| // tokens2 empty, but tokens1 isn't: Just delete tokens1 | |
| AppendEdit(Operation::DELETE, b1, e1, edits); | |
| return; | |
| } | |
| if (length1 > length2) { | |
| const int64_t offset = | |
| std::search(tokens1_begin_ + b1, tokens1_begin_ + e1, | |
| tokens2_begin_ + b2, tokens2_begin_ + e2) - | |
| tokens1_begin_; | |
| if (offset != e1) { | |
| // tokens2 is a proper substring of tokens1: delete the rest. | |
| const int64_t offset_end = offset + length2; | |
| AppendEdit(Operation::DELETE, b1, offset, edits); | |
| AppendEdit(Operation::EQUALS, offset, offset_end, edits); | |
| AppendEdit(Operation::DELETE, offset_end, e1, edits); | |
| return; | |
| } | |
| if (length2 == 1) { | |
| // Single-token span. | |
| // After the previous speedup, the operation can't be EQUALS. | |
| AppendEdit(Operation::DELETE, b1, e1, edits); | |
| AppendEdit(Operation::INSERT, b2, e2, edits); | |
| return; | |
| } | |
| } else if (length2 > length1) { | |
| const int64_t offset = | |
| std::search(tokens2_begin_ + b2, tokens2_begin_ + e2, | |
| tokens1_begin_ + b1, tokens1_begin_ + e1) - | |
| tokens2_begin_; | |
| if (offset != e2) { | |
| // tokens1 is a proper substring of tokens2: insert the rest. | |
| AppendEdit(Operation::INSERT, b2, offset, edits); | |
| AppendEdit(Operation::EQUALS, b1, e1, edits); // Index into tokens1! | |
| AppendEdit(Operation::INSERT, offset + length1, e2, edits); | |
| return; | |
| } | |
| if (length1 == 1) { | |
| // Single-token span. | |
| // After the previous speedup, the operation can't be EQUALS. | |
| AppendEdit(Operation::DELETE, b1, e1, edits); | |
| AppendEdit(Operation::INSERT, b2, e2, edits); | |
| return; | |
| } | |
| } | |
| } | |
| // No speedups apply? Bisect and diff each half, then combine results. | |
| Bisect(b1, e1, b2, e2, edits); | |
| } | |
| /** | |
| * Find the 'middle snake' of a diff, returning split points. | |
| * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. | |
| * @param b1 Beginning index into tokens1. | |
| * @param e1 Ending index into tokens1. | |
| * @param b2 Beginning index into tokens2. | |
| * @param e2 Ending index into tokens2. | |
| * @return Index of split points in tokens1 and tokens2, respectively. | |
| * Returns {-1, -1} if there is no need to bisect. | |
| */ | |
| std::pair<int64_t, int64_t> GetBisectSplitPoints(int64_t b1, int64_t e1, | |
| int64_t b2, | |
| int64_t e2) const { | |
| std::pair<int64_t, int64_t> splits; | |
| int64_t &x1 = splits.first; | |
| int64_t &y1 = splits.second; | |
| const int64_t length1 = e1 - b1; | |
| const int64_t length2 = e2 - b2; | |
| const int64_t max_d = (length1 + length2 + 1) / 2; | |
| const int64_t v_offset = max_d; | |
| const int64_t v_size = 2 * max_d; | |
| const int64_t w_size = 2 * v_size; | |
| std::vector<int64_t> w(w_size + 4, | |
| -1); // interleave for cache friendliness | |
| w[2 * v_offset + 2] = 0; | |
| w[2 * v_offset + 3] = 0; | |
| const int64_t delta = length1 - length2; | |
| // If the total number of tokens is odd, then the front path will | |
| // collide with the reverse path. | |
| const bool front = (delta % 2 != 0); | |
| // Offsets for start and end of k loop. | |
| // Prevents mapping of space beyond the grid. | |
| int64_t k1start = 0; | |
| int64_t k1end = 0; | |
| int64_t k2start = 0; | |
| int64_t k2end = 0; | |
| for (int64_t d = 0; d < max_d; d++) { | |
| // Walk the front path one step. | |
| for (int64_t k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { | |
| const int64_t k1_offset = v_offset + k1; | |
| x1 = 0; | |
| if (k1 == -d || | |
| (k1 != d && w[2 * k1_offset - 2] < w[2 * k1_offset + 2])) { | |
| x1 = w[2 * k1_offset + 2]; | |
| } else { | |
| x1 = w[2 * k1_offset - 2] + 1; | |
| } | |
| y1 = x1 - k1; | |
| while (x1 < length1 && y1 < length2 && | |
| *(tokens1_begin_ + (b1 + x1)) == *(tokens2_begin_ + (b2 + y1))) { | |
| x1++; | |
| y1++; | |
| } | |
| w[2 * k1_offset] = x1; | |
| if (x1 > length1) { | |
| // Ran off the right of the graph. | |
| k1end += 2; | |
| } else if (y1 > length2) { | |
| // Ran off the bottom of the graph. | |
| k1start += 2; | |
| } else if (front) { | |
| int64_t k2_offset = v_offset + delta - k1; | |
| if (k2_offset >= 0 && k2_offset < v_size && | |
| w[2 * k2_offset + 1] != -1) { | |
| // Mirror x2 onto top-left coordinate system. | |
| int64_t x2 = length1 - w[2 * k2_offset + 1]; | |
| if (x1 >= x2) { | |
| // Overlap detected. | |
| return splits; | |
| } | |
| } | |
| } | |
| } | |
| // Walk the reverse path one step. | |
| for (int64_t k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { | |
| const int64_t k2_offset = v_offset + k2; | |
| int64_t x2; | |
| if (k2 == -d || | |
| (k2 != d && w[2 * k2_offset - 1] < w[2 * k2_offset + 3])) { | |
| x2 = w[2 * k2_offset + 3]; | |
| } else { | |
| x2 = w[2 * k2_offset - 1] + 1; | |
| } | |
| int64_t y2 = x2 - k2; | |
| while (x2 < length1 && y2 < length2 && | |
| *(tokens1_begin_ + (b1 + length1 - x2 - 1)) == | |
| *(tokens2_begin_ + (b2 + length2 - y2 - 1))) { | |
| x2++; | |
| y2++; | |
| } | |
| w[2 * k2_offset + 1] = x2; | |
| if (x2 > length1) { | |
| // Ran off the left of the graph. | |
| k2end += 2; | |
| } else if (y2 > length2) { | |
| // Ran off the top of the graph. | |
| k2start += 2; | |
| } else if (!front) { | |
| int64_t k1_offset = v_offset + delta - k2; | |
| if (k1_offset >= 0 && k1_offset < v_size && w[2 * k1_offset] != -1) { | |
| x1 = w[2 * k1_offset]; | |
| y1 = v_offset + x1 - k1_offset; | |
| // Mirror x2 onto top-left coordinate system. | |
| x2 = length1 - x2; | |
| if (x1 >= x2) { | |
| // Overlap detected. | |
| return splits; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| x1 = -1; | |
| y1 = -1; | |
| return splits; | |
| } | |
| /** | |
| * Bisect and recurse if necessary. | |
| * @param b1 Beginning index into tokens1. | |
| * @param e1 Ending index into tokens1. | |
| * @param b2 Beginning index into tokens2. | |
| * @param e2 Ending index into tokens2. | |
| * @param edits Cumulative edits to transform tokens1 into tokens2 (inout). | |
| */ | |
| void Bisect(int64_t b1, int64_t e1, int64_t b2, int64_t e2, | |
| Edits *edits) const { | |
| int64_t x1, x2; | |
| std::tie(x1, x2) = GetBisectSplitPoints(b1, e1, b2, e2); | |
| if (x1 >= 0) { | |
| // Some commonality, so bisect and recurse. | |
| Generate(b1, b1 + x1, b2, b2 + x2, edits); | |
| Generate(b1 + x1, e1, b2 + x2, e2, edits); | |
| } else { | |
| // No commonality at all (number of edits equals number of tokens), | |
| // so just delete the old and insert the new. | |
| AppendEdit(Operation::DELETE, b1, e1, edits); | |
| AppendEdit(Operation::INSERT, b2, e2, edits); | |
| } | |
| } | |
| private: | |
| TokenIter tokens1_begin_; | |
| TokenIter tokens2_begin_; | |
| }; // class Diff | |
| } // namespace diff_impl | |
| template <typename TokenIter> | |
| inline Edits GetTokenDiffs(TokenIter tokens1_begin, TokenIter tokens1_end, | |
| TokenIter tokens2_begin, TokenIter tokens2_end) { | |
| Edits token_edits; | |
| diff_impl::Diff<TokenIter>(tokens1_begin, tokens2_begin) | |
| .Generate(0, std::distance(tokens1_begin, tokens1_end), 0, | |
| std::distance(tokens2_begin, tokens2_end), &token_edits); | |
| return token_edits; // efficient: uses named return value optimization | |
| } | |
| } // namespace diff | |
| #endif // EDITSCRIPT_H_ |