OldDifferencer::findDifferences()   F
last analyzed

Complexity

Conditions 22
Paths 192

Size

Total Lines 99
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 44
CRAP Score 22

Importance

Changes 0
Metric Value
cc 22
eloc 46
nc 192
nop 2
dl 0
loc 99
ccs 44
cts 44
cp 1
crap 22
rs 3.4
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * (c) Steve Nebes <[email protected]>
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
declare(strict_types=1);
10
11
namespace SN\DaisyDiff\RangeDifferencer;
12
13
/**
14
 * The algorithm used is an objectified version of one described in: A File Comparison Program, by Webb Miller and
15
 * Eugene W. Myers, Software Practice and Experience, Vol. 15, Nov. 1985.
16
 */
17
final class OldDifferencer
18
{
19
    /**
20
     * Prevent class instantiation.
21
     *
22
     * @codeCoverageIgnore
23
     */
24
    private function __construct()
25
    {
26
    }
27
28
    /**
29
     * Finds the differences between two RangeComparatorInterfaces. The differences are returned as an array of
30
     * RangeDifferences. If no differences are detected an empty array is returned.
31
     *
32
     * @param RangeComparatorInterface $left
33
     * @param RangeComparatorInterface $right
34
     * @return RangeDifference[]
35
     */
36 66
    public static function findDifferences(RangeComparatorInterface $left, RangeComparatorInterface $right): array
37
    {
38
        // Assert that both RangeComparatorInterface are of the same class.
39 66
        \assert(\get_class($right) === \get_class($left));
40
41 66
        $rightSize = $right->getRangeCount();
42 66
        $leftSize = $left->getRangeCount();
43
44
        // Differences matrix:
45
        // Only the last d of each diagonal is stored, i.e., $lastDiagonal[$k] = row of d
46 66
        $diagLen = (int) (2 * \max($rightSize, $leftSize));
47 66
        $maxDiagonal = $diagLen;
48
49
        /** @var int[] The row containing the last d */
50 66
        $lastDiagonal = \array_fill(0, $diagLen + 1, 0);
51
52
        // On diagonal $k ($lastDiagonal[$k] = $row
53 66
        $origin = (int) ($diagLen / 2);
54
55
        // Script corresponding to $d[$k]
56
        /** @var LinkedRangeDifference[] $script */
57 66
        $script = \array_fill(0, $diagLen + 1, null);
58 66
        $row = 0;
59
60
        // Find common prefix.
61 66
        for (;$row < $rightSize && $row < $leftSize && self::rangesEqual($right, $row, $left, $row);) {
62 58
            $row++;
63
        }
64
65 66
        $lastDiagonal[$origin] = $row;
66 66
        $script[$origin] = null;
67
68 66
        $lower = ($row === $rightSize) ? $origin + 1 : $origin - 1;
69 66
        $upper = ($row === $leftSize) ? $origin - 1 : $origin + 1;
70
71 66
        if ($lower > $upper) {
72 42
            return [];
73
        }
74
75
        // For each value of the edit distance.
76 63
        for ($d = 1; $d <= $maxDiagonal; $d++) {
77
            // $d is the current edit distance.
78
79 63
            if ($right->skipRangeComparison($d, $maxDiagonal, $left)) {
80
                // This condition always returns false, so the following code is never executed.
81
                // It should be something we already found.
82
                return []; // @codeCoverageIgnore
83
            }
84
85
            // For each relevant diagonal (-d, -d+2, ... d-2, d)
86 63
            for ($k = $lower; $k <= $upper; $k += 2) {
87
                // $k is the current diagonal.
88 63
                if ($k === $origin - $d || $k !== $origin + $d && $lastDiagonal[$k + 1] >= $lastDiagonal[$k - 1]) {
89
                    // Move down.
90 59
                    $row = $lastDiagonal[$k + 1] + 1;
91 59
                    $edit = new LinkedRangeDifference($script[$k + 1], LinkedRangeDifference::DELETE);
92
                } else {
93
                    // Move right.
94 54
                    $row = $lastDiagonal[$k - 1];
95 54
                    $edit = new LinkedRangeDifference($script[$k - 1], LinkedRangeDifference::INSERT);
96
                }
97
98 63
                $col = $row + $k - $origin;
99
100 63
                $edit->setRightStart($row);
101 63
                $edit->setLeftStart($col);
102
103 63
                \assert($k >= 0 && $k <= $maxDiagonal);
104 63
                $script[$k] = $edit;
105
106
                // Slide down the diagonal as far as possible.
107 63
                while ($row < $rightSize && $col < $leftSize && self::rangesEqual($right, $row, $left, $col)) {
108 38
                    $row++;
109 38
                    $col++;
110
                }
111
112
                // Unreasonable value for diagonal index.
113 63
                \assert($k >= 0 && $k <= $maxDiagonal);
114 63
                $lastDiagonal[$k] = $row;
115
116 63
                if ($row === $rightSize && $col === $leftSize) {
117 63
                    return self::createRangeDifferences($script[$k]);
118
                }
119
120 54
                if ($row === $rightSize) {
121 30
                    $lower = $k + 2;
122
                }
123
124 54
                if ($col === $leftSize) {
125 25
                    $upper = $k - 2;
126
                }
127
            }
128
129 48
            $lower--;
130 48
            $upper++;
131
        }
132
133
        // Too many differences.
134
        throw new \RuntimeException(); // @codeCoverageIgnore
135
    }
136
137
    /**
138
     * Tests if two ranges are equal
139
     *
140
     * @param RangeComparatorInterface $a
141
     * @param int                      $ai
142
     * @param RangeComparatorInterface $b
143
     * @param int                      $bi
144
     * @return bool
145
     */
146 62
    private static function rangesEqual(
147
        RangeComparatorInterface $a,
148
        int $ai,
149
        RangeComparatorInterface $b,
150
        int $bi
151
    ): bool {
152 62
        return $a->rangesEqual($ai, $b, $bi);
153
    }
154
155
    /**
156
     * Creates a Vector of DifferencesRanges out of the LinkedRangeDifference. It coalesces adjacent changes. In
157
     * addition, indices are changed such that the ranges are 1) open, i.e, the end of the range is not included, and 2)
158
     * are zero based.
159
     *
160
     * @param LinkedRangeDifference $start
161
     * @return RangeDifference[]
162
     */
163 63
    private static function createRangeDifferences(LinkedRangeDifference $start): array
164
    {
165 63
        $ep = self::reverseDifferences($start);
166 63
        $result = [];
167
168 63
        while (null !== $ep) {
169 63
            $es = new RangeDifference(RangeDifference::CHANGE);
170
171 63
            if ($ep->isInsert()) {
172 22
                $es->setRightStart($ep->getRightStart() + 1);
173 22
                $es->setLeftStart($ep->getLeftStart());
174 22
                $b = $ep;
175
176
                do {
177 22
                    $ep = $ep->getNext();
178 22
                    $es->setLeftLength($es->getLeftLength() + 1);
179 22
                } while (null !== $ep && $ep->isInsert() && $ep->getRightStart() === $b->getRightStart());
180
            } else {
181 50
                $es->setRightStart($ep->getRightStart());
182 50
                $es->setLeftStart($ep->getLeftStart());
183
184
                // Deleted lines.
185
                do {
186 50
                    $a = $ep;
187 50
                    $ep = $ep->getNext();
188 50
                    $es->setRightLength($es->getRightLength() + 1);
189 50
                } while (null !== $ep && $ep->isDelete() && $ep->getRightStart() === $a->getRightStart() + 1);
190
191 50
                $change = (null !== $ep && $ep->isInsert() && $ep->getRightStart() === $a->getRightStart());
192
193 50
                if ($change) {
194 27
                    $b = $ep;
195
196
                    // Replacement lines.
197
                    do {
198 27
                        $ep = $ep->getNext();
199 27
                        $es->setLeftLength($es->getLeftLength() + 1);
200 27
                    } while (null !== $ep && $ep->isInsert() && $ep->getRightStart() === $b->getRightStart());
201
                } else {
202 31
                    $es->setLeftLength(0);
203
                }
204
205
                // Meaning of range changes from "insert after", to "replace with".
206 50
                $es->setLeftStart($es->getLeftStart() + 1);
207
            }
208
209
            // The script commands are 1 based, subtract one to make them zero based.
210 63
            $es->setRightStart($es->getRightStart() - 1);
211 63
            $es->setLeftStart($es->getLeftStart() - 1);
212
213 63
            $result[] = $es;
214
        }
215
216 63
        return $result;
217
    }
218
219
    /**
220
     * Reverses the range differences.
221
     *
222
     * @param LinkedRangeDifference $start
223
     * @return LinkedRangeDifference
224
     */
225 63
    private static function reverseDifferences(LinkedRangeDifference $start): LinkedRangeDifference
226
    {
227 63
        $ahead = $start;
228 63
        $ep = null;
229
230 63
        while (null !== $ahead) {
231 63
            $behind = $ep;
232 63
            $ep = $ahead;
233 63
            $ahead = $ahead->getNext();
234 63
            $ep->setNext($behind);
235
        }
236
237 63
        return $ep;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ep could return the type null which is incompatible with the type-hinted return SN\DaisyDiff\RangeDiffer...r\LinkedRangeDifference. Consider adding an additional type-check to rule them out.
Loading history...
238
    }
239
}
240