diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dc40f8a..d35f4649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Added -- Implementation of the ISREF() information function. +- Implementation of the 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. (i.e a value of "12,345.67" can be read as numeric `1235.67`, not simply as a string `"12,345.67"`, if the `castFormattedNumberToNumeric()` setting is enabled. diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 36adef01..b3988260 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -2583,7 +2583,7 @@ class Calculation ], 'UNIQUE' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [LookupRef\Unique::class, 'unique'], 'argumentCount' => '1+', ], 'UPPER' => [ diff --git a/src/PhpSpreadsheet/Calculation/Information/ExcelError.php b/src/PhpSpreadsheet/Calculation/Information/ExcelError.php index 98242eb6..e0f40848 100644 --- a/src/PhpSpreadsheet/Calculation/Information/ExcelError.php +++ b/src/PhpSpreadsheet/Calculation/Information/ExcelError.php @@ -127,10 +127,20 @@ class ExcelError /** * DIV0. * - * @return string #Not Yet Implemented + * @return string #DIV/0! */ public static function DIV0() { return self::$errorCodes['divisionbyzero']; } + + /** + * CALC. + * + * @return string #Not Yet Implemented + */ + public static function CALC() + { + return '#CALC!'; + } } diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/Unique.php b/src/PhpSpreadsheet/Calculation/LookupRef/Unique.php new file mode 100644 index 00000000..67b911aa --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/LookupRef/Unique.php @@ -0,0 +1,141 @@ + count($flattenedLookupVector, COUNT_RECURSIVE) + 1) { + // We're looking at a full column check (multiple rows) + $transpose = Matrix::transpose($lookupVector); + $result = self::uniqueByRow($transpose, $exactlyOnce); + + return (is_array($result)) ? Matrix::transpose($result) : $result; + } + + $result = self::countValuesCaseInsensitive($flattenedLookupVector); + + if ($exactlyOnce === true) { + $result = self::exactlyOnceFilter($result); + } + + if (count($result) === 0) { + return ExcelError::CALC(); + } + + $result = array_keys($result); + + return $result; + } + + private static function countValuesCaseInsensitive(array $caseSensitiveLookupValues): array + { + $caseInsensitiveCounts = array_count_values( + array_map( + function (string $value) { + return StringHelper::strToUpper($value); + }, + $caseSensitiveLookupValues + ) + ); + + $caseSensitiveCounts = []; + foreach ($caseInsensitiveCounts as $caseInsensitiveKey => $count) { + if (is_numeric($caseInsensitiveKey)) { + $caseSensitiveCounts[$caseInsensitiveKey] = $count; + } else { + foreach ($caseSensitiveLookupValues as $caseSensitiveValue) { + if ($caseInsensitiveKey === StringHelper::strToUpper($caseSensitiveValue)) { + $caseSensitiveCounts[$caseSensitiveValue] = $count; + + break; + } + } + } + } + + return $caseSensitiveCounts; + } + + private static function exactlyOnceFilter(array $values): array + { + return array_filter( + $values, + function ($value) { + return $value === 1; + } + ); + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/UniqueTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/UniqueTest.php new file mode 100644 index 00000000..c0a6f88e --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/UniqueTest.php @@ -0,0 +1,157 @@ +