From 9ed557e79848cb61268b0a21eb7753c280a13ee2 Mon Sep 17 00:00:00 2001 From: jorgecc Date: Thu, 26 Sep 2024 09:00:23 -0300 Subject: [PATCH] 2.6 --- README.md | 116 ++++++++----------- src/ArrayOne.php | 251 +++++++++++++++++++++++++++++------------- test/ArrayOneTest.php | 55 +++++++++ 3 files changed, 272 insertions(+), 150 deletions(-) diff --git a/README.md b/README.md index 1e4812f..fcabf58 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ What it does? Filter, order, renaming column, grouping, validating, amongst many * [Parameters:](#parameters-41) * [versions](#versions) * [License](#license) - + ## Basic examples @@ -729,79 +729,46 @@ ValidateOne ->validate('field'=>'fn:test') // or ->validate('field'=>[['fn','test']]) ->all(); ``` -| condition | description | example work | example fail | expression | -|-----------------------|----------------------------------------------------------------------------------------|----------------|--------------|------------| -| not**\** | negates any comparison, excepting nullable and custom functions. Example: "notint" | | | | -| "hello" | 20 | notint | | | -| nullable | the value **CAN** be a null. **If the value is null, then it ignores other | | | | -| validations** | null | | nullable | | -| f:**\** | It calls a **custom function** defined in the service class. See example | | | | -| "hello" | | f:test | | | -| contain like | if a text is contained in | | | | -| "helloworld" | "hello" | contain;world | | | -| alpha | if the value is alphabetic | | | | -| "hello" | "hello33" | alpha | | | -| alphanumunder | if the value is alphanumeric or under-case | | | | -| "hello_33" | "hello!33" | alphanumunder | | | -| alphanum | if the value is alphanumeric | | | | -| "hello33" | "hello!33" | alphanum | | | -| text | if the value is a text | | | | -| "hello" | true | text | | | -| regexp | if the value match a regular expression. You can't use comma in the regular | | | | -| expression. | "abc123" | "xyz123" | regexp; | | -| email | if the value is an email | | | | -| "aaa@bbb.com" | "aaa.bbb.com" | email | | | -| url | if the value is an url | | | | -| https://www.nic.cl | "aaaa" | url | | | -| domain | if the value is a domain | | | | -| www.nic.cl | "….." | domain | | | -| minlen | the value must have a minimum length | | | | -| "hello" | "h" | minlen;3 | | | -| maxlen | the value must have a maximum lenght | | | | -| "h" | "hello" | maxlen;3 | | | -| betweenlen | if the value has a size between | | | | -| "hello" | "h" | betweenlen;4,5 | | | -| exist | if the value exists | | | | -| "hi" | null | exist | | | -| missing | if the value not exist | | | | -| null | "hi" | missing | | | -| req,required | if the value is required | | | | -| "hi" | null | req,required | | | -| eq == | if the value is equals to | | | | -| 1 | 0 | eq;1 | | | -| ne != <> | if the value is not equals to | | | | -| 1 | 0 | ne;0 | | | -| null | The value **MUST** be null. It is different to **nullable** because **nullable** is a | | | | -| "**CAN**" | null | "hello" | null | | -| empty | if the value is empty | | | | -| "" | "hello" | empty | | | -| lt | if the value is less than | | | | -| 1 | 10 | lt;5 | | | -| lte/le | if the value is less or equals than | | | | -| 1 | 10 | lte;5 | | | -| gt | if the value is great than | | | | -| 10 | 1 | gt;5 | | | -| gte/ge | if the value is great or equals than | | | | -| 10 | 1 | gte;5 | | | -| between | if the value is between | | | | -| 5 | 0 | between;4,5 | | | -| true | if the value is true or 1 | | | | -| true | false | true | | | -| false | if the value is false, or 0 | | | | -| false | true | false | | | -| array | if the value is an array | | | | -| [1,2,3] | 1 | array | | | -| int | if the value is an integer | | | | -| 1 | "hello" | int | | | -| string | if the value is a string | | | | -| "hello" | true | string | | | -| float | if the value is a float | | | | -| 333.3 | "hello" | float | | | -| object | if the value is an object | | | | -| new stdClass() | 1 | object | | | -| in | the value must be in a list | | | | +| condition | description | example work | example fail | expression | +|------------------|-------------------------------------------------------------------------------------|-------------------------------------------|------------------------|----------------| +| not | negates any comparison, excepting nullable and custom functions. Example: "notint" | "hello" | 20 | notint | +| nullable | the value CAN be a null. **If the value is null, then it ignores other | validations** | null | nullable | +| f: | It calls a custom function defined in the service class. See example | "hello" | | f:test | +| contain like | if a text is contained in | "helloworld" | "hello" | contain;world | +| alpha | if the value is alphabetic | "hello" | "hello33" | alpha | +| alphanumunder | if the value is alphanumeric or under-case | "hello_33" | "hello!33" | alphanumunder | +| alphanum | if the value is alphanumeric | "hello33" | "hello!33" | alphanum | +| text | if the value is a text | "hello" | true | text | +| regexp | if the value match a regular expression. You can't use comma in the regular | expression. | "abc123" "xyz123" | regexp; | +| email | if the value is an email | ["aaa@bbb.com"](mailto:aaa@bbb.com) | "aaa.bbb.com" | email | +| url | if the value is an url | [https://www.nic.cl](https://www.nic.cl/) | "aaaa" | url | +| domain | if the value is a domain | [www.nic.cl](www.nic.cl) | "….." | domain | +| minlen | the value must have a minimum length | "hello" | "h" | minlen;3 | +| maxlen | the value must have a maximum lenght | "h" | "hello" | maxlen;3 | +| betweenlen | if the value has a size between | "hello" | "h" | betweenlen;4,5 | +| exist | if the value exists | "hi" | null | exist | +| missing | if the value not exist | null | "hi" | missing | +| req,required | if the value is required | "hi" | null | req,required | +| eq == | if the value is equals to | 1 | 0 | eq;1 | +| ne != <> | if the value is not equals to | 1 | 0 | ne;0 | +| null | The value MUST be null. It is different to nullable because nullable is a | "CAN" | null | null | +| empty | if the value is empty | "" | "hello" | empty | +| lt | if the value is less than | 1 | 10 | lt;5 | +| lte/le | if the value is less or equals than | 1 | 10 | lte;5 | +| gt | if the value is great than | 10 | 1 | gt;5 | +| gte/ge | if the value is great or equals than | 10 | 1 | gte;5 | +| between | if the value is between | 5 | 0 | between;4,5 | +| true | if the value is true or 1 | true | false | true | +| false | if the value is false, or 0 | false | true | false | +| array | if the value is an array | [1,2,3] | 1 | array | +| int | if the value is an integer | 1 | "hello" | int | +| string | if the value is a string | "hello" | true | string | +| float | if the value is a float | 333.3 | "hello" | float | +| object | if the value is an object | new stdClass() | 1 | object | +| in | the value must be in a list | | | | #### Parameters: + * **$comparisonTable** see table (array) * **$extraFieldError** if true and the current array has more values than comparison table, then it returns an error. (bool) @@ -809,6 +776,9 @@ ValidateOne ## versions +* 2.6 2024-09-26 + * [new] minRow() + * [new] maxRow() * 2.5.1 2024-09-20 * [new] keepCol() * [new] coolRename() @@ -826,7 +796,7 @@ ValidateOne * removeDuplicate(),group() now accepts multiples columns. * 2.00 2024-03-10 * nav() and currentArray() are removed from 2.0. The library was optimized and streamlined, and those functions are redundant. -**migration:** + **migration:** ```php // before: $r=ArrayOne::set($array)->nav('field')->...->all(); diff --git a/src/ArrayOne.php b/src/ArrayOne.php index 9f390ec..e1ebe27 100644 --- a/src/ArrayOne.php +++ b/src/ArrayOne.php @@ -10,6 +10,7 @@ use JsonException; use RuntimeException; + /** * Class ArrayOne * @see https://github.com/EFTEC/ArrayOne @@ -17,7 +18,7 @@ */ class ArrayOne implements ArrayAccess { - public const VERSION = "2.5"; + public const VERSION = "2.6"; public static string $error = ''; public string $separator = '|'; public array $errorStack = []; @@ -420,7 +421,7 @@ public function aggr(string $type, $colName = null, bool $getKey = false) if ($type === 'count') { return count($this->array); } - $firstKey = \array_key_first($this->array); + $firstKey = array_key_first($this->array); $max = $this->array[$firstKey][$colName] ?? null; $condition = false; $sum = 0; @@ -1076,8 +1077,8 @@ public function mask(array $arrayMask): ArrayOne * $max=$this->set($arr)->max('col'); // returns the max value of the column 'col' * $max=$this->set($arr)->max('col',true); // returns the key where is the max value * ``` - * @param mixed $colName the column - * @param bool $getKey + * @param mixed $colName the name of the column to find a value + * @param bool $getKey (default false), if true then it returns the key, if not returns the max value. * @return float|int|mixed|string|null * @throws Exception */ @@ -1093,8 +1094,8 @@ public function max($colName, bool $getKey = false) * $max=$this->set($arr)->min('col'); // returns the min value of the column 'col' * $max=$this->set($arr)->min('col',true); // returns the key where is the min value * ``` - * @param mixed $colName the column - * @param bool $getKey + * @param mixed $colName the name of the column to find a value + * @param bool $getKey (default false), if true then it returns the key, if not returns the min value. * @return float|int|mixed|string|null * @throws Exception */ @@ -1103,6 +1104,100 @@ public function min($colName, bool $getKey = false) return $this->aggr('min', $colName, $getKey); } + /** + * It finds the minimum value of a specific column and returns one or many values
+ * **Example:** + * ``` + * $max=$this->set($arr)->min('col'); // returns the min value of the column 'col' + * $max=$this->set($arr)->min('col',true); // returns the key where is the min value + * ``` + * @param mixed $colName the name of the column to find a value + * @param string $returnType =['first','last','random','all'][$i]
+ * * **first** returns the first min value
+ * * **last** returns the last min value
+ * * **random** if there are many min values, then it returns one randomly
+ * * **all** returns all the values that have the min value + * + * @param bool $getKey + * @return mixed + * @noinspection TypeUnsafeComparisonInspection + */ + public function minRow($colName, string $returnType = 'first', bool $getKey = false) + { + return $this->aggRow('min',$colName, $returnType, $getKey); + } + /** + * It finds the minimum value of a specific column and returns one or many values
+ * **Example:** + * ``` + * $max=$this->set($arr)->min('col'); // returns the min value of the column 'col' + * $max=$this->set($arr)->min('col',true); // returns the key where is the min value + * ``` + * @param mixed $colName the name of the column to find a value + * @param string $returnType =['first','last','random','all'][$i]
+ * * **first** returns the first min value
+ * * **last** returns the last min value
+ * * **random** if there are many min values, then it returns one randomly
+ * * **all** returns all the values that have the min value + * + * @param bool $getKey + * @return mixed + * @noinspection TypeUnsafeComparisonInspection + */ + public function maxRow($colName, string $returnType = 'first', bool $getKey = false) + { + return $this->aggRow('max',$colName, $returnType, $getKey); + } + + /** + * Used internally for minRow() and maxRow() + * @param string $comparison=['min','max'][$i] + * @param mixed $colName + * @param string $returnType + * @param bool $getKey + * @return array|int|mixed|string|null + * @noinspection TypeUnsafeComparisonInspection + */ + protected function aggRow(string $comparison,$colName, string $returnType, bool $getKey) { + $firstKey = array_key_first($this->array); + $compareValue = $this->array[$firstKey][$colName]; + $lastKey = $firstKey; + foreach ($this->array as $index => $row) { + $bool=$comparison==='min'?$row[$colName] <= $compareValue : $row[$colName] >= $compareValue; + if ($bool) { + if ($row[$colName] == $compareValue) { + $lastKey = $index; + } else { + $firstKey = $index; + } + $compareValue = $row[$colName]; + } + } + switch ($returnType) { + case 'first': + return $getKey ? $firstKey : $this->array[$firstKey]; + case 'last': + return $getKey ? $lastKey : $this->array[$lastKey]; + case 'random': + case 'all': + $indexes = []; + $values = []; + foreach ($this->array as $index => $row) { + if ($row[$colName] == $compareValue) { + $indexes[] = $index; + $values[] = $row; + } + } + if ($returnType === 'random') { + shuffle($indexes); + shuffle($values); + return $getKey ? ($indexes[0] ?? null) : ($values[0] ?? null); + } + return $getKey ? ($indexes) : ($values); // all + } + return null; + } + /** * It adds or modify a column. * **Example:** @@ -1564,77 +1659,79 @@ public function sum($colName) * ->validate('field'=>'fn:test') // or ->validate('field'=>[['fn','test']]) * ->all(); * ``` - * | condition | description | example work | example fail | expression | - * |-----------------------|----------------------------------------------------------------------------------------|----------------|--------------|------------| - * | not**\** | negates any comparison, excepting nullable and custom functions. Example: "notint" | | | | - * | "hello" | 20 | notint | | | - * | nullable | the value **CAN** be a null. **If the value is null, then it ignores other | | | | - * | validations** | null | | nullable | | - * | f:**\** | It calls a **custom function** defined in the service class. See example | | | | - * | "hello" | | f:test | | | - * | contain like | if a text is contained in | | | | - * | "helloworld" | "hello" | contain;world | | | - * | alpha | if the value is alphabetic | | | | - * | "hello" | "hello33" | alpha | | | - * | alphanumunder | if the value is alphanumeric or under-case | | | | - * | "hello_33" | "hello!33" | alphanumunder | | | - * | alphanum | if the value is alphanumeric | | | | - * | "hello33" | "hello!33" | alphanum | | | - * | text | if the value is a text | | | | - * | "hello" | true | text | | | - * | regexp | if the value match a regular expression. You can't use comma in the regular | | | | - * | expression. | "abc123" | "xyz123" | regexp; | | - * | email | if the value is an email | | | | - * | "aaa@bbb.com" | "aaa.bbb.com" | email | | | - * | url | if the value is an url | | | | - * | https://www.nic.cl | "aaaa" | url | | | - * | domain | if the value is a domain | | | | - * | www.nic.cl | "….." | domain | | | - * | minlen | the value must have a minimum length | | | | - * | "hello" | "h" | minlen;3 | | | - * | maxlen | the value must have a maximum lenght | | | | - * | "h" | "hello" | maxlen;3 | | | - * | betweenlen | if the value has a size between | | | | - * | "hello" | "h" | betweenlen;4,5 | | | - * | exist | if the value exists | | | | - * | "hi" | null | exist | | | - * | missing | if the value not exist | | | | - * | null | "hi" | missing | | | - * | req,required | if the value is required | | | | - * | "hi" | null | req,required | | | - * | eq == | if the value is equals to | | | | - * | 1 | 0 | eq;1 | | | - * | ne != <> | if the value is not equals to | | | | - * | 1 | 0 | ne;0 | | | - * | null | The value **MUST** be null. It is different to **nullable** because **nullable** is a | | | | - * | "**CAN**" | null | "hello" | null | | - * | empty | if the value is empty | | | | - * | "" | "hello" | empty | | | - * | lt | if the value is less than | | | | - * | 1 | 10 | lt;5 | | | - * | lte/le | if the value is less or equals than | | | | - * | 1 | 10 | lte;5 | | | - * | gt | if the value is great than | | | | - * | 10 | 1 | gt;5 | | | - * | gte/ge | if the value is great or equals than | | | | - * | 10 | 1 | gte;5 | | | - * | between | if the value is between | | | | - * | 5 | 0 | between;4,5 | | | - * | true | if the value is true or 1 | | | | - * | true | false | true | | | - * | false | if the value is false, or 0 | | | | - * | false | true | false | | | - * | array | if the value is an array | | | | - * | [1,2,3] | 1 | array | | | - * | int | if the value is an integer | | | | - * | 1 | "hello" | int | | | - * | string | if the value is a string | | | | - * | "hello" | true | string | | | - * | float | if the value is a float | | | | - * | 333.3 | "hello" | float | | | - * | object | if the value is an object | | | | - * | new stdClass() | 1 | object | | | - * | in | the value must be in a list + * | condition | description | + * example work | example fail | expression | + * |------------------|-------------------------------------------------------------------------------------|-------------------------------------------|------------------------|----------------| + * | not | negates any comparison, excepting nullable and custom functions. Example: "notint" | + * "hello" | 20 | notint | + * | nullable | the value CAN be a null. **If the value is null, then it ignores other | + * validations** | null | nullable | + * | f: | It calls a custom function defined in the service class. See example | + * "hello" | | f:test | + * | contain like | if a text is contained in | + * "helloworld" | "hello" | contain;world | + * | alpha | if the value is alphabetic | + * "hello" | "hello33" | alpha | + * | alphanumunder | if the value is alphanumeric or under-case | + * "hello_33" | "hello!33" | alphanumunder | + * | alphanum | if the value is alphanumeric | + * "hello33" | "hello!33" | alphanum | + * | text | if the value is a text | + * "hello" | true | text | + * | regexp | if the value match a regular expression. You can't use comma in the regular | + * expression. | "abc123" "xyz123" | regexp; | + * | email | if the value is an email | + * ["aaa@bbb.com"](mailto:aaa@bbb.com) | "aaa.bbb.com" | email | + * | url | if the value is an url | + * [https://www.nic.cl](https://www.nic.cl/) | "aaaa" | url | + * | domain | if the value is a domain | + * [www.nic.cl](www.nic.cl) | "….." | domain | + * | minlen | the value must have a minimum length | + * "hello" | "h" | minlen;3 | + * | maxlen | the value must have a maximum lenght | "h" + * | "hello" | maxlen;3 | + * | betweenlen | if the value has a size between | + * "hello" | "h" | betweenlen;4,5 | + * | exist | if the value exists | "hi" + * | null | exist | + * | missing | if the value not exist | null + * | "hi" | missing | + * | req,required | if the value is required | "hi" + * | null | req,required | + * | eq == | if the value is equals to | 1 + * | 0 | eq;1 | + * | ne != <> | if the value is not equals to | 1 + * | 0 | ne;0 | + * | null | The value MUST be null. It is different to nullable because nullable is a | "CAN" + * | null | null | + * | empty | if the value is empty | "" + * | "hello" | empty | + * | lt | if the value is less than | 1 + * | 10 | lt;5 | + * | lte/le | if the value is less or equals than | 1 + * | 10 | lte;5 | + * | gt | if the value is great than | 10 + * | 1 | gt;5 | + * | gte/ge | if the value is great or equals than | 10 + * | 1 | gte;5 | + * | between | if the value is between | 5 + * | 0 | between;4,5 | + * | true | if the value is true or 1 | true + * | false | true | + * | false | if the value is false, or 0 | false + * | true | false | + * | array | if the value is an array | + * [1,2,3] | 1 | array | + * | int | if the value is an integer | 1 + * | "hello" | int | + * | string | if the value is a string | + * "hello" | true | string | + * | float | if the value is a float | 333.3 + * | "hello" | float | + * | object | if the value is an object | new + * stdClass() | 1 | object | + * | in | the value must be in a list | + * | | | * @param array $comparisonTable see table * @param bool $extraFieldError if true and the current array has more values than comparison table, then * it returns an error. diff --git a/test/ArrayOneTest.php b/test/ArrayOneTest.php index 961a748..6a1b419 100644 --- a/test/ArrayOneTest.php +++ b/test/ArrayOneTest.php @@ -442,6 +442,61 @@ public function testCol(): void ] ], $array); } + public function testMinRow(): void + { + $products = [ + ['name' => 'cocacola1', 'price' => 500, 'quantity' => 200], + ['name' => 'cocacola2', 'price' => 600, 'quantity' => 300], + ['name' => 'cocacola3', 'price' => 300, 'quantity' => 200], + ['name' => 'cocacola4', 'price' => 700, 'quantity' => 400], + ['name' => 'cocacola5', 'price' => 300, 'quantity' => 200], + ['name' => 'cocacola6', 'price' => 700, 'quantity' => 400], + + ]; + $this->assertEquals( + ['name' => 'cocacola3', 'price' => 300, 'quantity' => 200] + , ArrayOne::set($products)->minRow('price')); + $this->assertEquals( + 2 + , ArrayOne::set($products)->minRow('price','first',true)); + $this->assertEquals( + ['name' => 'cocacola5', 'price' => 300, 'quantity' => 200] + , ArrayOne::set($products)->minRow('price','last')); + $this->assertEquals( + [['name' => 'cocacola3', 'price' => 300, 'quantity' => 200], + ['name' => 'cocacola5', 'price' => 300, 'quantity' => 200] + ] + , ArrayOne::set($products)->minRow('price','all')); + $this->assertIsArray(ArrayOne::set($products)->minRow('price','random')); + } + public function testMaxRow(): void + { + $products = [ + ['name' => 'cocacola1', 'price' => 500, 'quantity' => 200], + ['name' => 'cocacola2', 'price' => 600, 'quantity' => 300], + ['name' => 'cocacola3', 'price' => 300, 'quantity' => 200], + ['name' => 'cocacola4', 'price' => 700, 'quantity' => 400], + ['name' => 'cocacola5', 'price' => 300, 'quantity' => 200], + ['name' => 'cocacola6', 'price' => 700, 'quantity' => 400], + + ]; + $this->assertEquals( + ['name' => 'cocacola4', 'price' => 700, 'quantity' => 400] + , ArrayOne::set($products)->maxRow('price')); + $this->assertEquals( + 3 + , ArrayOne::set($products)->maxRow('price','first',true)); + $this->assertEquals( + ['name' => 'cocacola6', 'price' => 700, 'quantity' => 400] + , ArrayOne::set($products)->maxRow('price','last')); + $this->assertEquals( + [ + ['name' => 'cocacola4', 'price' => 700, 'quantity' => 400], + ['name' => 'cocacola6', 'price' => 700, 'quantity' => 400] + ] + , ArrayOne::set($products)->maxRow('price','all')); + $this->assertIsArray(ArrayOne::set($products)->maxRow('price','random')); + } /** * @throws Exception