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:
MarkBaker 2022-03-20 14:03:36 +01:00
parent 1642ee4eb1
commit f3bb61f3e4
7 changed files with 248 additions and 4 deletions

View File

@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
### 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.
- Added support for reading "formatted" numeric values from Csv files; although default behaviour of reading these values as strings is preserved.

View File

@ -1040,7 +1040,7 @@ class Calculation
],
'FILTER' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [Functions::class, 'DUMMY'],
'functionCall' => [LookupRef\Filter::class, 'filter'],
'argumentCount' => '2-3',
],
'FILTERXML' => [

View File

@ -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);
}
}

View File

@ -10,6 +10,23 @@ class Matrix
{
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.
*

View File

@ -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],
];
}
}

View File

@ -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],
],
],
];
}
}