Skip to content

Commit f3bb61f

Browse files
author
MarkBaker
committed
Initial work implementing the FILTER() Lookup/Reference function
Tighten up on vector formats; and provide a couple of helper methods for testing row/column vectors
1 parent 1642ee4 commit f3bb61f

File tree

7 files changed

+248
-4
lines changed

7 files changed

+248
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
99

1010
### Added
1111

12-
- Implementation of the UNIQUE() Lookup/Reference (array) function
12+
- Implementation of the FILTER() and UNIQUE() Lookup/Reference (array) function
1313
- Implementation of the ISREF() Information function.
1414
- Added support for reading "formatted" numeric values from Csv files; although default behaviour of reading these values as strings is preserved.
1515

src/PhpSpreadsheet/Calculation/Calculation.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1040,7 +1040,7 @@ class Calculation
10401040
],
10411041
'FILTER' => [
10421042
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1043-
'functionCall' => [Functions::class, 'DUMMY'],
1043+
'functionCall' => [LookupRef\Filter::class, 'filter'],
10441044
'argumentCount' => '2-3',
10451045
],
10461046
'FILTERXML' => [
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
4+
5+
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
6+
7+
class Filter
8+
{
9+
/**
10+
* @param mixed $lookupArray
11+
* @param mixed $matchArray
12+
* @param mixed $ifEmpty
13+
*
14+
* @return mixed
15+
*/
16+
public static function filter($lookupArray, $matchArray, $ifEmpty = null)
17+
{
18+
if (!is_array($matchArray)) {
19+
return ExcelError::VALUE();
20+
}
21+
22+
$result = (Matrix::isColumnVector($matchArray))
23+
? self::filterByRow($lookupArray, $matchArray)
24+
: self::filterByColumn($lookupArray, $matchArray);
25+
26+
if (empty($result)) {
27+
return $ifEmpty ?? ExcelError::CALC();
28+
}
29+
30+
return array_values($result);
31+
}
32+
33+
private static function filterByRow(array $lookupArray, array $matchArray): array
34+
{
35+
$matchArray = array_values(array_column($matchArray, 0));
36+
37+
return array_filter(
38+
array_values($lookupArray),
39+
function ($index) use ($matchArray): bool {
40+
return (bool) $matchArray[$index];
41+
},
42+
ARRAY_FILTER_USE_KEY
43+
);
44+
}
45+
46+
private static function filterByColumn(array $lookupArray, array $matchArray): array
47+
{
48+
$lookupArray = Matrix::transpose($lookupArray);
49+
50+
if (count($matchArray) === 1) {
51+
$matchArray = array_pop($matchArray);
52+
}
53+
54+
array_walk(
55+
$matchArray,
56+
function (&$value): void {
57+
$value = [$value];
58+
}
59+
);
60+
61+
$result = self::filterByRow($lookupArray, $matchArray);
62+
63+
return Matrix::transpose($result);
64+
}
65+
}

src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,23 @@ class Matrix
1010
{
1111
use ArrayEnabled;
1212

13+
/**
14+
* Helper function; NOT an implementation of any Excel Function.
15+
*/
16+
public static function isColumnVector(array $values): bool
17+
{
18+
return count($values, COUNT_RECURSIVE) === (count($values, COUNT_NORMAL) * 2);
19+
}
20+
21+
/**
22+
* Helper function; NOT an implementation of any Excel Function.
23+
*/
24+
public static function isRowVector(array $values): bool
25+
{
26+
return count($values, COUNT_RECURSIVE) > 1 &&
27+
(count($values, COUNT_NORMAL) === 1 || count($values, COUNT_RECURSIVE) === count($values, COUNT_NORMAL));
28+
}
29+
1330
/**
1431
* TRANSPOSE.
1532
*

src/PhpSpreadsheet/Calculation/LookupRef/Unique.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ public static function unique($lookupVector, $byColumn = false, $exactlyOnce = f
2929
$exactlyOnce = (bool) $exactlyOnce;
3030

3131
return ($byColumn === true)
32-
? self::uniqueByColumn($lookupVector, $exactlyOnce)
33-
: self::uniqueByRow($lookupVector, $exactlyOnce);
32+
? self::uniqueByColumn($lookupVector, $exactlyOnce)
33+
: self::uniqueByRow($lookupVector, $exactlyOnce);
3434
}
3535

3636
/**
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
4+
5+
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
6+
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Filter;
7+
use PHPUnit\Framework\TestCase;
8+
9+
class FilterTest extends TestCase
10+
{
11+
public function testFilterByRow(): void
12+
{
13+
$criteria = [[true], [false], [false], [false], [true], [false], [false], [false], [false], [false], [false], [true], [false], [false], [false], [true]];
14+
$expectedResult = [
15+
['East', 'Tom', 'Apple', 6830],
16+
['East', 'Fritz', 'Apple', 4394],
17+
['South', 'Sal', 'Apple', 1310],
18+
['South', 'Hector', 'Apple', 98144],
19+
];
20+
$result = Filter::filter($this->sampleDataForRow(), $criteria);
21+
self::assertSame($expectedResult, $result);
22+
}
23+
24+
public function testFilterByColumn(): void
25+
{
26+
$criteria = [[false, false, true, false, true, false, false, false, true, true]];
27+
$expectedResult = [
28+
['Betty', 'Charlotte', 'Oliver', 'Zoe'],
29+
['B', 'B', 'B', 'B'],
30+
[1, 2, 4, 8],
31+
];
32+
$result = Filter::filter($this->sampleDataForColumn(), $criteria);
33+
self::assertSame($expectedResult, $result);
34+
}
35+
36+
public function testFilterException(): void
37+
{
38+
$criteria = 'INVALID';
39+
$result = Filter::filter($this->sampleDataForColumn(), $criteria);
40+
self::assertSame(ExcelError::VALUE(), $result);
41+
}
42+
43+
public function testFilterEmpty(): void
44+
{
45+
$criteria = [[false], [false], [false]];
46+
$expectedResult = ExcelError::CALC();
47+
$result = Filter::filter([[1], [2], [3]], $criteria);
48+
self::assertSame($expectedResult, $result);
49+
50+
$expectedResult = 'Invalid Data';
51+
$result = Filter::filter([[1], [2], [3]], $criteria, $expectedResult);
52+
self::assertSame($expectedResult, $result);
53+
}
54+
55+
protected function sampleDataForRow(): array
56+
{
57+
return [
58+
['East', 'Tom', 'Apple', 6830],
59+
['West', 'Fred', 'Grape', 5619],
60+
['North', 'Amy', 'Pear', 4565],
61+
['South', 'Sal', 'Banana', 5323],
62+
['East', 'Fritz', 'Apple', 4394],
63+
['West', 'Sravan', 'Grape', 7195],
64+
['North', 'Xi', 'Pear', 5231],
65+
['South', 'Hector', 'Banana', 2427],
66+
['East', 'Tom', 'Banana', 4213],
67+
['West', 'Fred', 'Pear', 3239],
68+
['North', 'Amy', 'Grape', 6420],
69+
['South', 'Sal', 'Apple', 1310],
70+
['East', 'Fritz', 'Banana', 6274],
71+
['West', 'Sravan', 'Pear', 4894],
72+
['North', 'Xi', 'Grape', 7580],
73+
['South', 'Hector', 'Apple', 98144],
74+
];
75+
}
76+
77+
protected function sampleDataForColumn(): array
78+
{
79+
return [
80+
['Aiden', 'Andrew', 'Betty', 'Caden', 'Charlotte', 'Emma', 'Isabella', 'Mason', 'Oliver', 'Zoe'],
81+
['A', 'C', 'B', 'A', 'B', 'C', 'A', 'A', 'B', 'B'],
82+
[0, 4, 1, 2, 2, 0, 2, 4, 4, 8],
83+
];
84+
}
85+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
4+
5+
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Matrix;
6+
use PHPUnit\Framework\TestCase;
7+
8+
class MatrixHelperFunctionsTest extends TestCase
9+
{
10+
/**
11+
* @dataProvider columnVectorProvider
12+
*/
13+
public function testIsColumnVector(bool $expectedResult, array $array): void
14+
{
15+
$result = Matrix::isColumnVector($array);
16+
self::assertSame($expectedResult, $result);
17+
}
18+
19+
/**
20+
* @dataProvider rowVectorProvider
21+
*/
22+
public function testIsRowVector(bool $expectedResult, array $array): void
23+
{
24+
$result = Matrix::isRowVector($array);
25+
self::assertSame($expectedResult, $result);
26+
}
27+
28+
public function columnVectorProvider(): array
29+
{
30+
return [
31+
[
32+
true,
33+
[
34+
[1], [2], [3],
35+
],
36+
],
37+
[
38+
false,
39+
[1, 2, 3],
40+
],
41+
[
42+
false,
43+
[
44+
[1, 2, 3],
45+
[4, 5, 6],
46+
],
47+
],
48+
];
49+
}
50+
51+
public function rowVectorProvider(): array
52+
{
53+
return [
54+
[
55+
false,
56+
[
57+
[1], [2], [3],
58+
],
59+
],
60+
[
61+
true,
62+
[1, 2, 3],
63+
],
64+
[
65+
true,
66+
[[1, 2, 3]],
67+
],
68+
[
69+
false,
70+
[
71+
[1, 2, 3],
72+
[4, 5, 6],
73+
],
74+
],
75+
];
76+
}
77+
}

0 commit comments

Comments
 (0)