From f042ae51424303baf8d2457117e4d89b8a59b503 Mon Sep 17 00:00:00 2001 From: Samuel Akopyan Date: Fri, 28 Nov 2025 11:04:30 +0200 Subject: [PATCH 1/6] Simplify boolean and null handling in PrettyPrint. --- src/PrettyPrint.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PrettyPrint.php b/src/PrettyPrint.php index 58f7a56..94667b4 100644 --- a/src/PrettyPrint.php +++ b/src/PrettyPrint.php @@ -245,7 +245,7 @@ public function __invoke(...$args) } else { $parts[] = $this->formatForArray($arg); } - } elseif (!$containsArray) { + } else { if (is_bool($arg)) { $parts[] = $arg ? 'True' : 'False'; } elseif (is_null($arg)) { From 3ac6ca8b335a59dd520ed976d82c6fd1458df645 Mon Sep 17 00:00:00 2001 From: Samuel Akopyan Date: Fri, 28 Nov 2025 11:48:05 +0200 Subject: [PATCH 2/6] Extracted some functions to separate helper class --- CHANGELOG.md | 4 ++ src/Helper.php | 75 ++++++++++++++++++++++++++++++++++ src/PrettyPrint.php | 86 +++++---------------------------------- tests/PrettyPrintTest.php | 14 +------ 4 files changed, 90 insertions(+), 89 deletions(-) create mode 100644 src/Helper.php diff --git a/CHANGELOG.md b/CHANGELOG.md index fb6d773..80d8644 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +0.3.2 / + - Adjusted is1D and is2D to support int|float|string|bool|null + - Extracted some functions to separate helper class + 0.3.1 / 2025-11-27 - Adjust default formatting dimensions in PrettyPrint tensor methods - README.md example updates diff --git a/src/Helper.php b/src/Helper.php new file mode 100644 index 0000000..971209b --- /dev/null +++ b/src/Helper.php @@ -0,0 +1,75 @@ +is2D($args[1])) { + if (count($args) === 2 && !is_array($args[0]) && is_array($args[1]) && Helper::is2D($args[1])) { $label = is_bool($args[0]) ? ($args[0] ? 'True' : 'False') : (is_null($args[0]) ? 'None' : (string)$args[0]); $out = $this->format2DAligned($args[1]); echo $start . ($label . "\n" . $out) . $end; @@ -195,7 +195,7 @@ public function __invoke(...$args) } $allRows = true; for ($i = $startIndex; $i < count($args); $i++) { - if ($this->is1D($args[$i])) { + if (Helper::is1D($args[$i])) { $rows[] = $args[$i]; } else { $allRows = false; @@ -233,7 +233,7 @@ public function __invoke(...$args) (int)($fmt['tailCols'] ?? 5), (string)($fmt['label'] ?? 'tensor') ); - } elseif ($this->is2D($arg)) { + } elseif (Helper::is2D($arg)) { $parts[] = $this->format2DTorch( $arg, (int)($fmt['headRows'] ?? 5), @@ -251,7 +251,7 @@ public function __invoke(...$args) } elseif (is_null($arg)) { $parts[] = 'None'; } elseif (is_int($arg) || is_float($arg)) { - $parts[] = $this->formatNumber($arg); + $parts[] = Helper::formatNumber($arg, $this->precision); } else { $parts[] = (string)$arg; } @@ -264,74 +264,6 @@ public function __invoke(...$args) // ---- Private helpers ---- - /** - * Format a value as a number when possible. - * - * Integers are returned verbatim; floats are rendered with 4 decimal places; - * non-numeric values are cast to string. - * - * @param mixed $v - * @return string - */ - private function formatNumber($v): string - { - if (is_int($v)) { - return (string)$v; - } - if (is_float($v)) { - return number_format($v, $this->precision, '.', ''); - } - return (string)$v; - } - - /** - * Determine if the given value is a 1D array of numeric scalars. - * - * @param mixed $value - * @return bool True if $value is an array where every element is int or float. - */ - private function is1D($value): bool - { - if (!is_array($value)) { - return false; - } - foreach ($value as $cell) { - if (!is_int($cell) && !is_float($cell)) { - return false; - } - } - return true; - } - - /** - * Determine if the given value is a 2D numeric matrix. - * - * Accepts empty arrays as 2D. - * - * @param mixed $value - * @return bool True if $value is an array of arrays of int|float. - */ - private function is2D($value): bool - { - if (!is_array($value)) { - return false; - } - if (empty($value)) { - return true; - } - foreach ($value as $row) { - if (!is_array($row)) { - return false; - } - foreach ($row as $cell) { - if (!is_int($cell) && !is_float($cell) && !is_string($cell)) { - return false; - } - } - } - return true; - } - /** * Determine if the given value is a 3D tensor of numeric matrices. * @@ -406,7 +338,7 @@ private function format2DAligned(array $matrix): string if (array_key_exists($c, $row)) { $cell = $row[$c]; if (is_int($cell) || is_float($cell)) { - $s = $this->formatNumber($cell); + $s = Helper::formatNumber($cell, $this->precision); } elseif (is_string($cell)) { $s = "'" . addslashes($cell) . "'"; } elseif (is_bool($cell)) { @@ -501,7 +433,7 @@ private function format2DSummarized(array $matrix, int $headRows = 5, int $tailR } elseif (isset($matrix[$rIndex][$pos])) { $cell = $matrix[$rIndex][$pos]; if (is_int($cell) || is_float($cell)) { - $s = $this->formatNumber($cell); + $s = Helper::formatNumber($cell, $this->precision); } elseif (is_string($cell)) { $s = "'" . addslashes($cell) . "'"; } elseif (is_bool($cell)) { @@ -563,14 +495,14 @@ private function format2DSummarized(array $matrix, int $headRows = 5, int $tailR private function formatForArray($value): string { if (is_array($value)) { - if ($this->is2D($value)) { + if (Helper::is2D($value)) { return $this->format2DAligned($value); } $formattedItems = array_map(fn ($v) => $this->formatForArray($v), $value); return '[' . implode(', ', $formattedItems) . ']'; } if (is_int($value) || is_float($value)) { - return $this->formatNumber($value); + return Helper::formatNumber($value, $this->precision); } if (is_bool($value)) { return $value ? 'True' : 'False'; @@ -669,3 +601,5 @@ private function format2DTorch(array $matrix, int $headRows = 5, int $tailRows = return $s; } } + +// 672/605== diff --git a/tests/PrettyPrintTest.php b/tests/PrettyPrintTest.php index dceb496..27916d8 100644 --- a/tests/PrettyPrintTest.php +++ b/tests/PrettyPrintTest.php @@ -12,7 +12,7 @@ use PHPUnit\Framework\Attributes\CoversClass; #[CoversClass(PrettyPrint::class)] -#[Group('prettyprint')] +#[Group('PrettyPrint')] final class PrettyPrintTest extends TestCase { #[Test] @@ -336,16 +336,4 @@ public function multipleRowsWithLabel(): void $out = ob_get_clean(); self::assertSame("Label\n[[1, 2],\n [3, 4]]\n", $out); } - - #[Test] - #[TestDox('covers fallback branch in formatNumber for non-numeric values')] - public function formatNumberFallbackCoversString(): void - { - $pp = new PrettyPrint(); - $caller = \Closure::bind(function ($v) { - return $this->formatNumber($v); - }, $pp, PrettyPrint::class); - $result = $caller('abc'); - self::assertSame('abc', $result); - } } From 2ee38bb899ee4b079ba64918531e95cbe75e92c1 Mon Sep 17 00:00:00 2001 From: Samuel Akopyan Date: Fri, 28 Nov 2025 11:48:39 +0200 Subject: [PATCH 3/6] Created test class for Helper --- tests/HelperTest.php | 118 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 tests/HelperTest.php diff --git a/tests/HelperTest.php b/tests/HelperTest.php new file mode 100644 index 0000000..610c887 --- /dev/null +++ b/tests/HelperTest.php @@ -0,0 +1,118 @@ + [5, 2, '5'], + 'zero int' => [0, 4, '0'], + 'positive float with rounding' => [1.23456, 2, '1.23'], + 'positive float with padding' => [1.2, 4, '1.2000'], + 'negative float rounding' => [-3.14159, 3, '-3.142'], + 'string passthrough' => ['abc', 2, 'abc'], + 'null' => [null, 2, ''], + 'false casts to empty string' => [false, 2, ''], + 'true casts to 1' => [true, 2, '1'], + 'zero precision rounds halves up' => [2.5, 0, '3'], + 'high precision pads zeros' => [1.23, 6, '1.230000'], + 'small scientific notation' => [1e-6, 8, '0.00000100'], + 'large integer unchanged' => [123456789, 5, '123456789'], + ]; + } + + public static function defaultFormatNumberProvider(): array + { + return [ + 'int default precision ignored' => [5, '5'], + 'zero int default' => [0, '0'], + 'float rounds to 2 by default' => [1.23456, '1.23'], + 'float pads to 2 by default' => [1.2, '1.20'], + 'negative float default rounding' => [-3.14159, '-3.14'], + 'string passthrough default' => ['abc', 'abc'], + 'null to empty string default' => [null, ''], + 'true to 1 default' => [true, '1'], + 'false to empty string default' => [false, ''], + 'zero float default' => [0.0, '0.00'], + ]; + } + + #[Test] + #[TestDox('formatNumber formats ints, floats (with precision), and falls back to string casting')] + #[DataProvider('formatNumberProvider')] + public function testFormatNumber(mixed $value, int $precision, string $expected): void + { + self::assertSame($expected, Helper::formatNumber($value, $precision)); + } + + #[Test] + #[TestDox('formatNumber uses default precision when none is provided')] + #[DataProvider('defaultFormatNumberProvider')] + public function testFormatNumberDefaultPrecision(mixed $value, string $expected): void + { + self::assertSame($expected, Helper::formatNumber($value)); + } + + public static function is1DProvider(): array + { + return [ + 'ints only' => [[1, 2, 3], true], + 'floats only' => [[1.0, 2.5, -3.14], true], + 'ints and floats' => [[1, 2.0, -3.5], true], + 'empty array' => [[], true], + 'contains string' => [[1, '2', 3], true], + 'contains bool' => [[1, true, 3], true], + 'contains null' => [[1, null, 3], true], + 'nested array' => [[1, [2], 3], false], + 'non-array int' => [123, false], + 'non-array string' => ['abc', false], + ]; + } + + #[Test] + #[TestDox('is1D returns true only for 1D arrays of int|float|string|bool|null')] + #[DataProvider('is1DProvider')] + public function testIs1D(mixed $value, bool $expected): void + { + self::assertSame($expected, Helper::is1D($value)); + } + + public static function is2DProvider(): array + { + return [ + 'empty outer array' => [[], true], + 'single empty row' => [[[ ]], true], + 'multiple empty rows' => [[[ ], [ ]], true], + 'numeric matrix' => [[[1,2,3],[4,5,6]], true], + 'mixed scalars' => [[[1,'2',true,null],[3.5, 'x', false, 0]], true], + 'ragged rows allowed' => [[[1,2],[3,4,5]], true], + 'assoc rows allowed' => [[['a' => 1,'b' => 2], ['c' => 3]], true], + 'row contains array (nested) invalid' => [[[1,[2],3]], false], + 'row is not array invalid' => [[1,2,3], false], + 'outer non-array invalid' => [123, false], + ]; + } + + #[Test] + #[TestDox('is2D returns true only for arrays-of-arrays with scalar|null cells')] + #[DataProvider('is2DProvider')] + public function testIs2D(mixed $value, bool $expected): void + { + self::assertSame($expected, Helper::is2D($value)); + } +} From a1e059b7c6eb4729ee6711cd4bb08ab0846c0e50 Mon Sep 17 00:00:00 2001 From: Samuel Akopyan Date: Fri, 28 Nov 2025 11:51:01 +0200 Subject: [PATCH 4/6] Created test class for Helper --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80d8644..24470f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.3.2 / - Adjusted is1D and is2D to support int|float|string|bool|null - Extracted some functions to separate helper class + - Created test class for Helper 0.3.1 / 2025-11-27 - Adjust default formatting dimensions in PrettyPrint tensor methods From bcfe7ec2fd79c9532a8252b948cbde286b9ffbbe Mon Sep 17 00:00:00 2001 From: Samuel Akopyan Date: Fri, 28 Nov 2025 12:00:44 +0200 Subject: [PATCH 5/6] Code refactoring and cleanup --- src/PrettyPrint.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/PrettyPrint.php b/src/PrettyPrint.php index f141140..d44be07 100644 --- a/src/PrettyPrint.php +++ b/src/PrettyPrint.php @@ -210,16 +210,6 @@ public function __invoke(...$args) } } - // Default formatting - $parts = []; - $containsArray = false; - foreach ($args as $a) { - if (is_array($a)) { - $containsArray = true; - break; - } - } - foreach ($args as $arg) { if (is_array($arg)) { if ($this->is3D($arg)) { @@ -264,6 +254,7 @@ public function __invoke(...$args) // ---- Private helpers ---- + // TODO: >>>>>>>> /** * Determine if the given value is a 3D tensor of numeric matrices. * From d13e23e16c8926731eb0a51e4178d7ab171163a8 Mon Sep 17 00:00:00 2001 From: Samuel Akopyan Date: Fri, 28 Nov 2025 12:01:12 +0200 Subject: [PATCH 6/6] Code refactoring and cleanup --- src/PrettyPrint.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/PrettyPrint.php b/src/PrettyPrint.php index d44be07..7e555ab 100644 --- a/src/PrettyPrint.php +++ b/src/PrettyPrint.php @@ -210,6 +210,16 @@ public function __invoke(...$args) } } + // Default formatting + $parts = []; + $containsArray = false; + foreach ($args as $a) { + if (is_array($a)) { + $containsArray = true; + break; + } + } + foreach ($args as $arg) { if (is_array($arg)) { if ($this->is3D($arg)) {