Skip to content

Commit

Permalink
[FEATURE] Add an ArrayIntersector class
Browse files Browse the repository at this point in the history
Part of #708.
  • Loading branch information
JakeQZ committed Sep 14, 2019
1 parent cce60a8 commit 68b1243
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
54 changes: 54 additions & 0 deletions src/Emogrifier/HtmlProcessor/ArrayIntersector.php
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 tests/Unit/Emogrifier/HtmlProcessor/ArrayIntersectorTest.php
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);
}
}

0 comments on commit 68b1243

Please sign in to comment.