See Release Notes
Long Term Support Release
1 <?php 2 3 namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef; 4 5 use PhpOffice\PhpSpreadsheet\Calculation\Functions; 6 use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; 7 use PhpOffice\PhpSpreadsheet\Shared\StringHelper; 8 9 class Unique 10 { 11 /** 12 * UNIQUE 13 * The UNIQUE function searches for value either from a one-row or one-column range or from an array. 14 * 15 * @param mixed $lookupVector The range of cells being searched 16 * @param mixed $byColumn Whether the uniqueness should be determined by row (the default) or by column 17 * @param mixed $exactlyOnce Whether the function should return only entries that occur just once in the list 18 * 19 * @return mixed The unique values from the search range 20 */ 21 public static function unique($lookupVector, $byColumn = false, $exactlyOnce = false) 22 { 23 if (!is_array($lookupVector)) { 24 // Scalars are always returned "as is" 25 return $lookupVector; 26 } 27 28 $byColumn = (bool) $byColumn; 29 $exactlyOnce = (bool) $exactlyOnce; 30 31 return ($byColumn === true) 32 ? self::uniqueByColumn($lookupVector, $exactlyOnce) 33 : self::uniqueByRow($lookupVector, $exactlyOnce); 34 } 35 36 /** 37 * @return mixed 38 */ 39 private static function uniqueByRow(array $lookupVector, bool $exactlyOnce) 40 { 41 // When not $byColumn, we count whole rows or values, not individual values 42 // so implode each row into a single string value 43 array_walk( 44 $lookupVector, 45 function (array &$value): void { 46 $value = implode(chr(0x00), $value); 47 } 48 ); 49 50 $result = self::countValuesCaseInsensitive($lookupVector); 51 52 if ($exactlyOnce === true) { 53 $result = self::exactlyOnceFilter($result); 54 } 55 56 if (count($result) === 0) { 57 return ExcelError::CALC(); 58 } 59 60 $result = array_keys($result); 61 62 // restore rows from their strings 63 array_walk( 64 $result, 65 function (string &$value): void { 66 $value = explode(chr(0x00), $value); 67 } 68 ); 69 70 return (count($result) === 1) ? array_pop($result) : $result; 71 } 72 73 /** 74 * @return mixed 75 */ 76 private static function uniqueByColumn(array $lookupVector, bool $exactlyOnce) 77 { 78 $flattenedLookupVector = Functions::flattenArray($lookupVector); 79 80 if (count($lookupVector, COUNT_RECURSIVE) > count($flattenedLookupVector, COUNT_RECURSIVE) + 1) { 81 // We're looking at a full column check (multiple rows) 82 $transpose = Matrix::transpose($lookupVector); 83 $result = self::uniqueByRow($transpose, $exactlyOnce); 84 85 return (is_array($result)) ? Matrix::transpose($result) : $result; 86 } 87 88 $result = self::countValuesCaseInsensitive($flattenedLookupVector); 89 90 if ($exactlyOnce === true) { 91 $result = self::exactlyOnceFilter($result); 92 } 93 94 if (count($result) === 0) { 95 return ExcelError::CALC(); 96 } 97 98 $result = array_keys($result); 99 100 return $result; 101 } 102 103 private static function countValuesCaseInsensitive(array $caseSensitiveLookupValues): array 104 { 105 $caseInsensitiveCounts = array_count_values( 106 array_map( 107 function (string $value) { 108 return StringHelper::strToUpper($value); 109 }, 110 $caseSensitiveLookupValues 111 ) 112 ); 113 114 $caseSensitiveCounts = []; 115 foreach ($caseInsensitiveCounts as $caseInsensitiveKey => $count) { 116 if (is_numeric($caseInsensitiveKey)) { 117 $caseSensitiveCounts[$caseInsensitiveKey] = $count; 118 } else { 119 foreach ($caseSensitiveLookupValues as $caseSensitiveValue) { 120 if ($caseInsensitiveKey === StringHelper::strToUpper($caseSensitiveValue)) { 121 $caseSensitiveCounts[$caseSensitiveValue] = $count; 122 123 break; 124 } 125 } 126 } 127 } 128 129 return $caseSensitiveCounts; 130 } 131 132 private static function exactlyOnceFilter(array $values): array 133 { 134 return array_filter( 135 $values, 136 function ($value) { 137 return $value === 1; 138 } 139 ); 140 } 141 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body