Skip to content

Commit f38057b

Browse files
Merge pull request sebastianbergmann#22 from gcrico/Improve-LCS-TimeEfficientImplementation
Improve LCS TimeEfficientImplementation
2 parents 3e22c89 + f3f8c09 commit f38057b

File tree

2 files changed

+225
-12
lines changed

2 files changed

+225
-12
lines changed

src/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,24 +66,26 @@ class TimeEfficientImplementation implements LongestCommonSubsequence
6666
public function calculate(array $from, array $to)
6767
{
6868
$common = array();
69-
$matrix = array();
7069
$fromLength = count($from);
7170
$toLength = count($to);
71+
$width = $fromLength + 1;
72+
$matrix = new \SplFixedArray($width * ($toLength + 1));
7273

7374
for ($i = 0; $i <= $fromLength; ++$i) {
74-
$matrix[$i][0] = 0;
75+
$matrix[$i] = 0;
7576
}
7677

7778
for ($j = 0; $j <= $toLength; ++$j) {
78-
$matrix[0][$j] = 0;
79+
$matrix[$j * $width] = 0;
7980
}
8081

8182
for ($i = 1; $i <= $fromLength; ++$i) {
8283
for ($j = 1; $j <= $toLength; ++$j) {
83-
$matrix[$i][$j] = max(
84-
$matrix[$i-1][$j],
85-
$matrix[$i][$j-1],
86-
$from[$i-1] === $to[$j-1] ? $matrix[$i-1][$j-1] + 1 : 0
84+
$o = ($j * $width) + $i;
85+
$matrix[$o] = max(
86+
$matrix[$o - 1],
87+
$matrix[$o - $width],
88+
$from[$i - 1] === $to[$j - 1] ? $matrix[$o - $width - 1] + 1 : 0
8789
);
8890
}
8991
}
@@ -93,16 +95,19 @@ public function calculate(array $from, array $to)
9395

9496
while ($i > 0 && $j > 0) {
9597
if ($from[$i-1] === $to[$j-1]) {
96-
array_unshift($common, $from[$i-1]);
98+
$common[] = $from[$i-1];
9799
--$i;
98100
--$j;
99-
} elseif ($matrix[$i][$j-1] > $matrix[$i-1][$j]) {
100-
--$j;
101101
} else {
102-
--$i;
102+
$o = ($j * $width) + $i;
103+
if ($matrix[$o - $width] > $matrix[$o - 1]) {
104+
--$j;
105+
} else {
106+
--$i;
107+
}
103108
}
104109
}
105110

106-
return $common;
111+
return array_reverse($common);
107112
}
108113
}
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
<?php
2+
/**
3+
* Diff
4+
*
5+
* Copyright (c) 2001-2014, Sebastian Bergmann <[email protected]>.
6+
* All rights reserved.
7+
*
8+
* Redistribution and use in source and binary forms, with or without
9+
* modification, are permitted provided that the following conditions
10+
* are met:
11+
*
12+
* * Redistributions of source code must retain the above copyright
13+
* notice, this list of conditions and the following disclaimer.
14+
*
15+
* * Redistributions in binary form must reproduce the above copyright
16+
* notice, this list of conditions and the following disclaimer in
17+
* the documentation and/or other materials provided with the
18+
* distribution.
19+
*
20+
* * Neither the name of Sebastian Bergmann nor the names of his
21+
* contributors may be used to endorse or promote products derived
22+
* from this software without specific prior written permission.
23+
*
24+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27+
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28+
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29+
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30+
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31+
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33+
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34+
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35+
* POSSIBILITY OF SUCH DAMAGE.
36+
*
37+
* @package Diff
38+
* @author Guillaume Crico <[email protected]>
39+
* @copyright 2001-2014 Sebastian Bergmann <[email protected]>
40+
* @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License
41+
* @link http://www.github.com/sebastianbergmann/diff
42+
*/
43+
44+
namespace SebastianBergmann\Diff\LCS;
45+
46+
use PHPUnit_Framework_TestCase;
47+
48+
/**
49+
* Some of these tests are volontary stressfull, in order to give some approximative benchmark hints.
50+
*/
51+
class TimeEfficientImplementationTest extends PHPUnit_Framework_TestCase
52+
{
53+
private $implementation;
54+
private $memory_limit;
55+
private $stress_sizes = array(1, 2, 3, 100, 500, 1000, 2000);
56+
57+
protected function setUp()
58+
{
59+
$this->memory_limit = ini_get('memory_limit');
60+
ini_set('memory_limit', '256M');
61+
62+
$this->implementation = new TimeEfficientImplementation;
63+
}
64+
65+
protected function tearDown()
66+
{
67+
ini_set('memory_limit', $this->memory_limit);
68+
}
69+
70+
public function testBothEmpty()
71+
{
72+
$from = array();
73+
$to = array();
74+
$common = $this->implementation->calculate($from, $to);
75+
76+
$this->assertEquals(array(), $common);
77+
}
78+
79+
public function testIsStrictComparison()
80+
{
81+
$from = array(
82+
false, 0, 0.0, '', null, array(),
83+
true, 1, 1.0, 'foo', array('foo', 'bar'), array('foo' => 'bar')
84+
);
85+
$to = $from;
86+
$common = $this->implementation->calculate($from, $to);
87+
88+
$this->assertEquals($from, $common);
89+
90+
$to = array(
91+
false, false, false, false, false, false,
92+
true, true, true, true, true, true
93+
);
94+
$expected = array(
95+
false,
96+
true,
97+
);
98+
$common = $this->implementation->calculate($from, $to);
99+
100+
$this->assertEquals($expected, $common);
101+
}
102+
103+
public function testEqualSequences()
104+
{
105+
foreach ($this->stress_sizes as $size) {
106+
$range = range(1, $size);
107+
$from = $range;
108+
$to = $range;
109+
$common = $this->implementation->calculate($from, $to);
110+
111+
$this->assertEquals($range, $common);
112+
}
113+
}
114+
115+
public function testDistinctSequences()
116+
{
117+
$from = array('A');
118+
$to = array('B');
119+
$common = $this->implementation->calculate($from, $to);
120+
$this->assertEquals(array(), $common);
121+
122+
$from = array('A', 'B', 'C');
123+
$to = array('D', 'E', 'F');
124+
$common = $this->implementation->calculate($from, $to);
125+
$this->assertEquals(array(), $common);
126+
127+
foreach ($this->stress_sizes as $size) {
128+
$from = range(1, $size);
129+
$to = range($size + 1, $size * 2);
130+
$common = $this->implementation->calculate($from, $to);
131+
$this->assertEquals(array(), $common);
132+
}
133+
}
134+
135+
public function testCommonSubsequence()
136+
{
137+
$from = array('A', 'C', 'E', 'F', 'G' );
138+
$to = array('A', 'B', 'D', 'E', 'H');
139+
$expected = array('A', 'E' );
140+
$common = $this->implementation->calculate($from, $to);
141+
$this->assertEquals($expected, $common);
142+
143+
$from = array('A', 'C', 'E', 'F', 'G' );
144+
$to = array( 'B', 'C', 'D', 'E', 'F', 'H');
145+
$expected = array('C', 'E', 'F' );
146+
$common = $this->implementation->calculate($from, $to);
147+
$this->assertEquals($expected, $common);
148+
149+
foreach ($this->stress_sizes as $size) {
150+
$from = $size < 2 ? array(1) : range(1, $size + 1, 2);
151+
$to = $size < 3 ? array(1) : range(1, $size + 1, 3);
152+
$expected = $size < 6 ? array(1) : range(1, $size + 1, 6);
153+
$common = $this->implementation->calculate($from, $to);
154+
155+
$this->assertEquals($expected, $common);
156+
}
157+
}
158+
159+
public function testSingleElementSubsequenceAtStart()
160+
{
161+
foreach ($this->stress_sizes as $size) {
162+
$from = range(1, $size);
163+
$to = array_slice($from, 0, 1);
164+
$common = $this->implementation->calculate($from, $to);
165+
166+
$this->assertEquals($to, $common);
167+
}
168+
}
169+
170+
public function testSingleElementSubsequenceAtMiddle()
171+
{
172+
foreach ($this->stress_sizes as $size) {
173+
$from = range(1, $size);
174+
$to = array_slice($from, (int) $size / 2, 1);
175+
$common = $this->implementation->calculate($from, $to);
176+
177+
$this->assertEquals($to, $common);
178+
}
179+
}
180+
181+
public function testSingleElementSubsequenceAtEnd()
182+
{
183+
foreach ($this->stress_sizes as $size) {
184+
$from = range(1, $size);
185+
$to = array_slice($from, $size - 1, 1);
186+
$common = $this->implementation->calculate($from, $to);
187+
188+
$this->assertEquals($to, $common);
189+
}
190+
}
191+
192+
public function testReversedSequences()
193+
{
194+
$from = array('A', 'B');
195+
$to = array('B', 'A');
196+
$expected = array('A');
197+
$common = $this->implementation->calculate($from, $to);
198+
$this->assertEquals($expected, $common);
199+
200+
foreach ($this->stress_sizes as $size) {
201+
$from = range(1, $size);
202+
$to = array_reverse($from);
203+
$common = $this->implementation->calculate($from, $to);
204+
205+
$this->assertEquals(array(1), $common);
206+
}
207+
}
208+
}

0 commit comments

Comments
 (0)