-
Notifications
You must be signed in to change notification settings - Fork 154
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FEATURE] Add an ArrayIntersector class
Part of #708.
- Loading branch information
Showing
3 changed files
with
207 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<?php | ||
|
||
namespace Pelago\Emogrifier\HtmlProcessor; | ||
|
||
/** | ||
* When computing many array intersections using the same array, it is more efficient to use `array_flip()` first and | ||
* then `array_intersect_key()`, than `array_intersect()`. See the discussion at | ||
* {@link https://stackoverflow.com/questions/6329211/php-array-intersect-efficiency Stack Overflow} for more | ||
* information. | ||
* | ||
* Of course, this is only possible if the arrays contain integer or string values, and either don't contain duplicates, | ||
* or that fact that duplicates will be removed does not matter. | ||
* | ||
* This class takes care of the detail. | ||
* | ||
* @author Jake Hotson <[email protected]> | ||
*/ | ||
class ArrayIntersector | ||
{ | ||
/** | ||
* the array with which the object was constructed, with all its keys exchanged with their associated values | ||
* | ||
* @var (int|string)[] | ||
*/ | ||
private $invertedArray; | ||
|
||
/** | ||
* Constructs the object with the array that will be reused for many intersection computations. | ||
* | ||
* @param (int|string)[] $array | ||
*/ | ||
public function __construct(array $array) | ||
{ | ||
$this->invertedArray = \array_flip($array); | ||
} | ||
|
||
/** | ||
* Computes the intersection of `$array` and the array with which this object was constructed. | ||
* | ||
* @param (int|string)[] $array | ||
* | ||
* @return (int|string)[] Returns an array containing all of the values in `$array` whose values exist the array | ||
* with which this object was constructed. Note that keys are preserved, order is maintained, but | ||
* duplicates are removed. | ||
*/ | ||
public function intersectWith(array $array) | ||
{ | ||
$invertedArray = \array_flip($array); | ||
|
||
$invertedIntersection = \array_intersect_key($invertedArray, $this->invertedArray); | ||
|
||
return \array_flip($invertedIntersection); | ||
} | ||
} |
150 changes: 150 additions & 0 deletions
150
tests/Unit/Emogrifier/HtmlProcessor/ArrayIntersectorTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
<?php | ||
|
||
namespace Pelago\Emogrifier\Tests\Unit\HtmlProcessor; | ||
|
||
use Pelago\Emogrifier\HtmlProcessor\ArrayIntersector; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
/** | ||
* Test case. | ||
* | ||
* @author Jake Hotson <[email protected]> | ||
*/ | ||
class ArrayIntersectorTest extends TestCase | ||
{ | ||
/** | ||
* @return (int|string)[][][] | ||
*/ | ||
public function arraysDataProvider() | ||
{ | ||
return [ | ||
'empty arrays' => [ | ||
[], | ||
[], | ||
], | ||
'empty array & 1-value array' => [ | ||
[], | ||
[1], | ||
], | ||
'empty array & 2-value array' => [ | ||
[], | ||
[1, 2], | ||
], | ||
'1-value array & empty array' => [ | ||
[1], | ||
[], | ||
], | ||
'2-value array & empty array' => [ | ||
[1, 2], | ||
[], | ||
], | ||
'different 1-value arrays' => [ | ||
[1], | ||
[2], | ||
], | ||
'1-value array & 2-value array with different values' => [ | ||
[1], | ||
[2, 3], | ||
], | ||
'2-value array & 1-value array with different values' => [ | ||
[1, 2], | ||
[3], | ||
], | ||
'2-value arrays with different values' => [ | ||
[1, 2], | ||
[3, 4], | ||
], | ||
'identical 1-value arrays' => [ | ||
[1], | ||
[1], | ||
], | ||
'identical 2-value arrays' => [ | ||
[1, 2], | ||
[1, 2], | ||
], | ||
'2-value array & its reverse' => [ | ||
[1, 2], | ||
[2, 1], | ||
], | ||
'1-value array & 2-value superset' => [ | ||
[1], | ||
[1, 2], | ||
], | ||
'2-value array & 1-value subset' => [ | ||
[1, 2], | ||
[1], | ||
], | ||
'2-value arrays with 1 common value' => [ | ||
[1, 2], | ||
[2, 3], | ||
], | ||
'arrays with string values' => [ | ||
['one', 'two'], | ||
['two', 'three'], | ||
], | ||
'arrays with mixed values' => [ | ||
['one', 2], | ||
[2, 'three'], | ||
], | ||
'associative arrays' => [ | ||
['one' => 1, 'two' => 2], | ||
['foo' => 2, 'bar' => 3], | ||
], | ||
'associative arrays with string values' => [ | ||
['one' => 'one', 'two' => 'two'], | ||
['foo' => 'two', 'bar' => 'three'], | ||
], | ||
'mixed numeric/associative arrays' => [ | ||
['one' => 1, 2], | ||
['foo' => 2, 3], | ||
], | ||
// The following datasets focus more on preserving keys and order: | ||
'identical 2-value arrays: numeric array with sparse keys' => [ | ||
[2 => 1, 4 => 2], | ||
[2 => 1, 4 => 2], | ||
], | ||
'identical 2-value arrays: numeric array with keys in reverse order' => [ | ||
[1 => 1, 0 => 2], | ||
[1 => 1, 0 => 2], | ||
], | ||
'identical 2-value arrays: numeric array with elements in reverse numeric order' => [ | ||
[2, 1], | ||
[2, 1], | ||
], | ||
'identical 2-value arrays: associative array' => [ | ||
['one' => 1, 'two' => 2], | ||
['one' => 1, 'two' => 2], | ||
], | ||
'2-value associative arrays with same values but different keys' => [ | ||
['one' => 1, 'two' => 2], | ||
['foo' => 1, 'bar' => 2], | ||
], | ||
'3-value associative arrays with 2 common elements' => [ | ||
['one' => 1, 'two' => 2, 'three' => 3], | ||
['foo' => 1, 'bar' => 3, 'baz' => 5], | ||
], | ||
'3-value associative arrays with 2 common elements in reverse order' => [ | ||
['one' => 1, 'two' => 2, 'three' => 3], | ||
['foo' => 5, 'bar' => 3, 'baz' => 1], | ||
], | ||
]; | ||
} | ||
|
||
/** | ||
* @test | ||
* | ||
* @param (int|string)[] $array1 | ||
* @param (int|string)[] $array2 | ||
* | ||
* @dataProvider arraysDataProvider | ||
*/ | ||
public function computesIntersection(array $array1, array $array2) | ||
{ | ||
$expectedResult = \array_intersect($array1, $array2); | ||
|
||
$subject = new ArrayIntersector($array2); | ||
$result = $subject->intersectWith($array1); | ||
|
||
self::assertSame($expectedResult, $result); | ||
} | ||
} |