diff --git a/CHANGELOG.md b/CHANGELOG.md index cb3e5c69..6891c29a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ This project adheres to [Semantic Versioning](https://semver.org/). ## x.y.z ### Added +- Add an `ArrayIntersector` class + ([#708](https://github.com/MyIntervals/emogrifier/pull/708), + [#710](https://github.com/MyIntervals/emogrifier/pull/710)) - Add `CssInliner::getMatchingUninlinableSelectors` ([#380](https://github.com/MyIntervals/emogrifier/issues/380), [#707](https://github.com/MyIntervals/emogrifier/pull/707)) diff --git a/src/Emogrifier/HtmlProcessor/ArrayIntersector.php b/src/Emogrifier/HtmlProcessor/ArrayIntersector.php new file mode 100644 index 00000000..3bc3e7df --- /dev/null +++ b/src/Emogrifier/HtmlProcessor/ArrayIntersector.php @@ -0,0 +1,54 @@ + + */ +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); + } +} diff --git a/tests/Unit/Emogrifier/HtmlProcessor/ArrayIntersectorTest.php b/tests/Unit/Emogrifier/HtmlProcessor/ArrayIntersectorTest.php new file mode 100644 index 00000000..7efec7ee --- /dev/null +++ b/tests/Unit/Emogrifier/HtmlProcessor/ArrayIntersectorTest.php @@ -0,0 +1,150 @@ + + */ +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); + } +}