Merge pull request #2707 from PHPOffice/LookupRef-Filter-Function
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
This commit is contained in:
commit
0ce56b1655
|
|
@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Implementation of the UNIQUE() Lookup/Reference (array) function
|
- Implementation of the FILTER() and UNIQUE() Lookup/Reference (array) function
|
||||||
- Implementation of the ISREF() Information function.
|
- Implementation of the ISREF() Information function.
|
||||||
- Added support for reading "formatted" numeric values from Csv files; although default behaviour of reading these values as strings is preserved.
|
- Added support for reading "formatted" numeric values from Csv files; although default behaviour of reading these values as strings is preserved.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1040,7 +1040,7 @@ class Calculation
|
||||||
],
|
],
|
||||||
'FILTER' => [
|
'FILTER' => [
|
||||||
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
||||||
'functionCall' => [Functions::class, 'DUMMY'],
|
'functionCall' => [LookupRef\Filter::class, 'filter'],
|
||||||
'argumentCount' => '2-3',
|
'argumentCount' => '2-3',
|
||||||
],
|
],
|
||||||
'FILTERXML' => [
|
'FILTERXML' => [
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||||
|
|
||||||
|
class Filter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param mixed $lookupArray
|
||||||
|
* @param mixed $matchArray
|
||||||
|
* @param mixed $ifEmpty
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function filter($lookupArray, $matchArray, $ifEmpty = null)
|
||||||
|
{
|
||||||
|
if (!is_array($matchArray)) {
|
||||||
|
return ExcelError::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = (Matrix::isColumnVector($matchArray))
|
||||||
|
? self::filterByRow($lookupArray, $matchArray)
|
||||||
|
: self::filterByColumn($lookupArray, $matchArray);
|
||||||
|
|
||||||
|
if (empty($result)) {
|
||||||
|
return $ifEmpty ?? ExcelError::CALC();
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function filterByRow(array $lookupArray, array $matchArray): array
|
||||||
|
{
|
||||||
|
$matchArray = array_values(array_column($matchArray, 0));
|
||||||
|
|
||||||
|
return array_filter(
|
||||||
|
array_values($lookupArray),
|
||||||
|
function ($index) use ($matchArray): bool {
|
||||||
|
return (bool) $matchArray[$index];
|
||||||
|
},
|
||||||
|
ARRAY_FILTER_USE_KEY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function filterByColumn(array $lookupArray, array $matchArray): array
|
||||||
|
{
|
||||||
|
$lookupArray = Matrix::transpose($lookupArray);
|
||||||
|
|
||||||
|
if (count($matchArray) === 1) {
|
||||||
|
$matchArray = array_pop($matchArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
array_walk(
|
||||||
|
$matchArray,
|
||||||
|
function (&$value): void {
|
||||||
|
$value = [$value];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = self::filterByRow($lookupArray, $matchArray);
|
||||||
|
|
||||||
|
return Matrix::transpose($result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,23 @@ class Matrix
|
||||||
{
|
{
|
||||||
use ArrayEnabled;
|
use ArrayEnabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function; NOT an implementation of any Excel Function.
|
||||||
|
*/
|
||||||
|
public static function isColumnVector(array $values): bool
|
||||||
|
{
|
||||||
|
return count($values, COUNT_RECURSIVE) === (count($values, COUNT_NORMAL) * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function; NOT an implementation of any Excel Function.
|
||||||
|
*/
|
||||||
|
public static function isRowVector(array $values): bool
|
||||||
|
{
|
||||||
|
return count($values, COUNT_RECURSIVE) > 1 &&
|
||||||
|
(count($values, COUNT_NORMAL) === 1 || count($values, COUNT_RECURSIVE) === count($values, COUNT_NORMAL));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TRANSPOSE.
|
* TRANSPOSE.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ class Unique
|
||||||
$exactlyOnce = (bool) $exactlyOnce;
|
$exactlyOnce = (bool) $exactlyOnce;
|
||||||
|
|
||||||
return ($byColumn === true)
|
return ($byColumn === true)
|
||||||
? self::uniqueByColumn($lookupVector, $exactlyOnce)
|
? self::uniqueByColumn($lookupVector, $exactlyOnce)
|
||||||
: self::uniqueByRow($lookupVector, $exactlyOnce);
|
: self::uniqueByRow($lookupVector, $exactlyOnce);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Filter;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class FilterTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testFilterByRow(): void
|
||||||
|
{
|
||||||
|
$criteria = [[true], [false], [false], [false], [true], [false], [false], [false], [false], [false], [false], [true], [false], [false], [false], [true]];
|
||||||
|
$expectedResult = [
|
||||||
|
['East', 'Tom', 'Apple', 6830],
|
||||||
|
['East', 'Fritz', 'Apple', 4394],
|
||||||
|
['South', 'Sal', 'Apple', 1310],
|
||||||
|
['South', 'Hector', 'Apple', 98144],
|
||||||
|
];
|
||||||
|
$result = Filter::filter($this->sampleDataForRow(), $criteria);
|
||||||
|
self::assertSame($expectedResult, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFilterByColumn(): void
|
||||||
|
{
|
||||||
|
$criteria = [[false, false, true, false, true, false, false, false, true, true]];
|
||||||
|
$expectedResult = [
|
||||||
|
['Betty', 'Charlotte', 'Oliver', 'Zoe'],
|
||||||
|
['B', 'B', 'B', 'B'],
|
||||||
|
[1, 2, 4, 8],
|
||||||
|
];
|
||||||
|
$result = Filter::filter($this->sampleDataForColumn(), $criteria);
|
||||||
|
self::assertSame($expectedResult, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFilterException(): void
|
||||||
|
{
|
||||||
|
$criteria = 'INVALID';
|
||||||
|
$result = Filter::filter($this->sampleDataForColumn(), $criteria);
|
||||||
|
self::assertSame(ExcelError::VALUE(), $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFilterEmpty(): void
|
||||||
|
{
|
||||||
|
$criteria = [[false], [false], [false]];
|
||||||
|
$expectedResult = ExcelError::CALC();
|
||||||
|
$result = Filter::filter([[1], [2], [3]], $criteria);
|
||||||
|
self::assertSame($expectedResult, $result);
|
||||||
|
|
||||||
|
$expectedResult = 'Invalid Data';
|
||||||
|
$result = Filter::filter([[1], [2], [3]], $criteria, $expectedResult);
|
||||||
|
self::assertSame($expectedResult, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function sampleDataForRow(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
['East', 'Tom', 'Apple', 6830],
|
||||||
|
['West', 'Fred', 'Grape', 5619],
|
||||||
|
['North', 'Amy', 'Pear', 4565],
|
||||||
|
['South', 'Sal', 'Banana', 5323],
|
||||||
|
['East', 'Fritz', 'Apple', 4394],
|
||||||
|
['West', 'Sravan', 'Grape', 7195],
|
||||||
|
['North', 'Xi', 'Pear', 5231],
|
||||||
|
['South', 'Hector', 'Banana', 2427],
|
||||||
|
['East', 'Tom', 'Banana', 4213],
|
||||||
|
['West', 'Fred', 'Pear', 3239],
|
||||||
|
['North', 'Amy', 'Grape', 6420],
|
||||||
|
['South', 'Sal', 'Apple', 1310],
|
||||||
|
['East', 'Fritz', 'Banana', 6274],
|
||||||
|
['West', 'Sravan', 'Pear', 4894],
|
||||||
|
['North', 'Xi', 'Grape', 7580],
|
||||||
|
['South', 'Hector', 'Apple', 98144],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function sampleDataForColumn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
['Aiden', 'Andrew', 'Betty', 'Caden', 'Charlotte', 'Emma', 'Isabella', 'Mason', 'Oliver', 'Zoe'],
|
||||||
|
['A', 'C', 'B', 'A', 'B', 'C', 'A', 'A', 'B', 'B'],
|
||||||
|
[0, 4, 1, 2, 2, 0, 2, 4, 4, 8],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Matrix;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class MatrixHelperFunctionsTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider columnVectorProvider
|
||||||
|
*/
|
||||||
|
public function testIsColumnVector(bool $expectedResult, array $array): void
|
||||||
|
{
|
||||||
|
$result = Matrix::isColumnVector($array);
|
||||||
|
self::assertSame($expectedResult, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider rowVectorProvider
|
||||||
|
*/
|
||||||
|
public function testIsRowVector(bool $expectedResult, array $array): void
|
||||||
|
{
|
||||||
|
$result = Matrix::isRowVector($array);
|
||||||
|
self::assertSame($expectedResult, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function columnVectorProvider(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
true,
|
||||||
|
[
|
||||||
|
[1], [2], [3],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
false,
|
||||||
|
[1, 2, 3],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
false,
|
||||||
|
[
|
||||||
|
[1, 2, 3],
|
||||||
|
[4, 5, 6],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rowVectorProvider(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
false,
|
||||||
|
[
|
||||||
|
[1], [2], [3],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
true,
|
||||||
|
[1, 2, 3],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
true,
|
||||||
|
[[1, 2, 3]],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
false,
|
||||||
|
[
|
||||||
|
[1, 2, 3],
|
||||||
|
[4, 5, 6],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue