From ec7953eb969905af0b3017b4377308b403eae98b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Tue, 2 Mar 2021 20:54:26 +0100 Subject: [PATCH 01/20] NumberValue and Numberable --- spec/PlainNumberSpec.php | 285 +++++++++++++++++++++++++++++++++ src/NumberValue.php | 51 ++++++ src/Numberable.php | 11 ++ src/Numberable/Absolute.php | 22 +++ src/Numberable/Ceil.php | 22 +++ src/Numberable/Divide.php | 32 ++++ src/Numberable/Floor.php | 22 +++ src/Numberable/Formula.php | 27 ++++ src/Numberable/JustFloat.php | 20 +++ src/Numberable/JustInteger.php | 20 +++ src/Numberable/Math.php | 62 +++++++ src/Numberable/Multiply.php | 32 ++++ src/Numberable/Round.php | 27 ++++ src/Numberable/Subtract.php | 33 ++++ src/Numberable/Sum.php | 33 ++++ src/PlainNumber.php | 113 +++++++++++++ 16 files changed, 812 insertions(+) create mode 100644 spec/PlainNumberSpec.php create mode 100644 src/NumberValue.php create mode 100644 src/Numberable.php create mode 100644 src/Numberable/Absolute.php create mode 100644 src/Numberable/Ceil.php create mode 100644 src/Numberable/Divide.php create mode 100644 src/Numberable/Floor.php create mode 100644 src/Numberable/Formula.php create mode 100644 src/Numberable/JustFloat.php create mode 100644 src/Numberable/JustInteger.php create mode 100644 src/Numberable/Math.php create mode 100644 src/Numberable/Multiply.php create mode 100644 src/Numberable/Round.php create mode 100644 src/Numberable/Subtract.php create mode 100644 src/Numberable/Sum.php create mode 100644 src/PlainNumber.php diff --git a/spec/PlainNumberSpec.php b/spec/PlainNumberSpec.php new file mode 100644 index 0000000..08bd708 --- /dev/null +++ b/spec/PlainNumberSpec.php @@ -0,0 +1,285 @@ +beConstructedWith(new JustInteger(123)); + + $this->toNumber()->shouldBe(123); + $this->toInteger()->shouldBe(123); + $this->toFloat()->shouldBe(123.0); + } + + function it_can_be_float() + { + $this->beConstructedWith(new JustFloat(123.66)); + + $this->toNumber()->shouldBe(123.66); + $this->toInteger()->shouldBe(123); + $this->toFloat()->shouldBe(123.66); + } + + function it_compares_float_and_int_as_equal_just_like_scalars() + { + $this->beConstructedWith(new JustInteger(123)); + + $this->compare(new JustFloat(123.00))->shouldBe(0); + $this->equals(new JustFloat(123.00))->shouldBe(true); + } + + function it_adds_integers() + { + $this->beConstructedWith(new JustInteger(123)); + + $this->add(new JustInteger(45))->toNumber()->shouldBe(168); + } + + function it_adds_integer_and_float() + { + $this->beConstructedWith(new JustInteger(123)); + + $this->add(new JustFloat(.45))->toNumber()->shouldBe(123.45); + } + + function it_adds_floats() + { + $this->beConstructedWith(new JustFloat(.1)); + + $this->add(new JustFloat(.1))->toNumber()->shouldBe(.2); + } + + function it_adds_float_and_integer() + { + $this->beConstructedWith(new JustFloat(.1)); + + $this->add(new JustInteger(123))->toNumber()->shouldBe(123.1); + } + + function it_subtracts_integers() + { + $this->beConstructedWith(new JustInteger(123)); + + $this->subtract(new JustInteger(45))->toNumber()->shouldBe(78); + } + + function it_subtracts_integer_and_float() + { + $this->beConstructedWith(new JustInteger(123)); + + $this->subtract(new JustFloat(.45))->toNumber()->shouldBe(122.55); + } + + function it_subtracts_floats() + { + $this->beConstructedWith(new JustFloat(.2)); + + $this->subtract(new JustFloat(.1))->toNumber()->shouldBe(.1); + } + + function it_subtracts_float_and_integer() + { + $this->beConstructedWith(new JustFloat(123.5)); + + $this->subtract(new JustInteger(23))->toNumber()->shouldBe(100.5); + } + + function it_multiplies_integers() + { + $this->beConstructedWith(new JustInteger(8)); + + $this->multiply(new JustInteger(9))->toNumber()->shouldBe(72); + } + + function it_multiplies_integer_and_float() + { + $this->beConstructedWith(new JustInteger(8)); + + $this->multiply(new JustFloat(2.4))->toNumber()->shouldBe(19.2); + } + + function it_multiplies_floats() + { + $this->beConstructedWith(new JustFloat(.4)); + + $this->multiply(new JustFloat(.5))->toNumber()->shouldBe(.2); + } + + function it_multiplies_float_and_integer() + { + $this->beConstructedWith(new JustFloat(11.2)); + + $this->multiply(new JustInteger(4))->toNumber()->shouldBe(44.8); + } + + function it_divides_integers() + { + $this->beConstructedWith(new JustInteger(12)); + + $this->divide(new JustInteger(4))->toNumber()->shouldBe(3); + } + + function it_divides_integers_returning_float_when_fraction_result() + { + $this->beConstructedWith(new JustInteger(12)); + + $this->divide(new JustInteger(5))->toNumber()->shouldBe(2.4); + } + + function it_divides_integer_and_float_returning_float() + { + $this->beConstructedWith(new JustInteger(12)); + + $this->divide(new JustFloat(.5))->toNumber()->shouldBe(24.0); + } + + function it_divides_floats() + { + $this->beConstructedWith(new JustFloat(.12)); + + $this->divide(new JustFloat(.04))->toNumber()->shouldBe(3.0); + } + + function it_divides_float_and_integer() + { + $this->beConstructedWith(new JustFloat(12.5)); + + $this->divide(new JustInteger(5))->toNumber()->shouldBe(2.5); + } + + function it_absolutes_positive_integer() + { + $this->beConstructedWith(new JustInteger(2)); + + $this->abs()->toNumber()->shouldBe(2); + } + + function it_absolutes_negative_integer() + { + $this->beConstructedWith(new JustInteger(-2)); + + $this->abs()->toNumber()->shouldBe(2); + } + + function it_absolutes_positive_float() + { + $this->beConstructedWith(new JustFloat(12.3)); + + $this->abs()->toNumber()->shouldBe(12.3); + } + + function it_absolutes_negative_float() + { + $this->beConstructedWith(new JustFloat(-12.3)); + + $this->abs()->toNumber()->shouldBe(12.3); + } + + function it_rounds_integer() + { + $this->beConstructedWith(new JustInteger(123)); + + $this->round(-2)->toNumber()->shouldBe(100.0); + } + + function it_rounds_float() + { + $this->beConstructedWith(new JustFloat(12.3)); + + $this->round()->toNumber()->shouldBe(12.0); + } + + function it_rounds_float_half_up() + { + $this->beConstructedWith(new JustFloat(12.5)); + + $this->round()->toNumber()->shouldBe(13.0); + } + + function it_rounds_float_half_down() + { + $this->beConstructedWith(new JustFloat(12.5)); + + $this->round(0, PHP_ROUND_HALF_DOWN)->toNumber()->shouldBe(12.0); + } + + function it_floors_integer_to_float() + { + $this->beConstructedWith(new JustInteger(2)); + + $this->floor()->toNumber()->shouldBe(2.0); + } + + function it_floors_float() + { + $this->beConstructedWith(new JustFloat(2.9)); + + $this->floor()->toNumber()->shouldBe(2.0); + } + + function it_ceil_integer_to_float() + { + $this->beConstructedWith(new JustInteger(2)); + + $this->ceil()->toNumber()->shouldBe(2.0); + } + + function it_ceil_float() + { + $this->beConstructedWith(new JustFloat(2.1)); + + $this->ceil()->toNumber()->shouldBe(3.0); + } + + function it_is_empty_when_zero_integer() + { + $this->beConstructedWith(new JustInteger(0)); + + $this->isEmpty()->shouldBe(true); + } + + function it_is_empty_when_zero_float() + { + $this->beConstructedWith(new Sum(new JustFloat(-1.0), new JustFloat(1.0))); + + $this->isEmpty()->shouldBe(true); + } + + function it_calculates_custom_formula() + { + $this->beConstructedWith(new JustInteger(100)); + + $formula = fn(Numberable $number): Numberable => new Divide( + new Sum($number, new JustInteger(700)), + new JustInteger(2) + ); + + $this->calculate($formula)->toNumber()->shouldBe(400); + } + + function it_calculates_math_formulas() + { + $this->beConstructedWith(new JustInteger(100)); + + $this->calculate(Math::cos())->toNumber()->shouldBe(cos(100)); + $this->calculate(Math::acos())->toNumber()->shouldBe(acos(100)); + $this->calculate(Math::sin())->toNumber()->shouldBe(sin(100)); + $this->calculate(Math::asin())->toNumber()->shouldBe(asin(100)); + $this->calculate(Math::tan())->toNumber()->shouldBe(tan(100)); + $this->calculate(Math::atan())->toNumber()->shouldBe(atan(100)); + } +} diff --git a/src/NumberValue.php b/src/NumberValue.php new file mode 100644 index 0000000..5219f67 --- /dev/null +++ b/src/NumberValue.php @@ -0,0 +1,51 @@ +numberable = $numberable; + } + + /** @return int|float */ + public function toNumber() + { + return abs($this->numberable->toNumber()); + } +} diff --git a/src/Numberable/Ceil.php b/src/Numberable/Ceil.php new file mode 100644 index 0000000..2c17307 --- /dev/null +++ b/src/Numberable/Ceil.php @@ -0,0 +1,22 @@ +numberable = $numberable; + } + + /** @return int|float */ + public function toNumber() + { + return ceil($this->numberable->toNumber()); + } +} diff --git a/src/Numberable/Divide.php b/src/Numberable/Divide.php new file mode 100644 index 0000000..81ce581 --- /dev/null +++ b/src/Numberable/Divide.php @@ -0,0 +1,32 @@ +dividend = $dividend; + $this->divisors = $divisors; + } + + public function toNumber() + { + return array_reduce( + $this->divisors, + /** + * @param int|float $fraction + * @return int|float + */ + static fn($fraction, Numberable $divisor) => $fraction / $divisor->toNumber(), + $this->dividend->toNumber() + ); + } +} diff --git a/src/Numberable/Floor.php b/src/Numberable/Floor.php new file mode 100644 index 0000000..f3dab98 --- /dev/null +++ b/src/Numberable/Floor.php @@ -0,0 +1,22 @@ +numberable = $numberable; + } + + /** @return int|float */ + public function toNumber() + { + return floor($this->numberable->toNumber()); + } +} diff --git a/src/Numberable/Formula.php b/src/Numberable/Formula.php new file mode 100644 index 0000000..c5f21d9 --- /dev/null +++ b/src/Numberable/Formula.php @@ -0,0 +1,27 @@ +numberable = $numberable; + $this->fn = $fn; + } + + /** @return int|float */ + public function toNumber() + { + return ($this->fn)($this->numberable->toNumber()); + } +} diff --git a/src/Numberable/JustFloat.php b/src/Numberable/JustFloat.php new file mode 100644 index 0000000..702a6a3 --- /dev/null +++ b/src/Numberable/JustFloat.php @@ -0,0 +1,20 @@ +float = $float; + } + + public function toNumber(): float + { + return $this->float; + } +} diff --git a/src/Numberable/JustInteger.php b/src/Numberable/JustInteger.php new file mode 100644 index 0000000..6c589f0 --- /dev/null +++ b/src/Numberable/JustInteger.php @@ -0,0 +1,20 @@ +integer = $integer; + } + + public function toNumber(): int + { + return $this->integer; + } +} diff --git a/src/Numberable/Math.php b/src/Numberable/Math.php new file mode 100644 index 0000000..777eaba --- /dev/null +++ b/src/Numberable/Math.php @@ -0,0 +1,62 @@ +fn = $fn; + } + + public static function cos(): self + { + return new self('cos'); + } + + public static function acos(): self + { + return new self('acos'); + } + + public static function sin(): self + { + return new self('sin'); + } + + public static function asin(): self + { + return new self('asin'); + } + + public static function tan(): self + { + return new self('tan'); + } + + public static function atan(): self + { + return new self('atan'); + } + + public static function exp(): self + { + return new self('exp'); + } + + public static function sqrt(): self + { + return new self('sqrt'); + } + + public function __invoke(Numberable $numberable): Numberable + { + return new Formula($numberable, $this->fn); + } +} diff --git a/src/Numberable/Multiply.php b/src/Numberable/Multiply.php new file mode 100644 index 0000000..1d939fb --- /dev/null +++ b/src/Numberable/Multiply.php @@ -0,0 +1,32 @@ +factor = $factor; + $this->factors = $factors; + } + + public function toNumber() + { + return array_reduce( + $this->factors, + /** + * @param int|float $product + * @return int|float + */ + static fn($product, Numberable $next) => $product * $next->toNumber(), + $this->factor->toNumber() + ); + } +} diff --git a/src/Numberable/Round.php b/src/Numberable/Round.php new file mode 100644 index 0000000..833f2a1 --- /dev/null +++ b/src/Numberable/Round.php @@ -0,0 +1,27 @@ +numberable = $numberable; + $this->precision = $precision; + $this->mode = $mode; + } + + /** @return int|float */ + public function toNumber() + { + return round($this->numberable->toNumber(), $this->precision, $this->mode ?? PHP_ROUND_HALF_UP); + } +} diff --git a/src/Numberable/Subtract.php b/src/Numberable/Subtract.php new file mode 100644 index 0000000..0590f06 --- /dev/null +++ b/src/Numberable/Subtract.php @@ -0,0 +1,33 @@ +term = $term; + $this->terms = $terms; + } + + /** @return int|float */ + public function toNumber() + { + return array_reduce( + $this->terms, + /** + * @param int|float $difference + * @return int|float + */ + static fn($difference, Numberable $next) => $difference - $next->toNumber(), + $this->term->toNumber() + ); + } +} diff --git a/src/Numberable/Sum.php b/src/Numberable/Sum.php new file mode 100644 index 0000000..68e48ba --- /dev/null +++ b/src/Numberable/Sum.php @@ -0,0 +1,33 @@ +term = $term; + $this->terms = $terms; + } + + /** @return int|float */ + public function toNumber() + { + return array_reduce( + $this->terms, + /** + * @param int|float $sum + * @return int|float + */ + static fn($sum, Numberable $next) => $sum + $next->toNumber(), + $this->term->toNumber() + ); + } +} diff --git a/src/PlainNumber.php b/src/PlainNumber.php new file mode 100644 index 0000000..6d354eb --- /dev/null +++ b/src/PlainNumber.php @@ -0,0 +1,113 @@ +number = $number; + } + + public function toInteger(): int + { + return (int)$this->number->toNumber(); + } + + public function toFloat(): float + { + return (float)$this->number->toNumber(); + } + + public function toStringValue(): StringValue + { + return $this->format(PHP_FLOAT_DIG, '.', ''); + } + + public function format(int $decimals = 0, string $separator = '.', string $thousandsSeparator = ','): StringValue + { + return Wrap::string(number_format($this->number->toNumber(), $decimals, $separator, $thousandsSeparator)) + ->trimRight('0') + ->trimRight('.'); + } + + public function compare(Numberable $other): int + { + return $this->toNumber() <=> $other->toNumber(); + } + + public function equals(Numberable $other): bool + { + return $this->compare($other) === 0; + } + + public function add(Numberable $other): NumberValue + { + return new self(new Sum($this->number, $other)); + } + + public function subtract(Numberable $other): NumberValue + { + return new self(new Subtract($this->number, $other)); + } + + public function multiply(Numberable $other): NumberValue + { + return new self(new Multiply($this->number, $other)); + } + + public function divide(Numberable $other): NumberValue + { + return new self(new Divide($this->number, $other)); + } + + public function abs(): NumberValue + { + return new self(new Absolute($this->number)); + } + + public function round(int $precision = 0, ?int $roundMode = null): NumberValue + { + return new self(new Round($this->number, $precision, $roundMode)); + } + + public function floor(): NumberValue + { + return new self(new Floor($this->number)); + } + + public function ceil(): NumberValue + { + return new self(new Ceil($this->number)); + } + + /** @param callable(Numberable):Numberable $formula */ + public function calculate(callable $formula): NumberValue + { + return new self($formula($this->number)); + } + + public function isEmpty(): bool + { + return $this->toFloat() === 0.0; + } + + /** @return int|float */ + public function toNumber() + { + return $this->number->toNumber(); + } +} From 5438bc4fbf9d0703fddda949ae0f69ab140efee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Wed, 3 Mar 2021 22:26:03 +0100 Subject: [PATCH 02/20] Numbers array initial --- spec/PlainNumberSpec.php | 6 +- src/GenericArray.php | 449 ++++++++++++++++++++++++++++++ src/NumberValue.php | 12 +- src/Numberable/Add.php | 22 ++ src/Numberable/Average.php | 33 +++ src/Numberable/Max.php | 33 +++ src/Numberable/Min.php | 34 +++ src/Numberable/Sum.php | 15 +- src/Numberable/ToScalarNumber.php | 14 + src/Numberable/Zero.php | 13 + src/NumbersArray.php | 171 ++++++++++++ src/PlainNumber.php | 4 +- src/PlainNumbersArray.php | 58 ++++ test.sh | 2 +- 14 files changed, 847 insertions(+), 19 deletions(-) create mode 100644 src/GenericArray.php create mode 100644 src/Numberable/Add.php create mode 100644 src/Numberable/Average.php create mode 100644 src/Numberable/Max.php create mode 100644 src/Numberable/Min.php create mode 100644 src/Numberable/ToScalarNumber.php create mode 100644 src/Numberable/Zero.php create mode 100644 src/NumbersArray.php create mode 100644 src/PlainNumbersArray.php diff --git a/spec/PlainNumberSpec.php b/spec/PlainNumberSpec.php index 08bd708..df60339 100644 --- a/spec/PlainNumberSpec.php +++ b/spec/PlainNumberSpec.php @@ -7,7 +7,7 @@ use GW\Value\Numberable\JustFloat; use GW\Value\Numberable\JustInteger; use GW\Value\Numberable\Math; -use GW\Value\Numberable\Sum; +use GW\Value\Numberable\Add; use PhpSpec\ObjectBehavior; use function acos; use function cos; @@ -254,7 +254,7 @@ function it_is_empty_when_zero_integer() function it_is_empty_when_zero_float() { - $this->beConstructedWith(new Sum(new JustFloat(-1.0), new JustFloat(1.0))); + $this->beConstructedWith(new Add(new JustFloat(-1.0), new JustFloat(1.0))); $this->isEmpty()->shouldBe(true); } @@ -264,7 +264,7 @@ function it_calculates_custom_formula() $this->beConstructedWith(new JustInteger(100)); $formula = fn(Numberable $number): Numberable => new Divide( - new Sum($number, new JustInteger(700)), + new Add($number, new JustInteger(700)), new JustInteger(2) ); diff --git a/src/GenericArray.php b/src/GenericArray.php new file mode 100644 index 0000000..e0b10c2 --- /dev/null +++ b/src/GenericArray.php @@ -0,0 +1,449 @@ + + */ +abstract class GenericArray implements ArrayValue +{ + /** @return Arrayable */ + abstract protected function items(): Arrayable; + + /** + * @template TNewValue + * @param Arrayable $items + * @return static + */ + abstract public static function new(Arrayable $items): self; + + /** + * @template TNewValue + * @param callable(TValue $value):TNewValue $transformer + * @phpstan-return static + */ + public function map(callable $transformer): self + { + return static::new(new Map($this->items(), $transformer)); + } + + /** + * @template TNewValue + * @param callable(TValue $value):iterable $transformer + * @phpstan-return static + */ + public function flatMap(callable $transformer): self + { + return static::new(new FlatMap($this->items(), $transformer)); + } + + /** + * @template TNewKey + * @phpstan-param callable(TValue $value):TNewKey $reducer + * @phpstan-return AssocValue> + */ + public function groupBy(callable $reducer): AssocValue + { + /** @phpstan-var array> $groups */ + $groups = []; + + foreach ($this->items()->toArray() as $item) { + /** @phpstan-var TNewKey $key */ + $key = $reducer($item); + $groups[$key][] = $item; + } + + /** @phpstan-var array> $groupsWrapped */ + $groupsWrapped = array_map([Wrap::class, 'array'], $groups); + + return Wrap::assocArray($groupsWrapped); + } + + /** + * @phpstan-return static> + */ + public function chunk(int $size): self + { + return static::new(new Chunk($this->items(), $size)); + } + + /** + * @phpstan-return static + */ + public function filterEmpty(): self + { + return $this->filter(Filters::notEmpty()); + } + + /** + * @param callable(TValue $value):bool $filter + * @phpstan-return static + */ + public function filter(callable $filter): self + { + return static::new(new Filter($this->items(), $filter)); + } + + /** + * @param callable(TValue $leftValue, TValue $rightValue):int $comparator + * @phpstan-return static + */ + public function sort(callable $comparator): self + { + return static::new(new Sort($this->items(), $comparator)); + } + + /** + * @param callable(TValue $value):void $callback + * @phpstan-return static + */ + public function each(callable $callback): self + { + foreach ($this->items()->toArray() as $item) { + $callback($item); + } + + return $this; + } + + /** + * @phpstan-return static + */ + public function reverse(): self + { + return static::new(new Reverse($this->items())); + } + + /** + * @phpstan-param ArrayValue $other + * @phpstan-return static + */ + public function join(ArrayValue $other): self + { + return static::new(new Join($this->items(), $other)); + } + + /** + * @phpstan-return static + */ + public function slice(int $offset, int $length): self + { + return static::new(new Slice($this->items(), $offset, $length)); + } + + /** + * @phpstan-param ArrayValue $replacement + * @phpstan-return static + */ + public function splice(int $offset, int $length, ?ArrayValue $replacement = null): self + { + return static::new(new Splice($this->items(), $offset, $length, $replacement ?? new JustArray([]))); + } + + /** + * @param callable(TValue $valueA, TValue $valueB):int | null $comparator + * @phpstan-return static + */ + public function unique(?callable $comparator = null): self + { + if ($comparator === null) { + return static::new(new UniqueByString($this->items())); + } + + return static::new(new UniqueByComparator($this->items(), $comparator)); + } + + /** + * @phpstan-param static $other + * @param (callable(TValue $valueA, TValue $valueB):int)|null $comparator + * @phpstan-return static + */ + public function diff(ArrayValue $other, ?callable $comparator = null): self + { + if ($comparator === null) { + return static::new(new DiffByString($this->items(), $other)); + } + + return static::new(new DiffByComparator($this->items(), $other, $comparator)); + } + + /** + * @phpstan-param static $other + * @param (callable(TValue $valueA, TValue $valueB):int)|null $comparator + * @phpstan-return static + */ + public function intersect(ArrayValue $other, ?callable $comparator = null): self + { + if ($comparator === null) { + return static::new(new IntersectByString($this->items(), $other)); + } + + return static::new(new IntersectByComparator($this->items(), $other, $comparator)); + } + + /** + * @phpstan-return static + */ + public function shuffle(): self + { + return static::new(new Shuffle($this->items())); + } + + // adders and removers + + /** + * @phpstan-param TValue $value + * @phpstan-return static + */ + public function unshift($value): self + { + $items = $this->toArray(); + array_unshift($items, $value); + + return static::new(new JustArray($items)); + } + + /** + * @phpstan-param TValue $value + * @phpstan-return static + */ + public function shift(&$value = null): self + { + $items = $this->toArray(); + $value = array_shift($items); + + return static::new(new JustArray($items));; + } + + /** + * @phpstan-param TValue $value + * @phpstan-return static + */ + public function push($value): self + { + $items = $this->toArray(); + $items[] = $value; + + return static::new(new JustArray($items)); + } + + /** + * @phpstan-param TValue $value + * @phpstan-return static + */ + public function pop(&$value = null): self + { + $items = $this->toArray(); + $value = array_pop($items); + + return static::new(new JustArray($items)); + } + + // finalizers + + /** + * @template TNewValue + * @param callable(TNewValue $reduced, TValue $value):TNewValue $transformer + * @phpstan-param TNewValue $start + * @phpstan-return TNewValue + */ + public function reduce(callable $transformer, $start) + { + return array_reduce($this->items()->toArray(), $transformer, $start); + } + + /** + * @phpstan-return ?TValue + */ + public function first() + { + return $this->items()->toArray()[0] ?? null; + } + + /** + * @phpstan-return ?TValue + */ + public function last() + { + $count = $this->count(); + + return $count > 0 ? $this->items()->toArray()[$count - 1] : null; + } + + /** + * @param callable(TValue $value):bool $filter + * @phpstan-return ?TValue + */ + public function find(callable $filter) + { + foreach ($this->items()->toArray() as $item) { + if ($filter($item)) { + return $item; + } + } + + return null; + } + + /** + * @param callable(TValue $value):bool $filter + * @phpstan-return ?TValue + */ + public function findLast(callable $filter) + { + foreach (array_reverse($this->items()->toArray()) as $item) { + if ($filter($item)) { + return $item; + } + } + + return null; + } + + public function hasElement($element): bool + { + return in_array($element, $this->items()->toArray(), true); + } + + /** + * @param callable(TValue $value):bool $filter + */ + public function any(callable $filter): bool + { + foreach ($this->items()->toArray() as $item) { + if ($filter($item)) { + return true; + } + } + + return false; + } + + /** + * @param callable(TValue $value):bool $filter + */ + public function every(callable $filter): bool + { + foreach ($this->items()->toArray() as $item) { + if (!$filter($item)) { + return false; + } + } + + return true; + } + + public function count(): int + { + return count($this->items()->toArray()); + } + + /** + * @phpstan-return TValue[] + */ + public function toArray(): array + { + return $this->items()->toArray(); + } + + /** + * @phpstan-return ArrayIterator + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->items()->toArray()); + } + + /** + * @param int $offset + */ + public function offsetExists($offset): bool + { + return isset($this->items()->toArray()[$offset]); + } + + /** + * @param int $offset + * @return ?TValue + */ + public function offsetGet($offset) + { + return $this->items()->toArray()[$offset]; + } + + /** + * @param int $offset + * @phpstan-param TValue $value + * @throws BadMethodCallException For immutable types. + */ + public function offsetSet($offset, $value): void + { + throw new BadMethodCallException('ArrayValue is immutable'); + } + + /** + * @param int $offset + * @throws BadMethodCallException For immutable types. + */ + public function offsetUnset($offset): void + { + throw new BadMethodCallException('ArrayValue is immutable'); + } + + public function implode(string $glue): StringValue + { + return Wrap::string(implode($glue, $this->toArray())); + } + + /** + * @phpstan-return static + */ + public function notEmpty(): self + { + return $this->filter(Filters::notEmpty()); + } + + public function isEmpty(): bool + { + return $this->items()->toArray() === []; + } + + /** + * @phpstan-return AssocValue + */ + public function toAssocValue(): AssocValue + { + return new AssocArray(new Associate($this->items())); + } + + public function toStringsArray(): StringsArray + { + return Wrap::stringsArray($this->items()->toArray()); + } +} diff --git a/src/NumberValue.php b/src/NumberValue.php index 5219f67..b9fb451 100644 --- a/src/NumberValue.php +++ b/src/NumberValue.php @@ -17,19 +17,19 @@ public function format(int $decimals = 0, string $separator = '.' , string $thou /** * @return int {-1, 0, 1} */ - public function compare(NumberValue $other): int; + public function compare(Numberable $other): int; - public function equals(NumberValue $other): bool; + public function equals(Numberable $other): bool; // basic math - public function add(NumberValue $other): NumberValue; + public function add(Numberable $other): NumberValue; - public function subtract(NumberValue $other): NumberValue; + public function subtract(Numberable $other): NumberValue; - public function multiply(NumberValue $other): NumberValue; + public function multiply(Numberable $other): NumberValue; - public function divide(NumberValue $other): NumberValue; + public function divide(Numberable $other): NumberValue; public function abs(): NumberValue; diff --git a/src/Numberable/Add.php b/src/Numberable/Add.php new file mode 100644 index 0000000..1bf35ed --- /dev/null +++ b/src/Numberable/Add.php @@ -0,0 +1,22 @@ +sum = new Sum(new JustArray([$term, ...$terms])); + } + + /** @return int|float */ + public function toNumber() + { + return $this->sum->toNumber(); + } +} diff --git a/src/Numberable/Average.php b/src/Numberable/Average.php new file mode 100644 index 0000000..ff8ff23 --- /dev/null +++ b/src/Numberable/Average.php @@ -0,0 +1,33 @@ + */ + private Arrayable $terms; + + /** @param Arrayable $terms */ + public function __construct(Arrayable $terms) + { + $this->terms = $terms; + } + + /** @return int|float */ + public function toNumber() + { + $terms = $this->terms->toArray(); + $count = count($terms); + if ($count === 0) { + throw new LogicException('Cannot calculate avg number from empty set'); + } + + return (new Sum(new JustArray($terms)))->toNumber() / $count; + } +} diff --git a/src/Numberable/Max.php b/src/Numberable/Max.php new file mode 100644 index 0000000..7505aa4 --- /dev/null +++ b/src/Numberable/Max.php @@ -0,0 +1,33 @@ + */ + private Arrayable $numbers; + + /** @param Arrayable $numbers */ + public function __construct(Arrayable $numbers) + { + $this->numbers = new Map($numbers, new ToScalarNumber()); + } + + /** @return int|float */ + public function toNumber() + { + $numbers = $this->numbers->toArray(); + if (count($numbers) === 0) { + throw new LogicException('Cannot calculate max number from empty set'); + } + + return max(...$numbers); + } +} diff --git a/src/Numberable/Min.php b/src/Numberable/Min.php new file mode 100644 index 0000000..697345e --- /dev/null +++ b/src/Numberable/Min.php @@ -0,0 +1,34 @@ + */ + private Arrayable $numbers; + + /** @param Arrayable $numbers */ + public function __construct(Arrayable $numbers) + { + $this->numbers = new Map($numbers, new ToScalarNumber()); + } + + /** @return int|float */ + public function toNumber() + { + $numbers = $this->numbers->toArray(); + if (count($numbers) === 0) { + throw new LogicException('Cannot calculate min number from empty set'); + } + + return min(...$numbers); + } +} diff --git a/src/Numberable/Sum.php b/src/Numberable/Sum.php index 68e48ba..bbdb1fa 100644 --- a/src/Numberable/Sum.php +++ b/src/Numberable/Sum.php @@ -2,18 +2,19 @@ namespace GW\Value\Numberable; +use GW\Value\Arrayable; use GW\Value\Numberable; +use GW\Value\NumberValue; use function array_reduce; final class Sum implements Numberable { - private Numberable $term; - /** @var Numberable[] */ - private array $terms; + /** @var Arrayable */ + private Arrayable $terms; - public function __construct(Numberable $term, Numberable ...$terms) + /** @param Arrayable $terms */ + public function __construct(Arrayable $terms) { - $this->term = $term; $this->terms = $terms; } @@ -21,13 +22,13 @@ public function __construct(Numberable $term, Numberable ...$terms) public function toNumber() { return array_reduce( - $this->terms, + $this->terms->toArray(), /** * @param int|float $sum * @return int|float */ static fn($sum, Numberable $next) => $sum + $next->toNumber(), - $this->term->toNumber() + 0 ); } } diff --git a/src/Numberable/ToScalarNumber.php b/src/Numberable/ToScalarNumber.php new file mode 100644 index 0000000..cfea07e --- /dev/null +++ b/src/Numberable/ToScalarNumber.php @@ -0,0 +1,14 @@ +toNumber(); + } +} diff --git a/src/Numberable/Zero.php b/src/Numberable/Zero.php new file mode 100644 index 0000000..6238dae --- /dev/null +++ b/src/Numberable/Zero.php @@ -0,0 +1,13 @@ + + */ +interface NumbersArray extends ArrayValue +{ + public function sum(): NumberValue; + + public function average(): NumberValue; + + public function min(): NumberValue; + + public function max(): NumberValue; + + // ArrayValue + + /** + * @param callable(NumberValue):void $callback + */ + public function each(callable $callback): NumbersArray; + + /** + * @param (callable(NumberValue $valueA, NumberValue $valueB):int)|null $comparator + */ + public function unique(?callable $comparator = null): NumbersArray; + + /** @return NumberValue[] */ + public function toArray(): array; + + /** + * @param callable(NumberValue):bool $filter + */ + public function filter(callable $filter): NumbersArray; + + public function filterEmpty(): NumbersArray; + + /** + * @template TNewValue + * @param callable(NumberValue):TNewValue $transformer + * @phpstan-return ArrayValue + */ + public function map(callable $transformer): ArrayValue; + + /** + * @template TNewValue + * @param callable(NumberValue):iterable $transformer + * @phpstan-return ArrayValue + */ + public function flatMap(callable $transformer): ArrayValue; + + /** + * @template TNewKey + * @param callable(NumberValue):TNewKey $reducer + * @phpstan-return AssocValue + */ + public function groupBy(callable $reducer): AssocValue; + + /** + * @phpstan-return ArrayValue> + */ + public function chunk(int $size): ArrayValue; + + public function sort(callable $comparator): NumbersArray; + + public function shuffle(): NumbersArray; + + public function reverse(): NumbersArray; + + /** + * @phpstan-param NumberValue $value + */ + public function unshift($value): NumbersArray; + + /** + * @phpstan-param NumberValue $value + */ + public function shift(&$value = null): NumbersArray; + + /** + * @phpstan-param NumberValue $value + */ + public function push($value): NumbersArray; + + /** + * @phpstan-param NumberValue $value + */ + public function pop(&$value = null): NumbersArray; + + public function offsetExists($offset): bool; + + public function offsetGet($offset): NumberValue; + + public function offsetSet($offset, $value): void; + + public function offsetUnset($offset): void; + + /** + * @phpstan-param Arrayable $other + */ + public function join(Arrayable $other): NumbersArray; + + public function slice(int $offset, int $length): NumbersArray; + + /** + * @phpstan-param Arrayable|null $replacement + */ + public function splice(int $offset, int $length, ?Arrayable $replacement = null): NumbersArray; + + /** + * @phpstan-param Arrayable $other + * @param (callable(NumberValue,NumberValue):int)|null $comparator + */ + public function diff(Arrayable $other, ?callable $comparator = null): NumbersArray; + + /** + * @phpstan-param Arrayable $other + * @param (callable(NumberValue,NumberValue):int)|null $comparator + */ + public function intersect(Arrayable $other, ?callable $comparator = null): NumbersArray; + + /** + * @template TNewValue + * @param callable(TNewValue, NumberValue):TNewValue $transformer + * @phpstan-param TNewValue $start + * @phpstan-return TNewValue + */ + public function reduce(callable $transformer, $start); + + public function implode(string $glue): StringValue; + + public function notEmpty(): NumbersArray; + + /** + * @phpstan-return AssocValue + */ + public function toAssocValue(): AssocValue; + + public function toStringsArray(): StringsArray; + + public function first(): ?NumberValue; + + public function last(): ?NumberValue; + + /** + * @param callable(NumberValue):bool $filter + */ + public function find(callable $filter): ?NumberValue; + + /** + * @param callable(NumberValue):bool $filter + */ + public function findLast(callable $filter): ?NumberValue; + + /** + * @phpstan-param NumberValue $element + */ + public function hasElement($element): bool; + + /** + * @param callable(NumberValue):bool $filter + */ + public function any(callable $filter): bool; + + /** + * @param callable(NumberValue):bool $filter + */ + public function every(callable $filter): bool; +} diff --git a/src/PlainNumber.php b/src/PlainNumber.php index 6d354eb..a823d5c 100644 --- a/src/PlainNumber.php +++ b/src/PlainNumber.php @@ -9,7 +9,7 @@ use GW\Value\Numberable\Multiply; use GW\Value\Numberable\Round; use GW\Value\Numberable\Subtract; -use GW\Value\Numberable\Sum; +use GW\Value\Numberable\Add; use function number_format; use const PHP_FLOAT_DIG; @@ -56,7 +56,7 @@ public function equals(Numberable $other): bool public function add(Numberable $other): NumberValue { - return new self(new Sum($this->number, $other)); + return new self(new Add($this->number, $other)); } public function subtract(Numberable $other): NumberValue diff --git a/src/PlainNumbersArray.php b/src/PlainNumbersArray.php new file mode 100644 index 0000000..7263d96 --- /dev/null +++ b/src/PlainNumbersArray.php @@ -0,0 +1,58 @@ + + */ +final class PlainNumbersArray extends GenericArray +{ + /** @phpstan-var Arrayable */ + private Arrayable $numbers; + + /** @param Arrayable $numbers */ + public function __construct(Arrayable $numbers) + { + $this->numbers = new Cache($numbers); + } + + /** + * @param Arrayable $items + */ + public static function new(Arrayable $items): self + { + return new self($items); + } + + /** @return Arrayable */ + protected function items(): Arrayable + { + return $this->numbers; + } + + public function sum(): NumberValue + { + return new PlainNumber(new Sum($this->numbers)); + } + + public function average(): NumberValue + { + return new PlainNumber(new Average($this->numbers)); + } + + public function min(): NumberValue + { + return new PlainNumber(new Min($this->numbers)); + } + + public function max(): NumberValue + { + return new PlainNumber(new Max($this->numbers)); + } +} diff --git a/test.sh b/test.sh index 636b527..1ded268 100755 --- a/test.sh +++ b/test.sh @@ -13,7 +13,7 @@ function stan() { } function psalm() { - ./bin/psalm + ./bin/psalm --show-info=true } function coverage() { From 0556d43354571dd1020f09e9788354410d747362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Wed, 3 Mar 2021 22:56:00 +0100 Subject: [PATCH 03/20] Numbers array initial --- src/GenericArray.php | 2 +- src/Numberable/Average.php | 5 ++--- src/Numberable/Formula.php | 4 ++-- src/Numberable/Math.php | 4 ++-- src/Numberable/Sum.php | 5 ++--- src/NumbersArray.php | 2 +- 6 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/GenericArray.php b/src/GenericArray.php index e0b10c2..5edb4eb 100644 --- a/src/GenericArray.php +++ b/src/GenericArray.php @@ -80,7 +80,7 @@ public function groupBy(callable $reducer): AssocValue } /** @phpstan-var array> $groupsWrapped */ - $groupsWrapped = array_map([Wrap::class, 'array'], $groups); + $groupsWrapped = array_map([static::class, 'new'], $groups); return Wrap::assocArray($groupsWrapped); } diff --git a/src/Numberable/Average.php b/src/Numberable/Average.php index ff8ff23..0dcd1d2 100644 --- a/src/Numberable/Average.php +++ b/src/Numberable/Average.php @@ -5,15 +5,14 @@ use GW\Value\Arrayable; use GW\Value\Arrayable\JustArray; use GW\Value\Numberable; -use GW\Value\NumberValue; use LogicException; final class Average implements Numberable { - /** @var Arrayable */ + /** @var Arrayable */ private Arrayable $terms; - /** @param Arrayable $terms */ + /** @param Arrayable $terms */ public function __construct(Arrayable $terms) { $this->terms = $terms; diff --git a/src/Numberable/Formula.php b/src/Numberable/Formula.php index c5f21d9..9455ffb 100644 --- a/src/Numberable/Formula.php +++ b/src/Numberable/Formula.php @@ -7,11 +7,11 @@ final class Formula implements Numberable { private Numberable $numberable; - /** @var callable(int|float):int|float */ + /** @var callable(int|float):(int|float) */ private $fn; /** - * @param callable(int|float):int|float $fn + * @param callable(int|float):(int|float) $fn */ public function __construct(Numberable $numberable, callable $fn) { diff --git a/src/Numberable/Math.php b/src/Numberable/Math.php index 777eaba..20f8e2c 100644 --- a/src/Numberable/Math.php +++ b/src/Numberable/Math.php @@ -6,10 +6,10 @@ final class Math { - /** @var callable(int|float):int|float */ + /** @var callable(int|float):(int|float) */ private $fn; - /** @param callable(int|float):int|float */ + /** @param callable(int|float):(int|float) $fn */ public function __construct(callable $fn) { $this->fn = $fn; diff --git a/src/Numberable/Sum.php b/src/Numberable/Sum.php index bbdb1fa..d6449eb 100644 --- a/src/Numberable/Sum.php +++ b/src/Numberable/Sum.php @@ -4,15 +4,14 @@ use GW\Value\Arrayable; use GW\Value\Numberable; -use GW\Value\NumberValue; use function array_reduce; final class Sum implements Numberable { - /** @var Arrayable */ + /** @var Arrayable */ private Arrayable $terms; - /** @param Arrayable $terms */ + /** @param Arrayable $terms */ public function __construct(Arrayable $terms) { $this->terms = $terms; diff --git a/src/NumbersArray.php b/src/NumbersArray.php index 30dcaef..7714b72 100644 --- a/src/NumbersArray.php +++ b/src/NumbersArray.php @@ -54,7 +54,7 @@ public function flatMap(callable $transformer): ArrayValue; /** * @template TNewKey * @param callable(NumberValue):TNewKey $reducer - * @phpstan-return AssocValue + * @phpstan-return AssocValue> */ public function groupBy(callable $reducer): AssocValue; From 487f6a7eca00cfdd32efbd5c5f42f23ab9a544b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Sun, 7 Mar 2021 17:39:30 +0100 Subject: [PATCH 04/20] Numbers array --- spec/PlainNumberSpec.php | 49 ++- spec/PlainNumbersArraySpec.php | 524 ++++++++++++++++++++++++++++++ src/GenericArray.php | 449 ------------------------- src/NumberValue.php | 24 +- src/Numberable/Average.php | 4 +- src/Numberable/CompareAsInt.php | 30 ++ src/Numberable/Divide.php | 10 +- src/Numberable/DivideReducer.php | 23 ++ src/Numberable/JustNumber.php | 23 ++ src/Numberable/JustNumbers.php | 31 ++ src/Numberable/Max.php | 3 +- src/Numberable/Min.php | 2 +- src/Numberable/Modulo.php | 22 ++ src/Numberable/ToNumberValue.php | 37 +++ src/Numberable/ToScalarNumber.php | 4 +- src/NumbersArray.php | 23 +- src/PlainNumber.php | 57 +++- src/PlainNumbersArray.php | 349 ++++++++++++++++++-- src/Stringable/ToStringValue.php | 10 +- 19 files changed, 1142 insertions(+), 532 deletions(-) create mode 100644 spec/PlainNumbersArraySpec.php delete mode 100644 src/GenericArray.php create mode 100644 src/Numberable/CompareAsInt.php create mode 100644 src/Numberable/DivideReducer.php create mode 100644 src/Numberable/JustNumber.php create mode 100644 src/Numberable/JustNumbers.php create mode 100644 src/Numberable/Modulo.php create mode 100644 src/Numberable/ToNumberValue.php diff --git a/spec/PlainNumberSpec.php b/spec/PlainNumberSpec.php index df60339..65e3f44 100644 --- a/spec/PlainNumberSpec.php +++ b/spec/PlainNumberSpec.php @@ -2,12 +2,14 @@ namespace spec\GW\Value; +use DivisionByZeroError; use GW\Value\Numberable; use GW\Value\Numberable\Divide; use GW\Value\Numberable\JustFloat; use GW\Value\Numberable\JustInteger; use GW\Value\Numberable\Math; use GW\Value\Numberable\Add; +use GW\Value\PlainNumber; use PhpSpec\ObjectBehavior; use function acos; use function cos; @@ -140,6 +142,15 @@ function it_divides_integers_returning_float_when_fraction_result() $this->divide(new JustInteger(5))->toNumber()->shouldBe(2.4); } + function it_throws_error_when_dividing_by_zero() + { + $this->beConstructedThrough( + fn() => (new PlainNumber(new JustInteger(12)))->divide(new JustInteger(0)) + ); + + $this->shouldThrow(DivisionByZeroError::class)->during('toNumber'); + } + function it_divides_integer_and_float_returning_float() { $this->beConstructedWith(new JustInteger(12)); @@ -161,6 +172,30 @@ function it_divides_float_and_integer() $this->divide(new JustInteger(5))->toNumber()->shouldBe(2.5); } + function it_calculates_modulo_of_integer() + { + $this->beConstructedWith(new JustInteger(12)); + + $this->modulo(new JustInteger(11))->toNumber()->shouldBe(1); + $this->modulo(new JustInteger(7))->toNumber()->shouldBe(5); + } + + function it_calculates_modulo_of_float_just_like_php_does() + { + $this->beConstructedWith(new JustInteger(12)); + + $this->modulo(new JustFloat(11.9))->toNumber()->shouldBe(1); + } + + function it_throws_error_when_modulo_divider_is_zero() + { + $this->beConstructedThrough( + fn() => (new PlainNumber(new JustInteger(12)))->modulo(new JustInteger(0)) + ); + + $this->shouldThrow(DivisionByZeroError::class)->during('toNumber'); + } + function it_absolutes_positive_integer() { $this->beConstructedWith(new JustInteger(2)); @@ -273,13 +308,13 @@ function it_calculates_custom_formula() function it_calculates_math_formulas() { - $this->beConstructedWith(new JustInteger(100)); + $this->beConstructedWith(new JustInteger(90)); - $this->calculate(Math::cos())->toNumber()->shouldBe(cos(100)); - $this->calculate(Math::acos())->toNumber()->shouldBe(acos(100)); - $this->calculate(Math::sin())->toNumber()->shouldBe(sin(100)); - $this->calculate(Math::asin())->toNumber()->shouldBe(asin(100)); - $this->calculate(Math::tan())->toNumber()->shouldBe(tan(100)); - $this->calculate(Math::atan())->toNumber()->shouldBe(atan(100)); + $this->calculate(Math::cos())->toNumber()->shouldBe(cos(90)); + $this->divide(new JustInteger(100))->calculate(Math::acos())->toNumber()->shouldBe(acos(.90)); + $this->calculate(Math::sin())->toNumber()->shouldBe(sin(90)); + $this->divide(new JustInteger(100))->calculate(Math::asin())->toNumber()->shouldBe(asin(.90)); + $this->calculate(Math::tan())->toNumber()->shouldBe(tan(90)); + $this->divide(new JustInteger(100))->calculate(Math::atan())->toNumber()->shouldBe(atan(.90)); } } diff --git a/spec/PlainNumbersArraySpec.php b/spec/PlainNumbersArraySpec.php new file mode 100644 index 0000000..dcd892e --- /dev/null +++ b/spec/PlainNumbersArraySpec.php @@ -0,0 +1,524 @@ +beConstructedThrough('just', [1, 2, 3, 4, 5, 6, 7]); + + $this->sum()->toNumber()->shouldBe(28); + } + + function it_calculates_sum_of_floats() + { + $this->beConstructedThrough('just', [.5, 1.0, 1.5, 2.0, 2.5]); + + $this->sum()->toNumber()->shouldBeApproximately(7.5, 1.0e-9); + } + + function it_calculates_average_of_integers() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5, 6, 7]); + + $this->average()->toNumber()->shouldBe(4); + } + + function it_calculates_average_of_floats() + { + $this->beConstructedThrough('just', [1.1, 1.2, 1.3, 1.4, 1.5, 2.2]); + + $this->average()->toNumber()->shouldBeApproximately(1.45, 1.0e-9); + } + + function it_cannot_calculate_average_from_empty_set() + { + $this->beConstructedThrough('just', []); + + $this->average()->shouldThrow(DivisionByZeroError::class)->during('toNumber'); + } + + function it_returns_min_of_integers() + { + $this->beConstructedThrough('just', [6, 2, 1, 7, -2, 3, 4, 5]); + + $this->min()->toNumber()->shouldBe(-2); + } + + function it_returns_min_of_floats() + { + $this->beConstructedThrough('just', [.6, .2, .1, .7, -.2, .3, .4, .5]); + + $this->min()->toNumber()->shouldBe(-.2); + } + + function it_returns_max_of_integers() + { + $this->beConstructedThrough('just', [6, 2, 1, 7, -2, 3, 4, 5]); + + $this->max()->toNumber()->shouldBe(7); + } + + function it_returns_max_of_floats() + { + $this->beConstructedThrough('just', [.6, .2, .1, .7, -.2, .3, .4, .5]); + + $this->max()->toNumber()->shouldBe(.7); + } + + function it_cannot_calculate_min_nor_max_from_empty_set() + { + $this->beConstructedThrough('just', []); + + $this->min()->shouldThrow(LogicException::class)->during('toNumber'); + $this->max()->shouldThrow(LogicException::class)->during('toNumber'); + } + + function it_filters_numbers() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5, 6, 7]); + + $even = $this->filter(fn(NumberValue $value): bool => $value->toNumber() % 2 === 0); + $even->shouldBeAnInstanceOf(PlainNumbersArray::class); + $even->toNativeNumbers()->shouldBe([2, 4, 6]); + } + + function it_filters_zeros_as_empty_elements() + { + $this->beConstructedThrough('just', [1, 2, 3, 0, 4, 5, .0]); + + $even = $this->filterEmpty(); + $even->shouldBeAnInstanceOf(PlainNumbersArray::class); + $even->toNativeNumbers()->shouldBe([1, 2, 3, 4, 5]); + } + + function it_maps_to_ArrayValue() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5, 6, 7]); + + $mapped = $this->map(fn(NumberValue $number): string => "#{$number->toNumber()}"); + $mapped->beAnInstanceOf(PlainArray::class); + $mapped->toArray()->shouldBe(['#1', '#2', '#3', '#4', '#5', '#6', '#7']); + } + + function it_maps_flat_to_ArrayValue() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5, 6, 7]); + + $mapped = $this->flatMap( + fn(NumberValue $number): array => ["#{$number->toNumber()}.1", "#{$number->toNumber()}.2"] + ); + $mapped->beAnInstanceOf(PlainArray::class); + $mapped->toArray()->shouldBe( + [ + '#1.1', + '#1.2', + '#2.1', + '#2.2', + '#3.1', + '#3.2', + '#4.1', + '#4.2', + '#5.1', + '#5.2', + '#6.1', + '#6.2', + '#7.1', + '#7.2', + ] + ); + } + + function it_groups_numbers_returning_association_of_numbers_array() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $grouped = $this->groupBy(fn(NumberValue $value) => $value->modulo(new JustInteger(2))->toNumber()); + + $even = $grouped->get(0); + $even->shouldBeAnInstanceOf(PlainNumbersArray::class); + $even->toNativeNumbers()->shouldBe([2, 4]); + + $odd = $grouped->get(1); + $odd->toNativeNumbers()->shouldBe([1, 3, 5]); + } + + function it_chunks_number_values() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $chunked = $this->chunk(2); + $chunked->shouldBeAnInstanceOf(PlainArray::class); + $chunked[0]->shouldBeArray(); + $chunked[0]->shouldHaveCount(2); + $chunked[0][0]->toNumber()->shouldBe(1); + $chunked[0][1]->toNumber()->shouldBe(2); + + $chunked[1]->shouldBeArray(); + $chunked[1]->shouldHaveCount(2); + $chunked[1][0]->toNumber()->shouldBe(3); + $chunked[1][1]->toNumber()->shouldBe(4); + + $chunked[2]->shouldBeArray(); + $chunked[2]->shouldHaveCount(1); + $chunked[2][0]->toNumber()->shouldBe(5); + } + + function it_sorts_numbers() + { + $this->beConstructedThrough('just', [6, 2, 1, 7, -2, 3, 4, 5]); + + $asc = $this->sort(Sorts::asc()); + $asc->beAnInstanceOf(PlainNumbersArray::class); + $asc->toNativeNumbers()->shouldBe([-2, 1, 2, 3, 4, 5, 6, 7]); + + $this->sort(Sorts::desc())->toNativeNumbers()->shouldBe([7, 6, 5, 4, 3, 2, 1, -2]); + } + + function it_reverses_numbers() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $this->reverse()->toNativeNumbers()->shouldBe([5, 4, 3, 2, 1]); + } + + function it_invokes_callback_for_each_number(CallableMock $callback) + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $expect = static fn(int $expected) => Argument::that(fn(NumberValue $n): bool => $n->toNumber() === $expected); + $callback->__invoke($expect(1))->shouldBeCalled(); + $callback->__invoke($expect(2))->shouldBeCalled(); + $callback->__invoke($expect(3))->shouldBeCalled(); + $callback->__invoke($expect(4))->shouldBeCalled(); + $callback->__invoke($expect(5))->shouldBeCalled(); + + $this->each($callback->getWrappedObject()); + } + + function it_joins_numbers_array_value() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $this->join(PlainNumbersArray::just(6, 7, 8)) + ->toNativeNumbers() + ->shouldBe([1, 2, 3, 4, 5, 6, 7, 8]); + + $this->join(Wrap::array([PlainNumber::from(6), PlainNumber::from(7)])) + ->toNativeNumbers() + ->shouldBe([1, 2, 3, 4, 5, 6, 7]); + } + + function it_slices_numbers_array() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $this->slice(0, 2)->toNativeNumbers()->shouldBe([1, 2]); + $this->slice(1, 2)->toNativeNumbers()->shouldBe([2, 3]); + $this->slice(-2, 2)->toNativeNumbers()->shouldBe([4, 5]); + } + + function it_splices_numbers_array() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5, 6, 7]); + + $this->splice(0, 4)->toNativeNumbers()->shouldBe([5, 6, 7]); + $this->splice(2, 3)->toNativeNumbers()->shouldBe([1, 2, 6, 7]); + $this->splice(-4, 3)->toNativeNumbers()->shouldBe([1, 2, 3, 7]); + $this->splice(-4, 3, PlainNumbersArray::just(11, 12)) + ->toNativeNumbers() + ->shouldBe([1, 2, 3, 11, 12, 7]); + } + + function it_resolves_unique_numbers() + { + $this->beConstructedThrough('just', [1, 2, 2, 3, 3.0, 4, 4, 4, 5]); + + $this->unique()->toNativeNumbers()->shouldBe([1, 2, 3, 3.0, 4, 5]); + } + + function it_resolves_unique_numbers_with_comparator() + { + $this->beConstructedThrough('just', [1, 2, 2, 3, 3.0, 4, 4, 4.3, 5, 5.9]); + + $this->unique(CompareAsInt::asc()) + ->toNativeNumbers() + ->shouldBe([1, 2, 3, 4, 5]); + } + + function it_resolves_diff() + { + $this->beConstructedThrough('just', [1, 2, 2, 3, 3.0, 4, 4, 4, 5]); + + $this->diff(PlainNumbersArray::just(2, 3, 4))->toNativeNumbers()->shouldBe([1, 3.0, 5]); + } + + function it_resolves_diff_by_comparator() + { + $this->beConstructedThrough('just', [1, 2, 2, 3, 3.0, 4, 4, 4.6, 5, 5.9]); + + $this->diff(PlainNumbersArray::just(2, 3, 4), CompareAsInt::asc())->toNativeNumbers()->shouldBe([1, 5, 5.9]); + } + + function it_resolves_intersect() + { + $this->beConstructedThrough('just', [1, 2, 2, 3, 3.0, 4, 4, 4, 5]); + + $this->intersect(PlainNumbersArray::just(2, 3, 4))->toNativeNumbers()->shouldBe([2, 2, 3, 4, 4, 4]); + } + + function it_resolves_intersect_by_comparator() + { + $this->beConstructedThrough('just', [1, 2, 2, 3, 3.0, 4, 4, 4.6, 5, 5.9]); + + $this->intersect(PlainNumbersArray::just(2, 3, 4), CompareAsInt::asc()) + ->toNativeNumbers() + ->shouldBe([2, 2, 3, 3.0, 4, 4, 4.6]); + } + + function it_shuffles_numbers() + { + $numbers = range(1, 1000); + $this->beConstructedThrough('just', [...$numbers]); + + $shuffled = $this->shuffle(); + $shuffled->beAnInstanceOf(PlainNumbersArray::class); + $shuffled->toNativeNumbers()->shouldNotBe($numbers); + $shuffled->diff($this)->toNativeNumbers()->shouldBe([]); + } + + function it_unshift_value() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $new = $this->unshift(PlainNumber::from(6)); + $new->toNativeNumbers()->shouldBe([6, 1, 2, 3, 4, 5]); + } + + function it_shifts_value() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $new = $this->getWrappedObject()->shift($value); + + if (!$value instanceof NumberValue || $value->toNumber() !== 1) { + throw new FailureException('Expected to shift 1'); + } + + if ($new->toNativeNumbers() !== [2, 3, 4, 5]) { + throw new FailureException('Expected array 2, 3, 4, 5'); + } + } + + function it_pushes_value() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $new = $this->push(PlainNumber::from(6)); + $new->toNativeNumbers()->shouldBe([1, 2, 3, 4, 5, 6]); + } + + function it_pops_value() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $new = $this->getWrappedObject()->pop($value); + + if (!$value instanceof NumberValue || $value->toNumber() !== 5) { + throw new FailureException('Expected to pop 5'); + } + + if ($new->toNativeNumbers() !== [1, 2, 3, 4]) { + throw new FailureException('Expected array 1, 2, 3, 4'); + } + } + + function it_reduces_numbers() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $this->reduce(fn(int $sum, NumberValue $number) => $sum + $number->toNumber(), 0) + ->shouldBe(15); + } + + function it_returns_first() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $this->first()->toNumber()->shouldBe(1); + } + + function it_returns_last() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $this->last()->toNumber()->shouldBe(5); + } + + function it_returns_null_as_first_and_last_when_empty() + { + $this->beConstructedThrough('just', []); + + $this->first()->shouldBeNull(); + $this->last()->shouldBeNull(); + } + + function it_finds_first() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $this->find($this->isEven())->toNumber()->shouldBe(2); + } + + function it_finds_last() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $this->findLast($this->isEven())->toNumber()->shouldBe(4); + } + + function it_returns_null_when_not_found() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $this->find($this->isGreater(5))->shouldBeNull(); + $this->findLast($this->isGreater(5))->shouldBeNull(); + } + + function it_resolves_any_and_every_condition() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $this->any($this->isEven())->shouldBe(true); + $this->any($this->isGreater(4))->shouldBe(true); + $this->any($this->isGreater(5))->shouldBe(false); + + + $this->every($this->isEven())->shouldBe(false); + $this->every($this->isGreater(1))->shouldBe(false); + $this->every($this->isGreater(0))->shouldBe(true); + } + + function it_resolves_that_array_has_element_strict() + { + $two = PlainNumber::from(2); + $numbers = new JustArray([PlainNumber::from(1), $two]); + $this->beConstructedThrough('fromArrayable', [$numbers]); + + $this->hasElement($two)->shouldBe(true); + $this->hasElement(PlainNumber::from(2))->shouldBe(false); + } + + function it_is_countable() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $this->count()->shouldBe(5); + } + + function it_is_arrayable() + { + $one = PlainNumber::from(1); + $two = PlainNumber::from(2); + $numbers = new JustArray([$one, $two]); + $this->beConstructedThrough('fromArrayable', [$numbers]); + + $this->toArray()->shouldBe([$one, $two]); + } + + function it_is_iterable() + { + $one = PlainNumber::from(1); + $two = PlainNumber::from(2); + $numbers = new JustArray([$one, $two]); + $this->beConstructedThrough('fromArrayable', [$numbers]); + + $this->shouldIterateLike([$one, $two]); + } + + function it_has_immutable_array_access() + { + $one = PlainNumber::from(1); + $two = PlainNumber::from(2); + $numbers = new JustArray([$one, $two]); + $this->beConstructedThrough('fromArrayable', [$numbers]); + + $this[0]->shouldBe($one); + $this[1]->shouldBe($two); + $this->offsetExists(0)->shouldBe(true); + $this->offsetExists(2)->shouldBe(false); + + $this->shouldThrow(BadMethodCallException::class)->during('offsetSet', [1, PlainNumber::from(3)]); + $this->shouldThrow(BadMethodCallException::class)->during('offsetUnset', [1]); + } + + function it_implodes_to_string_value() + { + $this->beConstructedThrough('just', [1, 2.5, 3.6, 4, 5]); + + $this->implode(', ')->toString()->shouldBe('1, 2.5, 3.6, 4, 5'); + } + + function it_is_not_empty_when_has_number() + { + $this->beConstructedThrough('just', [0]); + + $this->isEmpty()->shouldBe(false); + } + + function it_is_empty_when_no_numbers() + { + $this->beConstructedThrough('just', []); + + $this->isEmpty()->shouldBe(true); + } + + function it_can_be_casted_to_association() + { + $one = PlainNumber::from(1); + $two = PlainNumber::from(2); + $three = PlainNumber::from(3); + $four = PlainNumber::from(4); + $numbers = new JustArray([$one, $two, $three, $four]); + $this->beConstructedThrough('fromArrayable', [$numbers]); + + $this->toAssocValue()->filter($this->isEven())->toAssocArray()->shouldBe([1 => $two, 3 => $four]); + } + + function it_can_be_casted_to_strings_array() + { + $this->beConstructedThrough('just', [1, 2.5, 3.6, 4, 5]); + + $this->toStringsArray()->toNativeStrings()->shouldBe(['1', '2.5', '3.6', '4', '5']); + } + + private function isEven(): Closure + { + return static fn(NumberValue $number) => $number->toNumber() % 2 === 0; + } + + private function isGreater(int $than): Closure + { + return static fn(NumberValue $number) => $number->toNumber() > $than; + } +} diff --git a/src/GenericArray.php b/src/GenericArray.php deleted file mode 100644 index 5edb4eb..0000000 --- a/src/GenericArray.php +++ /dev/null @@ -1,449 +0,0 @@ - - */ -abstract class GenericArray implements ArrayValue -{ - /** @return Arrayable */ - abstract protected function items(): Arrayable; - - /** - * @template TNewValue - * @param Arrayable $items - * @return static - */ - abstract public static function new(Arrayable $items): self; - - /** - * @template TNewValue - * @param callable(TValue $value):TNewValue $transformer - * @phpstan-return static - */ - public function map(callable $transformer): self - { - return static::new(new Map($this->items(), $transformer)); - } - - /** - * @template TNewValue - * @param callable(TValue $value):iterable $transformer - * @phpstan-return static - */ - public function flatMap(callable $transformer): self - { - return static::new(new FlatMap($this->items(), $transformer)); - } - - /** - * @template TNewKey - * @phpstan-param callable(TValue $value):TNewKey $reducer - * @phpstan-return AssocValue> - */ - public function groupBy(callable $reducer): AssocValue - { - /** @phpstan-var array> $groups */ - $groups = []; - - foreach ($this->items()->toArray() as $item) { - /** @phpstan-var TNewKey $key */ - $key = $reducer($item); - $groups[$key][] = $item; - } - - /** @phpstan-var array> $groupsWrapped */ - $groupsWrapped = array_map([static::class, 'new'], $groups); - - return Wrap::assocArray($groupsWrapped); - } - - /** - * @phpstan-return static> - */ - public function chunk(int $size): self - { - return static::new(new Chunk($this->items(), $size)); - } - - /** - * @phpstan-return static - */ - public function filterEmpty(): self - { - return $this->filter(Filters::notEmpty()); - } - - /** - * @param callable(TValue $value):bool $filter - * @phpstan-return static - */ - public function filter(callable $filter): self - { - return static::new(new Filter($this->items(), $filter)); - } - - /** - * @param callable(TValue $leftValue, TValue $rightValue):int $comparator - * @phpstan-return static - */ - public function sort(callable $comparator): self - { - return static::new(new Sort($this->items(), $comparator)); - } - - /** - * @param callable(TValue $value):void $callback - * @phpstan-return static - */ - public function each(callable $callback): self - { - foreach ($this->items()->toArray() as $item) { - $callback($item); - } - - return $this; - } - - /** - * @phpstan-return static - */ - public function reverse(): self - { - return static::new(new Reverse($this->items())); - } - - /** - * @phpstan-param ArrayValue $other - * @phpstan-return static - */ - public function join(ArrayValue $other): self - { - return static::new(new Join($this->items(), $other)); - } - - /** - * @phpstan-return static - */ - public function slice(int $offset, int $length): self - { - return static::new(new Slice($this->items(), $offset, $length)); - } - - /** - * @phpstan-param ArrayValue $replacement - * @phpstan-return static - */ - public function splice(int $offset, int $length, ?ArrayValue $replacement = null): self - { - return static::new(new Splice($this->items(), $offset, $length, $replacement ?? new JustArray([]))); - } - - /** - * @param callable(TValue $valueA, TValue $valueB):int | null $comparator - * @phpstan-return static - */ - public function unique(?callable $comparator = null): self - { - if ($comparator === null) { - return static::new(new UniqueByString($this->items())); - } - - return static::new(new UniqueByComparator($this->items(), $comparator)); - } - - /** - * @phpstan-param static $other - * @param (callable(TValue $valueA, TValue $valueB):int)|null $comparator - * @phpstan-return static - */ - public function diff(ArrayValue $other, ?callable $comparator = null): self - { - if ($comparator === null) { - return static::new(new DiffByString($this->items(), $other)); - } - - return static::new(new DiffByComparator($this->items(), $other, $comparator)); - } - - /** - * @phpstan-param static $other - * @param (callable(TValue $valueA, TValue $valueB):int)|null $comparator - * @phpstan-return static - */ - public function intersect(ArrayValue $other, ?callable $comparator = null): self - { - if ($comparator === null) { - return static::new(new IntersectByString($this->items(), $other)); - } - - return static::new(new IntersectByComparator($this->items(), $other, $comparator)); - } - - /** - * @phpstan-return static - */ - public function shuffle(): self - { - return static::new(new Shuffle($this->items())); - } - - // adders and removers - - /** - * @phpstan-param TValue $value - * @phpstan-return static - */ - public function unshift($value): self - { - $items = $this->toArray(); - array_unshift($items, $value); - - return static::new(new JustArray($items)); - } - - /** - * @phpstan-param TValue $value - * @phpstan-return static - */ - public function shift(&$value = null): self - { - $items = $this->toArray(); - $value = array_shift($items); - - return static::new(new JustArray($items));; - } - - /** - * @phpstan-param TValue $value - * @phpstan-return static - */ - public function push($value): self - { - $items = $this->toArray(); - $items[] = $value; - - return static::new(new JustArray($items)); - } - - /** - * @phpstan-param TValue $value - * @phpstan-return static - */ - public function pop(&$value = null): self - { - $items = $this->toArray(); - $value = array_pop($items); - - return static::new(new JustArray($items)); - } - - // finalizers - - /** - * @template TNewValue - * @param callable(TNewValue $reduced, TValue $value):TNewValue $transformer - * @phpstan-param TNewValue $start - * @phpstan-return TNewValue - */ - public function reduce(callable $transformer, $start) - { - return array_reduce($this->items()->toArray(), $transformer, $start); - } - - /** - * @phpstan-return ?TValue - */ - public function first() - { - return $this->items()->toArray()[0] ?? null; - } - - /** - * @phpstan-return ?TValue - */ - public function last() - { - $count = $this->count(); - - return $count > 0 ? $this->items()->toArray()[$count - 1] : null; - } - - /** - * @param callable(TValue $value):bool $filter - * @phpstan-return ?TValue - */ - public function find(callable $filter) - { - foreach ($this->items()->toArray() as $item) { - if ($filter($item)) { - return $item; - } - } - - return null; - } - - /** - * @param callable(TValue $value):bool $filter - * @phpstan-return ?TValue - */ - public function findLast(callable $filter) - { - foreach (array_reverse($this->items()->toArray()) as $item) { - if ($filter($item)) { - return $item; - } - } - - return null; - } - - public function hasElement($element): bool - { - return in_array($element, $this->items()->toArray(), true); - } - - /** - * @param callable(TValue $value):bool $filter - */ - public function any(callable $filter): bool - { - foreach ($this->items()->toArray() as $item) { - if ($filter($item)) { - return true; - } - } - - return false; - } - - /** - * @param callable(TValue $value):bool $filter - */ - public function every(callable $filter): bool - { - foreach ($this->items()->toArray() as $item) { - if (!$filter($item)) { - return false; - } - } - - return true; - } - - public function count(): int - { - return count($this->items()->toArray()); - } - - /** - * @phpstan-return TValue[] - */ - public function toArray(): array - { - return $this->items()->toArray(); - } - - /** - * @phpstan-return ArrayIterator - */ - public function getIterator(): ArrayIterator - { - return new ArrayIterator($this->items()->toArray()); - } - - /** - * @param int $offset - */ - public function offsetExists($offset): bool - { - return isset($this->items()->toArray()[$offset]); - } - - /** - * @param int $offset - * @return ?TValue - */ - public function offsetGet($offset) - { - return $this->items()->toArray()[$offset]; - } - - /** - * @param int $offset - * @phpstan-param TValue $value - * @throws BadMethodCallException For immutable types. - */ - public function offsetSet($offset, $value): void - { - throw new BadMethodCallException('ArrayValue is immutable'); - } - - /** - * @param int $offset - * @throws BadMethodCallException For immutable types. - */ - public function offsetUnset($offset): void - { - throw new BadMethodCallException('ArrayValue is immutable'); - } - - public function implode(string $glue): StringValue - { - return Wrap::string(implode($glue, $this->toArray())); - } - - /** - * @phpstan-return static - */ - public function notEmpty(): self - { - return $this->filter(Filters::notEmpty()); - } - - public function isEmpty(): bool - { - return $this->items()->toArray() === []; - } - - /** - * @phpstan-return AssocValue - */ - public function toAssocValue(): AssocValue - { - return new AssocArray(new Associate($this->items())); - } - - public function toStringsArray(): StringsArray - { - return Wrap::stringsArray($this->items()->toArray()); - } -} diff --git a/src/NumberValue.php b/src/NumberValue.php index b9fb451..df1a32b 100644 --- a/src/NumberValue.php +++ b/src/NumberValue.php @@ -4,14 +4,6 @@ interface NumberValue extends Value, Numberable { - public function toInteger(): int; - - public function toFloat(): float; - - public function toStringValue(): StringValue; - - public function format(int $decimals = 0, string $separator = '.' , string $thousandsSeparator = ','): StringValue; - // comparators /** @@ -33,6 +25,8 @@ public function divide(Numberable $other): NumberValue; public function abs(): NumberValue; + public function modulo(Numberable $divider): NumberValue; + // rounding public function round(int $precision = 0, ?int $roundMode = null): NumberValue; @@ -46,6 +40,18 @@ public function calculate(callable $formula): NumberValue; // value - /** @return bool false when 0, true otherwise */ + /** @return bool false when 0 or 0.0, true otherwise */ public function isEmpty(): bool; + + // casting + + public function format(int $decimals = 0, string $separator = '.' , string $thousandsSeparator = ','): StringValue; + + public function toStringValue(): StringValue; + + public function toInteger(): int; + + public function toFloat(): float; + + public function __toString(): string; } diff --git a/src/Numberable/Average.php b/src/Numberable/Average.php index 0dcd1d2..59b0a1b 100644 --- a/src/Numberable/Average.php +++ b/src/Numberable/Average.php @@ -2,10 +2,10 @@ namespace GW\Value\Numberable; +use DivisionByZeroError; use GW\Value\Arrayable; use GW\Value\Arrayable\JustArray; use GW\Value\Numberable; -use LogicException; final class Average implements Numberable { @@ -24,7 +24,7 @@ public function toNumber() $terms = $this->terms->toArray(); $count = count($terms); if ($count === 0) { - throw new LogicException('Cannot calculate avg number from empty set'); + throw new DivisionByZeroError('Cannot calculate avg number from empty set'); } return (new Sum(new JustArray($terms)))->toNumber() / $count; diff --git a/src/Numberable/CompareAsInt.php b/src/Numberable/CompareAsInt.php new file mode 100644 index 0000000..99fd403 --- /dev/null +++ b/src/Numberable/CompareAsInt.php @@ -0,0 +1,30 @@ +direction = $direction; + } + + public static function asc(): self + { + return new self(1); + } + + public static function desc(): self + { + return new self(-1); + } + + public function __invoke(Numberable $left, Numberable $right): int + { + return $this->direction * ((int)$left->toNumber() <=> (int)$right->toNumber()); + } +} diff --git a/src/Numberable/Divide.php b/src/Numberable/Divide.php index 81ce581..64feac0 100644 --- a/src/Numberable/Divide.php +++ b/src/Numberable/Divide.php @@ -19,14 +19,6 @@ public function __construct(Numberable $dividend, Numberable ...$divisors) public function toNumber() { - return array_reduce( - $this->divisors, - /** - * @param int|float $fraction - * @return int|float - */ - static fn($fraction, Numberable $divisor) => $fraction / $divisor->toNumber(), - $this->dividend->toNumber() - ); + return array_reduce($this->divisors, new DivideReducer(), $this->dividend->toNumber()); } } diff --git a/src/Numberable/DivideReducer.php b/src/Numberable/DivideReducer.php new file mode 100644 index 0000000..322883f --- /dev/null +++ b/src/Numberable/DivideReducer.php @@ -0,0 +1,23 @@ +toNumber(); + if ($divisorNum === 0) { + throw new DivisionByZeroError('Division by zero'); + } + + return $fraction / $divisorNum; + } +} diff --git a/src/Numberable/JustNumber.php b/src/Numberable/JustNumber.php new file mode 100644 index 0000000..53698fa --- /dev/null +++ b/src/Numberable/JustNumber.php @@ -0,0 +1,23 @@ +number = $number; + } + + /** @return int|float */ + public function toNumber() + { + return $this->number; + } +} diff --git a/src/Numberable/JustNumbers.php b/src/Numberable/JustNumbers.php new file mode 100644 index 0000000..64b5997 --- /dev/null +++ b/src/Numberable/JustNumbers.php @@ -0,0 +1,31 @@ + + */ +final class JustNumbers implements Arrayable +{ + /** @var Arrayable */ + private Arrayable $numbers; + + /** @param int|float ...$numbers */ + public function __construct(...$numbers) + { + $this->numbers = new Map(new JustArray($numbers), new ToNumberValue()); + } + + /** + * @return NumberValue[] + */ + public function toArray(): array + { + return $this->numbers->toArray(); + } +} diff --git a/src/Numberable/Max.php b/src/Numberable/Max.php index 7505aa4..af46445 100644 --- a/src/Numberable/Max.php +++ b/src/Numberable/Max.php @@ -5,7 +5,6 @@ use GW\Value\Arrayable; use GW\Value\Arrayable\Map; use GW\Value\Numberable; -use GW\Value\NumberValue; use LogicException; use function count; @@ -14,7 +13,7 @@ final class Max implements Numberable /** @var Arrayable */ private Arrayable $numbers; - /** @param Arrayable $numbers */ + /** @param Arrayable $numbers */ public function __construct(Arrayable $numbers) { $this->numbers = new Map($numbers, new ToScalarNumber()); diff --git a/src/Numberable/Min.php b/src/Numberable/Min.php index 697345e..daac68c 100644 --- a/src/Numberable/Min.php +++ b/src/Numberable/Min.php @@ -15,7 +15,7 @@ final class Min implements Numberable /** @var Arrayable */ private Arrayable $numbers; - /** @param Arrayable $numbers */ + /** @param Arrayable $numbers */ public function __construct(Arrayable $numbers) { $this->numbers = new Map($numbers, new ToScalarNumber()); diff --git a/src/Numberable/Modulo.php b/src/Numberable/Modulo.php new file mode 100644 index 0000000..ef9a091 --- /dev/null +++ b/src/Numberable/Modulo.php @@ -0,0 +1,22 @@ +dividend = $dividend; + $this->divisor = $divisor; + } + + public function toNumber(): int + { + return $this->dividend->toNumber() % $this->divisor->toNumber(); + } +} diff --git a/src/Numberable/ToNumberValue.php b/src/Numberable/ToNumberValue.php new file mode 100644 index 0000000..8833549 --- /dev/null +++ b/src/Numberable/ToNumberValue.php @@ -0,0 +1,37 @@ +toNumber(); } diff --git a/src/NumbersArray.php b/src/NumbersArray.php index 7714b72..76cba40 100644 --- a/src/NumbersArray.php +++ b/src/NumbersArray.php @@ -54,7 +54,7 @@ public function flatMap(callable $transformer): ArrayValue; /** * @template TNewKey * @param callable(NumberValue):TNewKey $reducer - * @phpstan-return AssocValue> + * @phpstan-return AssocValue */ public function groupBy(callable $reducer): AssocValue; @@ -98,28 +98,28 @@ public function offsetSet($offset, $value): void; public function offsetUnset($offset): void; /** - * @phpstan-param Arrayable $other + * @phpstan-param ArrayValue $other */ - public function join(Arrayable $other): NumbersArray; + public function join(ArrayValue $other): NumbersArray; public function slice(int $offset, int $length): NumbersArray; /** - * @phpstan-param Arrayable|null $replacement + * @phpstan-param ArrayValue|null $replacement */ - public function splice(int $offset, int $length, ?Arrayable $replacement = null): NumbersArray; + public function splice(int $offset, int $length, ?ArrayValue $replacement = null): NumbersArray; /** - * @phpstan-param Arrayable $other + * @phpstan-param ArrayValue $other * @param (callable(NumberValue,NumberValue):int)|null $comparator */ - public function diff(Arrayable $other, ?callable $comparator = null): NumbersArray; + public function diff(ArrayValue $other, ?callable $comparator = null): NumbersArray; /** - * @phpstan-param Arrayable $other + * @phpstan-param ArrayValue $other * @param (callable(NumberValue,NumberValue):int)|null $comparator */ - public function intersect(Arrayable $other, ?callable $comparator = null): NumbersArray; + public function intersect(ArrayValue $other, ?callable $comparator = null): NumbersArray; /** * @template TNewValue @@ -129,6 +129,11 @@ public function intersect(Arrayable $other, ?callable $comparator = null): Numbe */ public function reduce(callable $transformer, $start); + /** + * @param callable(NumberValue $reduced, NumberValue $item):NumberValue $transformer + */ + public function reduceNumber(callable $transformer, NumberValue $start): NumberValue; + public function implode(string $glue): StringValue; public function notEmpty(): NumbersArray; diff --git a/src/PlainNumber.php b/src/PlainNumber.php index a823d5c..84a3c70 100644 --- a/src/PlainNumber.php +++ b/src/PlainNumber.php @@ -3,13 +3,17 @@ namespace GW\Value; use GW\Value\Numberable\Absolute; +use GW\Value\Numberable\Add; use GW\Value\Numberable\Ceil; use GW\Value\Numberable\Divide; use GW\Value\Numberable\Floor; +use GW\Value\Numberable\JustInteger; +use GW\Value\Numberable\JustNumber; +use GW\Value\Numberable\Modulo; use GW\Value\Numberable\Multiply; use GW\Value\Numberable\Round; use GW\Value\Numberable\Subtract; -use GW\Value\Numberable\Add; +use function is_int; use function number_format; use const PHP_FLOAT_DIG; @@ -22,26 +26,15 @@ public function __construct(Numberable $number) $this->number = $number; } - public function toInteger(): int - { - return (int)$this->number->toNumber(); - } - - public function toFloat(): float - { - return (float)$this->number->toNumber(); - } - - public function toStringValue(): StringValue + /** @param int|float $number */ + public static function from($number): self { - return $this->format(PHP_FLOAT_DIG, '.', ''); + return new self(new JustNumber($number)); } public function format(int $decimals = 0, string $separator = '.', string $thousandsSeparator = ','): StringValue { - return Wrap::string(number_format($this->number->toNumber(), $decimals, $separator, $thousandsSeparator)) - ->trimRight('0') - ->trimRight('.'); + return Wrap::string(number_format($this->number->toNumber(), $decimals, $separator, $thousandsSeparator)); } public function compare(Numberable $other): int @@ -74,6 +67,11 @@ public function divide(Numberable $other): NumberValue return new self(new Divide($this->number, $other)); } + public function modulo(Numberable $divider): NumberValue + { + return new self(new Modulo($this->number, $divider)); + } + public function abs(): NumberValue { return new self(new Absolute($this->number)); @@ -110,4 +108,31 @@ public function toNumber() { return $this->number->toNumber(); } + + public function toInteger(): int + { + return (int)$this->number->toNumber(); + } + + public function toFloat(): float + { + return (float)$this->number->toNumber(); + } + + public function toStringValue(): StringValue + { + $number = $this->number->toNumber(); + if (is_int($number)) { + return Wrap::string((string)$number); + } + + $value = $this->format(PHP_FLOAT_DIG, '.', '')->trimRight('0'); + + return $value->endsWith('.') ? $value->postfix('0') : $value; + } + + public function __toString(): string + { + return $this->toStringValue()->toString(); + } } diff --git a/src/PlainNumbersArray.php b/src/PlainNumbersArray.php index 7263d96..a8643bf 100644 --- a/src/PlainNumbersArray.php +++ b/src/PlainNumbersArray.php @@ -2,57 +2,362 @@ namespace GW\Value; -use GW\Value\Arrayable\Cache; +use BadMethodCallException; use GW\Value\Numberable\Average; +use GW\Value\Numberable\JustNumbers; use GW\Value\Numberable\Max; use GW\Value\Numberable\Min; use GW\Value\Numberable\Sum; +use GW\Value\Numberable\ToScalarNumber; +use Traversable; -/** - * @extends GenericArray - */ -final class PlainNumbersArray extends GenericArray +final class PlainNumbersArray implements NumbersArray { - /** @phpstan-var Arrayable */ - private Arrayable $numbers; + /** @var ArrayValue */ + private ArrayValue $numbers; - /** @param Arrayable $numbers */ - public function __construct(Arrayable $numbers) + /** @param ArrayValue $numbers */ + public function __construct(ArrayValue $numbers) { - $this->numbers = new Cache($numbers); + $this->numbers = $numbers; } - /** - * @param Arrayable $items - */ - public static function new(Arrayable $items): self + /** @param Arrayable $numbers */ + public static function fromArrayable(Arrayable $numbers): self { - return new self($items); + return new self(new PlainArray($numbers)); } - /** @return Arrayable */ - protected function items(): Arrayable + /** @param int|float ...$numbers */ + public static function just(...$numbers): self { - return $this->numbers; + return self::fromArrayable(new JustNumbers(...$numbers)); } public function sum(): NumberValue { - return new PlainNumber(new Sum($this->numbers)); + return new PlainNumber(new Sum($this)); } public function average(): NumberValue { - return new PlainNumber(new Average($this->numbers)); + return new PlainNumber(new Average($this)); } public function min(): NumberValue { - return new PlainNumber(new Min($this->numbers)); + return new PlainNumber(new Min($this)); } public function max(): NumberValue { - return new PlainNumber(new Max($this->numbers)); + return new PlainNumber(new Max($this)); + } + + /** @return int[]|float[] */ + public function toNativeNumbers(): array + { + return $this->map(new ToScalarNumber())->toArray(); + } + + public function getIterator(): Traversable + { + return $this->numbers->getIterator(); + } + + public function count(): int + { + return $this->numbers->count(); + } + + /** + * @param callable(NumberValue):void $callback + */ + public function each(callable $callback): NumbersArray + { + return new self($this->numbers->each($callback)); + } + + /** + * @param (callable(NumberValue $valueA, NumberValue $valueB):int)|null $comparator + */ + public function unique(?callable $comparator = null): NumbersArray + { + return new self($this->numbers->unique($comparator)); + } + + /** @return NumberValue[] */ + public function toArray(): array + { + return $this->numbers->toArray(); + } + + /** + * @param callable(NumberValue):bool $filter + */ + public function filter(callable $filter): NumbersArray + { + return new self($this->numbers->filter($filter)); + } + + public function filterEmpty(): NumbersArray + { + return new self($this->numbers->filterEmpty()); + } + + /** + * @template TNewValue + * @param callable(NumberValue):TNewValue $transformer + * @return ArrayValue + */ + public function map(callable $transformer): ArrayValue + { + return $this->numbers->map($transformer); + } + + /** + * @template TNewValue + * @param callable(NumberValue):iterable $transformer + * @return ArrayValue + */ + public function flatMap(callable $transformer): ArrayValue + { + return $this->numbers->flatMap($transformer); + } + + /** + * @template TNewKey + * @param callable(NumberValue):TNewKey $reducer + * @return AssocValue + */ + public function groupBy(callable $reducer): AssocValue + { + return $this->numbers->groupBy($reducer)->map(fn(ArrayValue $numbers) => new self($numbers)); + } + + /** + * @return ArrayValue> + */ + public function chunk(int $size): ArrayValue + { + return $this->numbers->chunk($size); + } + + public function sort(callable $comparator): NumbersArray + { + return new self($this->numbers->sort($comparator)); + } + + public function shuffle(): NumbersArray + { + return new self($this->numbers->shuffle()); + } + + public function reverse(): NumbersArray + { + return new self($this->numbers->reverse()); + } + + /** + * @param NumberValue $value + */ + public function unshift($value): NumbersArray + { + return new self($this->numbers->unshift($value)); + } + + /** + * @param NumberValue $value + */ + public function shift(&$value = null): NumbersArray + { + return new self($this->numbers->shift($value)); + } + + /** + * @param NumberValue $value + */ + public function push($value): NumbersArray + { + return new self($this->numbers->push($value)); + } + + /** + * @param NumberValue $value + */ + public function pop(&$value = null): NumbersArray + { + return new self($this->numbers->pop($value)); + } + + /** + * @param int $offset + */ + public function offsetExists($offset): bool + { + return $this->numbers->offsetExists($offset); + } + + /** + * @param int $offset + */ + public function offsetGet($offset): NumberValue + { + return $this->numbers->offsetGet($offset); + } + + /** + * @param int $offset + * @phpstan-param NumberValue $value + * @throws BadMethodCallException For immutable types. + */ + public function offsetSet($offset, $value): void + { + $this->numbers->offsetSet($offset, $value); + } + + /** + * @param int $offset + * @throws BadMethodCallException For immutable types. + */ + public function offsetUnset($offset): void + { + $this->numbers->offsetUnset($offset); + } + + /** + * @param ArrayValue $other + */ + public function join(ArrayValue $other): NumbersArray + { + return new self($this->numbers->join($other)); + } + + public function slice(int $offset, int $length): NumbersArray + { + return new self($this->numbers->slice($offset, $length)); + } + + /** + * @param ArrayValue|null $replacement + */ + public function splice(int $offset, int $length, ?ArrayValue $replacement = null): NumbersArray + { + return new self($this->numbers->splice($offset, $length, $replacement)); + } + + /** + * @param ArrayValue $other + * @param (callable(NumberValue,NumberValue):int)|null $comparator + */ + public function diff(ArrayValue $other, ?callable $comparator = null): NumbersArray + { + return new self($this->numbers->diff($other, $comparator)); + } + + /** + * @param ArrayValue $other + * @param (callable(NumberValue,NumberValue):int)|null $comparator + */ + public function intersect(ArrayValue $other, ?callable $comparator = null): NumbersArray + { + return new self($this->numbers->intersect($other, $comparator)); + } + + /** + * @template TNewValue + * @param callable(TNewValue, NumberValue):TNewValue $transformer + * @param TNewValue $start + * @return TNewValue + */ + public function reduce(callable $transformer, $start) + { + return $this->numbers->reduce($transformer, $start); + } + + /** + * @param callable(NumberValue $reduced, NumberValue $item):NumberValue $transformer + */ + public function reduceNumber(callable $transformer, NumberValue $start): NumberValue + { + return $this->reduce($transformer, $start); + } + + public function implode(string $glue): StringValue + { + return $this->numbers->implode($glue); + } + + public function notEmpty(): NumbersArray + { + return new self($this->numbers->notEmpty()); + } + + /** + * @return AssocValue + */ + public function toAssocValue(): AssocValue + { + return $this->numbers->toAssocValue(); + } + + public function toStringsArray(): StringsArray + { + return $this->numbers->toStringsArray(); + } + + public function first(): ?NumberValue + { + return $this->numbers->first(); + } + + public function last(): ?NumberValue + { + return $this->numbers->last(); + } + + /** + * @param callable(NumberValue):bool $filter + */ + public function find(callable $filter): ?NumberValue + { + return $this->numbers->find($filter); + } + + /** + * @param callable(NumberValue):bool $filter + */ + public function findLast(callable $filter): ?NumberValue + { + return $this->numbers->findLast($filter); + } + + /** + * @param NumberValue $element + */ + public function hasElement($element): bool + { + return $this->numbers->hasElement($element); + } + + /** + * @param callable(NumberValue):bool $filter + */ + public function any(callable $filter): bool + { + return $this->numbers->any($filter); + } + + /** + * @param callable(NumberValue):bool $filter + */ + public function every(callable $filter): bool + { + return $this->numbers->every($filter); + } + + public function isEmpty(): bool + { + return $this->numbers->isEmpty(); } } diff --git a/src/Stringable/ToStringValue.php b/src/Stringable/ToStringValue.php index c0a405a..41b513d 100644 --- a/src/Stringable/ToStringValue.php +++ b/src/Stringable/ToStringValue.php @@ -5,20 +5,22 @@ use GW\Value\StringValue; use GW\Value\Wrap; use InvalidArgumentException; +use function is_object; use function is_scalar; +use function method_exists; final class ToStringValue { public function __invoke(mixed $string): StringValue { - if (is_scalar($string)) { - return Wrap::string((string)$string); - } - if ($string instanceof StringValue) { return $string; } + if (is_scalar($string) || (is_object($string) && method_exists($string, '__toString'))) { + return Wrap::string((string)$string); + } + throw new InvalidArgumentException('StringsValue can contain only StringValue'); } } From f089a28ed5be95e11baaa5fe752529497f3912d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Sun, 7 Mar 2021 18:50:23 +0100 Subject: [PATCH 05/20] Ignore PhpStan groupBy and chunk :shrug: --- src/NumbersArray.php | 7 +------ src/PlainNumbersArray.php | 9 ++++++++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/NumbersArray.php b/src/NumbersArray.php index 76cba40..21bf1da 100644 --- a/src/NumbersArray.php +++ b/src/NumbersArray.php @@ -54,15 +54,10 @@ public function flatMap(callable $transformer): ArrayValue; /** * @template TNewKey * @param callable(NumberValue):TNewKey $reducer - * @phpstan-return AssocValue + * @phpstan-return AssocValue> */ public function groupBy(callable $reducer): AssocValue; - /** - * @phpstan-return ArrayValue> - */ - public function chunk(int $size): ArrayValue; - public function sort(callable $comparator): NumbersArray; public function shuffle(): NumbersArray; diff --git a/src/PlainNumbersArray.php b/src/PlainNumbersArray.php index a8643bf..b14cd35 100644 --- a/src/PlainNumbersArray.php +++ b/src/PlainNumbersArray.php @@ -129,14 +129,21 @@ public function flatMap(callable $transformer): ArrayValue * @template TNewKey * @param callable(NumberValue):TNewKey $reducer * @return AssocValue + * @phpstan-ignore-next-line shrug */ public function groupBy(callable $reducer): AssocValue { - return $this->numbers->groupBy($reducer)->map(fn(ArrayValue $numbers) => new self($numbers)); + // @phpstan-ignore-next-line shrug + return $this->numbers + // @phpstan-ignore-next-line shrug + ->groupBy($reducer) + // @phpstan-ignore-next-line shrug + ->map(static fn(ArrayValue $numbers) => new self($numbers)); } /** * @return ArrayValue> + * @phpstan-ignore-next-line shrug */ public function chunk(int $size): ArrayValue { From f80c00c2e03015c1b8a2c2627b88a87af4ca10cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Sun, 7 Mar 2021 21:03:49 +0100 Subject: [PATCH 06/20] Wrap number --- spec/PlainNumberSpec.php | 4 +++ spec/PlainNumbersArraySpec.php | 24 +++++++++++++--- spec/WrapSpec.php | 44 +++++++++++++++++++++++++++++- src/Numberable/Multiply.php | 10 +------ src/Numberable/MultiplyReducer.php | 17 ++++++++++++ src/Numberable/Subtract.php | 10 +------ src/Numberable/SubtractReducer.php | 17 ++++++++++++ src/Numberable/Sum.php | 10 +------ src/Numberable/SumReducer.php | 17 ++++++++++++ src/Wrap.php | 32 ++++++++++++++++++++++ 10 files changed, 153 insertions(+), 32 deletions(-) create mode 100644 src/Numberable/MultiplyReducer.php create mode 100644 src/Numberable/SubtractReducer.php create mode 100644 src/Numberable/SumReducer.php diff --git a/spec/PlainNumberSpec.php b/spec/PlainNumberSpec.php index 65e3f44..185a547 100644 --- a/spec/PlainNumberSpec.php +++ b/spec/PlainNumberSpec.php @@ -13,7 +13,9 @@ use PhpSpec\ObjectBehavior; use function acos; use function cos; +use function exp; use function sin; +use function sqrt; use const PHP_ROUND_HALF_DOWN; final class PlainNumberSpec extends ObjectBehavior @@ -316,5 +318,7 @@ function it_calculates_math_formulas() $this->divide(new JustInteger(100))->calculate(Math::asin())->toNumber()->shouldBe(asin(.90)); $this->calculate(Math::tan())->toNumber()->shouldBe(tan(90)); $this->divide(new JustInteger(100))->calculate(Math::atan())->toNumber()->shouldBe(atan(.90)); + $this->calculate(Math::exp())->toNumber()->shouldBe(exp(90)); + $this->calculate(Math::sqrt())->toNumber()->shouldBe(sqrt(90)); } } diff --git a/spec/PlainNumbersArraySpec.php b/spec/PlainNumbersArraySpec.php index dcd892e..0b02bb3 100644 --- a/spec/PlainNumbersArraySpec.php +++ b/spec/PlainNumbersArraySpec.php @@ -6,6 +6,7 @@ use Closure; use DivisionByZeroError; use GW\Value\Arrayable\JustArray; +use GW\Value\Numberable\Add; use GW\Value\Numberable\CompareAsInt; use GW\Value\Numberable\JustInteger; use GW\Value\Numberable\JustNumbers; @@ -107,9 +108,11 @@ function it_filters_zeros_as_empty_elements() { $this->beConstructedThrough('just', [1, 2, 3, 0, 4, 5, .0]); - $even = $this->filterEmpty(); - $even->shouldBeAnInstanceOf(PlainNumbersArray::class); - $even->toNativeNumbers()->shouldBe([1, 2, 3, 4, 5]); + $notEmpty = $this->filterEmpty(); + $notEmpty->shouldBeAnInstanceOf(PlainNumbersArray::class); + $notEmpty->toNativeNumbers()->shouldBe([1, 2, 3, 4, 5]); + + $this->notEmpty()->toNativeNumbers()->shouldBe([1, 2, 3, 4, 5]); } function it_maps_to_ArrayValue() @@ -277,7 +280,7 @@ function it_resolves_diff_by_comparator() { $this->beConstructedThrough('just', [1, 2, 2, 3, 3.0, 4, 4, 4.6, 5, 5.9]); - $this->diff(PlainNumbersArray::just(2, 3, 4), CompareAsInt::asc())->toNativeNumbers()->shouldBe([1, 5, 5.9]); + $this->diff(PlainNumbersArray::just(2, 3, 4), CompareAsInt::desc())->toNativeNumbers()->shouldBe([1, 5, 5.9]); } function it_resolves_intersect() @@ -361,6 +364,19 @@ function it_reduces_numbers() ->shouldBe(15); } + function it_reduces_to_number_value() + { + $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + + $this + ->reduceNumber( + fn(NumberValue $sum, NumberValue $number): NumberValue => $sum->add($number), + PlainNumber::from(0) + ) + ->toNumber() + ->shouldBe(15); + } + function it_returns_first() { $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); diff --git a/spec/WrapSpec.php b/spec/WrapSpec.php index af51670..9139da5 100644 --- a/spec/WrapSpec.php +++ b/spec/WrapSpec.php @@ -1,9 +1,15 @@ -beConstructedThrough('stringsArray', [['a', 'b', 'c']]); $this->shouldHaveType(StringsArray::class); } + + function it_should_return_NumberValue_of_scalar_number() + { + $this->beConstructedThrough('number', [123]); + $this->shouldHaveType(NumberValue::class); + } + + function it_should_return_NumberValue_of_Numberable() + { + $this->beConstructedThrough('number', [new JustInteger(123)]); + $this->shouldHaveType(NumberValue::class); + } + + function it_should_return_NumberValue_of_itself() + { + $this->beConstructedThrough('number', [PlainNumber::from(123)]); + $this->shouldHaveType(NumberValue::class); + } + + function it_should_return_NumbersArray_of_scalar_numbers() + { + $this->beConstructedThrough('numbersArray', [[1, 2, 3.0]]); + $this->shouldHaveType(NumbersArray::class); + } + + function it_should_return_NumbersArray_of_numberable() + { + $this->beConstructedThrough('numbersArray', [new JustArray([1, 2, 3.0])]); + $this->shouldHaveType(NumbersArray::class); + } + + function it_should_return_NumbersArray_of_itself() + { + $this->beConstructedThrough('numbersArray', [PlainNumbersArray::just(1, 2, 3.0)]); + $this->shouldHaveType(NumbersArray::class); + } } diff --git a/src/Numberable/Multiply.php b/src/Numberable/Multiply.php index 1d939fb..cc3235a 100644 --- a/src/Numberable/Multiply.php +++ b/src/Numberable/Multiply.php @@ -19,14 +19,6 @@ public function __construct(Numberable $factor, Numberable ...$factors) public function toNumber() { - return array_reduce( - $this->factors, - /** - * @param int|float $product - * @return int|float - */ - static fn($product, Numberable $next) => $product * $next->toNumber(), - $this->factor->toNumber() - ); + return array_reduce($this->factors, new MultiplyReducer(), $this->factor->toNumber()); } } diff --git a/src/Numberable/MultiplyReducer.php b/src/Numberable/MultiplyReducer.php new file mode 100644 index 0000000..ef22873 --- /dev/null +++ b/src/Numberable/MultiplyReducer.php @@ -0,0 +1,17 @@ +toNumber(); + } +} diff --git a/src/Numberable/Subtract.php b/src/Numberable/Subtract.php index 0590f06..eb602f9 100644 --- a/src/Numberable/Subtract.php +++ b/src/Numberable/Subtract.php @@ -20,14 +20,6 @@ public function __construct(Numberable $term, Numberable ...$terms) /** @return int|float */ public function toNumber() { - return array_reduce( - $this->terms, - /** - * @param int|float $difference - * @return int|float - */ - static fn($difference, Numberable $next) => $difference - $next->toNumber(), - $this->term->toNumber() - ); + return array_reduce($this->terms, new SubtractReducer(), $this->term->toNumber()); } } diff --git a/src/Numberable/SubtractReducer.php b/src/Numberable/SubtractReducer.php new file mode 100644 index 0000000..d74e627 --- /dev/null +++ b/src/Numberable/SubtractReducer.php @@ -0,0 +1,17 @@ +toNumber(); + } +} diff --git a/src/Numberable/Sum.php b/src/Numberable/Sum.php index d6449eb..986fe49 100644 --- a/src/Numberable/Sum.php +++ b/src/Numberable/Sum.php @@ -20,14 +20,6 @@ public function __construct(Arrayable $terms) /** @return int|float */ public function toNumber() { - return array_reduce( - $this->terms->toArray(), - /** - * @param int|float $sum - * @return int|float - */ - static fn($sum, Numberable $next) => $sum + $next->toNumber(), - 0 - ); + return array_reduce($this->terms->toArray(), new SumReducer(), 0); } } diff --git a/src/Numberable/SumReducer.php b/src/Numberable/SumReducer.php new file mode 100644 index 0000000..053c49e --- /dev/null +++ b/src/Numberable/SumReducer.php @@ -0,0 +1,17 @@ +toNumber(); + } +} diff --git a/src/Wrap.php b/src/Wrap.php index 6dfc566..21feb74 100644 --- a/src/Wrap.php +++ b/src/Wrap.php @@ -66,6 +66,38 @@ public static function stringsArray(array $strings = []): StringsArray return new PlainStringsArray(self::array($strings)); } + /** + * @param int|float|Numberable $number + */ + public static function number($number): NumberValue + { + if ($number instanceof NumberValue) { + return $number; + } + + if ($number instanceof Numberable) { + return new PlainNumber($number); + } + + return PlainNumber::from($number); + } + + /** + * @param int[]|float[]|Arrayable|NumbersArray $numbers + */ + public static function numbersArray($numbers): NumbersArray + { + if ($numbers instanceof NumbersArray) { + return $numbers; + } + + if ($numbers instanceof Arrayable) { + return PlainNumbersArray::fromArrayable($numbers); + } + + return PlainNumbersArray::just(...$numbers); + } + private function __construct() { // prohibits creation objects of this class From 0d402dc946a3b8cd63a50e796506afde52faedf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Sat, 22 May 2021 10:38:44 +0200 Subject: [PATCH 07/20] Support different kind of numerics --- spec/PlainNumberSpec.php | 80 +++++++++++++++++++++++++++++++- spec/PlainNumbersArraySpec.php | 40 +++++++++++++++- src/NumberValue.php | 35 ++++++++++---- src/Numberable/JustNumber.php | 12 +++-- src/Numberable/JustNumbers.php | 3 +- src/Numberable/ToNumberValue.php | 6 ++- src/NumbersArray.php | 30 ++++++------ src/PlainNumber.php | 38 ++++++++------- src/PlainNumbersArray.php | 4 +- src/Wrap.php | 8 +--- 10 files changed, 200 insertions(+), 56 deletions(-) diff --git a/spec/PlainNumberSpec.php b/spec/PlainNumberSpec.php index 185a547..4ba43ab 100644 --- a/spec/PlainNumberSpec.php +++ b/spec/PlainNumberSpec.php @@ -9,6 +9,7 @@ use GW\Value\Numberable\JustInteger; use GW\Value\Numberable\Math; use GW\Value\Numberable\Add; +use GW\Value\Numberable\Zero; use GW\Value\PlainNumber; use PhpSpec\ObjectBehavior; use function acos; @@ -29,6 +30,13 @@ function it_can_be_integer() $this->toFloat()->shouldBe(123.0); } + function it_can_be_created_from_integer() + { + $this->beConstructedThrough('from', [123]); + + $this->toNumber()->shouldBe(123); + } + function it_can_be_float() { $this->beConstructedWith(new JustFloat(123.66)); @@ -38,12 +46,40 @@ function it_can_be_float() $this->toFloat()->shouldBe(123.66); } - function it_compares_float_and_int_as_equal_just_like_scalars() + function it_can_be_created_from_float() + { + $this->beConstructedThrough('from', [123.66]); + + $this->toNumber()->shouldBe(123.66); + } + + function it_can_be_created_from_numeric_string() + { + $this->beConstructedThrough('from', ['123.66']); + + $this->toNumber()->shouldBe(123.66); + } + + function it_compares_int_with_numbers_just_like_scalars() { $this->beConstructedWith(new JustInteger(123)); $this->compare(new JustFloat(123.00))->shouldBe(0); + $this->compare(123)->shouldBe(0); + $this->compare(123.00)->shouldBe(0); + $this->compare('123.00')->shouldBe(0); + + $this->compare(122)->shouldBe(1); + $this->compare(124)->shouldBe(-1); + $this->compare(122.9)->shouldBe(1); + $this->compare(123.1)->shouldBe(-1); + $this->compare('122.9')->shouldBe(1); + $this->compare('123.1')->shouldBe(-1); + $this->equals(new JustFloat(123.00))->shouldBe(true); + $this->equals(123)->shouldBe(true); + $this->equals(123.00)->shouldBe(true); + $this->equals('123.00')->shouldBe(true); } function it_adds_integers() @@ -51,6 +87,8 @@ function it_adds_integers() $this->beConstructedWith(new JustInteger(123)); $this->add(new JustInteger(45))->toNumber()->shouldBe(168); + $this->add(45)->toNumber()->shouldBe(168); + $this->add('45')->toNumber()->shouldBe(168); } function it_adds_integer_and_float() @@ -58,6 +96,8 @@ function it_adds_integer_and_float() $this->beConstructedWith(new JustInteger(123)); $this->add(new JustFloat(.45))->toNumber()->shouldBe(123.45); + $this->add(.45)->toNumber()->shouldBe(123.45); + $this->add('.45')->toNumber()->shouldBe(123.45); } function it_adds_floats() @@ -65,6 +105,8 @@ function it_adds_floats() $this->beConstructedWith(new JustFloat(.1)); $this->add(new JustFloat(.1))->toNumber()->shouldBe(.2); + $this->add(.1)->toNumber()->shouldBe(.2); + $this->add('.1')->toNumber()->shouldBe(.2); } function it_adds_float_and_integer() @@ -72,6 +114,8 @@ function it_adds_float_and_integer() $this->beConstructedWith(new JustFloat(.1)); $this->add(new JustInteger(123))->toNumber()->shouldBe(123.1); + $this->add(123)->toNumber()->shouldBe(123.1); + $this->add('123')->toNumber()->shouldBe(123.1); } function it_subtracts_integers() @@ -79,6 +123,8 @@ function it_subtracts_integers() $this->beConstructedWith(new JustInteger(123)); $this->subtract(new JustInteger(45))->toNumber()->shouldBe(78); + $this->subtract(45)->toNumber()->shouldBe(78); + $this->subtract('45')->toNumber()->shouldBe(78); } function it_subtracts_integer_and_float() @@ -86,6 +132,8 @@ function it_subtracts_integer_and_float() $this->beConstructedWith(new JustInteger(123)); $this->subtract(new JustFloat(.45))->toNumber()->shouldBe(122.55); + $this->subtract(.45)->toNumber()->shouldBe(122.55); + $this->subtract('.45')->toNumber()->shouldBe(122.55); } function it_subtracts_floats() @@ -93,6 +141,8 @@ function it_subtracts_floats() $this->beConstructedWith(new JustFloat(.2)); $this->subtract(new JustFloat(.1))->toNumber()->shouldBe(.1); + $this->subtract(.1)->toNumber()->shouldBe(.1); + $this->subtract('.1')->toNumber()->shouldBe(.1); } function it_subtracts_float_and_integer() @@ -100,6 +150,8 @@ function it_subtracts_float_and_integer() $this->beConstructedWith(new JustFloat(123.5)); $this->subtract(new JustInteger(23))->toNumber()->shouldBe(100.5); + $this->subtract(23)->toNumber()->shouldBe(100.5); + $this->subtract('23')->toNumber()->shouldBe(100.5); } function it_multiplies_integers() @@ -107,6 +159,8 @@ function it_multiplies_integers() $this->beConstructedWith(new JustInteger(8)); $this->multiply(new JustInteger(9))->toNumber()->shouldBe(72); + $this->multiply(9)->toNumber()->shouldBe(72); + $this->multiply('9')->toNumber()->shouldBe(72); } function it_multiplies_integer_and_float() @@ -114,6 +168,8 @@ function it_multiplies_integer_and_float() $this->beConstructedWith(new JustInteger(8)); $this->multiply(new JustFloat(2.4))->toNumber()->shouldBe(19.2); + $this->multiply(2.4)->toNumber()->shouldBe(19.2); + $this->multiply('2.4')->toNumber()->shouldBe(19.2); } function it_multiplies_floats() @@ -121,6 +177,8 @@ function it_multiplies_floats() $this->beConstructedWith(new JustFloat(.4)); $this->multiply(new JustFloat(.5))->toNumber()->shouldBe(.2); + $this->multiply(.5)->toNumber()->shouldBe(.2); + $this->multiply('.5')->toNumber()->shouldBe(.2); } function it_multiplies_float_and_integer() @@ -128,6 +186,8 @@ function it_multiplies_float_and_integer() $this->beConstructedWith(new JustFloat(11.2)); $this->multiply(new JustInteger(4))->toNumber()->shouldBe(44.8); + $this->multiply(4)->toNumber()->shouldBe(44.8); + $this->multiply('4')->toNumber()->shouldBe(44.8); } function it_divides_integers() @@ -135,6 +195,8 @@ function it_divides_integers() $this->beConstructedWith(new JustInteger(12)); $this->divide(new JustInteger(4))->toNumber()->shouldBe(3); + $this->divide(4)->toNumber()->shouldBe(3); + $this->divide('4')->toNumber()->shouldBe(3); } function it_divides_integers_returning_float_when_fraction_result() @@ -142,6 +204,8 @@ function it_divides_integers_returning_float_when_fraction_result() $this->beConstructedWith(new JustInteger(12)); $this->divide(new JustInteger(5))->toNumber()->shouldBe(2.4); + $this->divide(5)->toNumber()->shouldBe(2.4); + $this->divide('5')->toNumber()->shouldBe(2.4); } function it_throws_error_when_dividing_by_zero() @@ -158,6 +222,8 @@ function it_divides_integer_and_float_returning_float() $this->beConstructedWith(new JustInteger(12)); $this->divide(new JustFloat(.5))->toNumber()->shouldBe(24.0); + $this->divide(.5)->toNumber()->shouldBe(24.0); + $this->divide('0.5')->toNumber()->shouldBe(24.0); } function it_divides_floats() @@ -165,6 +231,8 @@ function it_divides_floats() $this->beConstructedWith(new JustFloat(.12)); $this->divide(new JustFloat(.04))->toNumber()->shouldBe(3.0); + $this->divide(.04)->toNumber()->shouldBe(3.0); + $this->divide('0.04')->toNumber()->shouldBe(3.0); } function it_divides_float_and_integer() @@ -172,6 +240,8 @@ function it_divides_float_and_integer() $this->beConstructedWith(new JustFloat(12.5)); $this->divide(new JustInteger(5))->toNumber()->shouldBe(2.5); + $this->divide(5)->toNumber()->shouldBe(2.5); + $this->divide('5')->toNumber()->shouldBe(2.5); } function it_calculates_modulo_of_integer() @@ -179,7 +249,11 @@ function it_calculates_modulo_of_integer() $this->beConstructedWith(new JustInteger(12)); $this->modulo(new JustInteger(11))->toNumber()->shouldBe(1); + $this->modulo(11)->toNumber()->shouldBe(1); + $this->modulo('11')->toNumber()->shouldBe(1); $this->modulo(new JustInteger(7))->toNumber()->shouldBe(5); + $this->modulo(7)->toNumber()->shouldBe(5); + $this->modulo('7')->toNumber()->shouldBe(5); } function it_calculates_modulo_of_float_just_like_php_does() @@ -187,12 +261,14 @@ function it_calculates_modulo_of_float_just_like_php_does() $this->beConstructedWith(new JustInteger(12)); $this->modulo(new JustFloat(11.9))->toNumber()->shouldBe(1); + $this->modulo(11.9)->toNumber()->shouldBe(1); + $this->modulo('11.9')->toNumber()->shouldBe(1); } function it_throws_error_when_modulo_divider_is_zero() { $this->beConstructedThrough( - fn() => (new PlainNumber(new JustInteger(12)))->modulo(new JustInteger(0)) + fn() => (new PlainNumber(new JustInteger(12)))->modulo(new Zero()) ); $this->shouldThrow(DivisionByZeroError::class)->during('toNumber'); diff --git a/spec/PlainNumbersArraySpec.php b/spec/PlainNumbersArraySpec.php index 0b02bb3..4be3760 100644 --- a/spec/PlainNumbersArraySpec.php +++ b/spec/PlainNumbersArraySpec.php @@ -6,10 +6,11 @@ use Closure; use DivisionByZeroError; use GW\Value\Arrayable\JustArray; -use GW\Value\Numberable\Add; use GW\Value\Numberable\CompareAsInt; +use GW\Value\Numberable\JustFloat; use GW\Value\Numberable\JustInteger; -use GW\Value\Numberable\JustNumbers; +use GW\Value\Numberable\JustNumber; +use GW\Value\Numberable\Zero; use GW\Value\NumberValue; use GW\Value\PlainArray; use GW\Value\PlainNumber; @@ -38,6 +39,20 @@ function it_calculates_sum_of_floats() $this->sum()->toNumber()->shouldBeApproximately(7.5, 1.0e-9); } + function it_calculates_sum_of_mixed_numerics() + { + $this->beConstructedThrough('just', [.5, 1, '1.5', '2', new JustFloat(2.5), PlainNumber::from(2)]); + + $this->sum()->toNumber()->shouldBeApproximately(9.5, 1.0e-9); + } + + function it_fails_to_create_sum_with_non_numeric() + { + $this->beConstructedThrough('just', [.5, 1, 'x']); + + $this->sum()->shouldThrow(LogicException::class)->during('toNumber'); + } + function it_calculates_average_of_integers() { $this->beConstructedThrough('just', [1, 2, 3, 4, 5, 6, 7]); @@ -52,6 +67,13 @@ function it_calculates_average_of_floats() $this->average()->toNumber()->shouldBeApproximately(1.45, 1.0e-9); } + function it_calculates_average_of_numerics() + { + $this->beConstructedThrough('just', [1.1, '1.2', 1.3, new JustNumber(1.4), PlainNumber::from(1.7), '2']); + + $this->average()->toNumber()->shouldBeApproximately(1.45, 1.0e-9); + } + function it_cannot_calculate_average_from_empty_set() { $this->beConstructedThrough('just', []); @@ -73,6 +95,13 @@ function it_returns_min_of_floats() $this->min()->toNumber()->shouldBe(-.2); } + function it_returns_min_of_numerics() + { + $this->beConstructedThrough('just', [2, .1, '-0.2', new Zero(), '11']); + + $this->min()->toNumber()->shouldBe(-.2); + } + function it_returns_max_of_integers() { $this->beConstructedThrough('just', [6, 2, 1, 7, -2, 3, 4, 5]); @@ -87,6 +116,13 @@ function it_returns_max_of_floats() $this->max()->toNumber()->shouldBe(.7); } + function it_returns_max_of_numerics() + { + $this->beConstructedThrough('just', ['0.1', '7', -.2, new Zero()]); + + $this->max()->toNumber()->shouldBe(7); + } + function it_cannot_calculate_min_nor_max_from_empty_set() { $this->beConstructedThrough('just', []); diff --git a/src/NumberValue.php b/src/NumberValue.php index df1a32b..3436424 100644 --- a/src/NumberValue.php +++ b/src/NumberValue.php @@ -7,25 +7,44 @@ interface NumberValue extends Value, Numberable // comparators /** + * @param int|float|Numberable $other * @return int {-1, 0, 1} */ - public function compare(Numberable $other): int; + public function compare($other): int; - public function equals(Numberable $other): bool; + /** + * @param int|float|Numberable $other + */ + public function equals($other): bool; // basic math - public function add(Numberable $other): NumberValue; + /** + * @param int|float|Numberable $other + */ + public function add($other): NumberValue; - public function subtract(Numberable $other): NumberValue; + /** + * @param int|float|Numberable $other + */ + public function subtract($other): NumberValue; - public function multiply(Numberable $other): NumberValue; + /** + * @param int|float|Numberable $other + */ + public function multiply($other): NumberValue; - public function divide(Numberable $other): NumberValue; + /** + * @param int|float|Numberable $other + */ + public function divide($other): NumberValue; public function abs(): NumberValue; - public function modulo(Numberable $divider): NumberValue; + /** + * @param int|float|Numberable $divider + */ + public function modulo($divider): NumberValue; // rounding @@ -40,7 +59,7 @@ public function calculate(callable $formula): NumberValue; // value - /** @return bool false when 0 or 0.0, true otherwise */ + /** @return bool true when 0 or 0.0, false otherwise */ public function isEmpty(): bool; // casting diff --git a/src/Numberable/JustNumber.php b/src/Numberable/JustNumber.php index 53698fa..58a49f1 100644 --- a/src/Numberable/JustNumber.php +++ b/src/Numberable/JustNumber.php @@ -9,15 +9,21 @@ final class JustNumber implements Numberable /** @var int|float */ private $number; - /** @param int|float $number */ + /** @param int|float|numeric-string $number */ public function __construct($number) { - $this->number = $number; + $this->number = $number + 0; + } + + /** @param int|float|numeric-string|Numberable $number */ + public static function wrap($number): Numberable + { + return $number instanceof Numberable ? $number : new self($number); } /** @return int|float */ public function toNumber() { - return $this->number; + return $this->number + 0; } } diff --git a/src/Numberable/JustNumbers.php b/src/Numberable/JustNumbers.php index 64b5997..122ba97 100644 --- a/src/Numberable/JustNumbers.php +++ b/src/Numberable/JustNumbers.php @@ -5,6 +5,7 @@ use GW\Value\Arrayable; use GW\Value\Arrayable\JustArray; use GW\Value\Arrayable\Map; +use GW\Value\Numberable; use GW\Value\NumberValue; /** @@ -15,7 +16,7 @@ final class JustNumbers implements Arrayable /** @var Arrayable */ private Arrayable $numbers; - /** @param int|float ...$numbers */ + /** @param int|float|numeric-string|Numberable ...$numbers */ public function __construct(...$numbers) { $this->numbers = new Map(new JustArray($numbers), new ToNumberValue()); diff --git a/src/Numberable/ToNumberValue.php b/src/Numberable/ToNumberValue.php index 8833549..d4e11ad 100644 --- a/src/Numberable/ToNumberValue.php +++ b/src/Numberable/ToNumberValue.php @@ -8,6 +8,8 @@ use LogicException; use function is_float; use function is_int; +use function is_numeric; +use function is_string; final class ToNumberValue { @@ -30,7 +32,9 @@ public function __invoke($number): NumberValue return new PlainNumber(new JustFloat($number)); } - //TODO number from numeric string? + if (is_string($number) && is_numeric($number)) { + return new PlainNumber(new JustNumber($number + 0)); + } throw new LogicException('Cannot cast value to NumberValue'); } diff --git a/src/NumbersArray.php b/src/NumbersArray.php index 21bf1da..eaf51dd 100644 --- a/src/NumbersArray.php +++ b/src/NumbersArray.php @@ -40,21 +40,21 @@ public function filterEmpty(): NumbersArray; /** * @template TNewValue * @param callable(NumberValue):TNewValue $transformer - * @phpstan-return ArrayValue + * @return ArrayValue */ public function map(callable $transformer): ArrayValue; /** * @template TNewValue * @param callable(NumberValue):iterable $transformer - * @phpstan-return ArrayValue + * @return ArrayValue */ public function flatMap(callable $transformer): ArrayValue; /** * @template TNewKey * @param callable(NumberValue):TNewKey $reducer - * @phpstan-return AssocValue> + * @return AssocValue> */ public function groupBy(callable $reducer): AssocValue; @@ -65,22 +65,22 @@ public function shuffle(): NumbersArray; public function reverse(): NumbersArray; /** - * @phpstan-param NumberValue $value + * @param NumberValue $value */ public function unshift($value): NumbersArray; /** - * @phpstan-param NumberValue $value + * @param NumberValue|null $value */ public function shift(&$value = null): NumbersArray; /** - * @phpstan-param NumberValue $value + * @param NumberValue $value */ public function push($value): NumbersArray; /** - * @phpstan-param NumberValue $value + * @param NumberValue|null $value */ public function pop(&$value = null): NumbersArray; @@ -93,25 +93,25 @@ public function offsetSet($offset, $value): void; public function offsetUnset($offset): void; /** - * @phpstan-param ArrayValue $other + * @param ArrayValue $other */ public function join(ArrayValue $other): NumbersArray; public function slice(int $offset, int $length): NumbersArray; /** - * @phpstan-param ArrayValue|null $replacement + * @param ArrayValue|null $replacement */ public function splice(int $offset, int $length, ?ArrayValue $replacement = null): NumbersArray; /** - * @phpstan-param ArrayValue $other + * @param ArrayValue $other * @param (callable(NumberValue,NumberValue):int)|null $comparator */ public function diff(ArrayValue $other, ?callable $comparator = null): NumbersArray; /** - * @phpstan-param ArrayValue $other + * @param ArrayValue $other * @param (callable(NumberValue,NumberValue):int)|null $comparator */ public function intersect(ArrayValue $other, ?callable $comparator = null): NumbersArray; @@ -119,8 +119,8 @@ public function intersect(ArrayValue $other, ?callable $comparator = null): Numb /** * @template TNewValue * @param callable(TNewValue, NumberValue):TNewValue $transformer - * @phpstan-param TNewValue $start - * @phpstan-return TNewValue + * @param TNewValue $start + * @return TNewValue */ public function reduce(callable $transformer, $start); @@ -134,7 +134,7 @@ public function implode(string $glue): StringValue; public function notEmpty(): NumbersArray; /** - * @phpstan-return AssocValue + * @return AssocValue */ public function toAssocValue(): AssocValue; @@ -155,7 +155,7 @@ public function find(callable $filter): ?NumberValue; public function findLast(callable $filter): ?NumberValue; /** - * @phpstan-param NumberValue $element + * @param NumberValue $element */ public function hasElement($element): bool; diff --git a/src/PlainNumber.php b/src/PlainNumber.php index 84a3c70..a51362e 100644 --- a/src/PlainNumber.php +++ b/src/PlainNumber.php @@ -7,7 +7,6 @@ use GW\Value\Numberable\Ceil; use GW\Value\Numberable\Divide; use GW\Value\Numberable\Floor; -use GW\Value\Numberable\JustInteger; use GW\Value\Numberable\JustNumber; use GW\Value\Numberable\Modulo; use GW\Value\Numberable\Multiply; @@ -26,10 +25,10 @@ public function __construct(Numberable $number) $this->number = $number; } - /** @param int|float $number */ + /** @param int|float|numeric-string|Numberable $number */ public static function from($number): self { - return new self(new JustNumber($number)); + return new self(JustNumber::wrap($number)); } public function format(int $decimals = 0, string $separator = '.', string $thousandsSeparator = ','): StringValue @@ -37,39 +36,46 @@ public function format(int $decimals = 0, string $separator = '.', string $thous return Wrap::string(number_format($this->number->toNumber(), $decimals, $separator, $thousandsSeparator)); } - public function compare(Numberable $other): int + /** @param int|float|numeric-string|Numberable $other */ + public function compare($other): int { - return $this->toNumber() <=> $other->toNumber(); + return $this->toNumber() <=> JustNumber::wrap($other)->toNumber(); } - public function equals(Numberable $other): bool + /** @param int|float|numeric-string|Numberable $other */ + public function equals($other): bool { return $this->compare($other) === 0; } - public function add(Numberable $other): NumberValue + /** @param int|float|numeric-string|Numberable $other */ + public function add($other): NumberValue { - return new self(new Add($this->number, $other)); + return new self(new Add($this->number, JustNumber::wrap($other))); } - public function subtract(Numberable $other): NumberValue + /** @param int|float|numeric-string|Numberable $other */ + public function subtract($other): NumberValue { - return new self(new Subtract($this->number, $other)); + return new self(new Subtract($this->number, JustNumber::wrap($other))); } - public function multiply(Numberable $other): NumberValue + /** @param int|float|numeric-string|Numberable $other */ + public function multiply($other): NumberValue { - return new self(new Multiply($this->number, $other)); + return new self(new Multiply($this->number, JustNumber::wrap($other))); } - public function divide(Numberable $other): NumberValue + /** @param int|float|numeric-string|Numberable $other */ + public function divide($other): NumberValue { - return new self(new Divide($this->number, $other)); + return new self(new Divide($this->number, JustNumber::wrap($other))); } - public function modulo(Numberable $divider): NumberValue + /** @param int|float|numeric-string|Numberable $divider */ + public function modulo($divider): NumberValue { - return new self(new Modulo($this->number, $divider)); + return new self(new Modulo($this->number, JustNumber::wrap($divider))); } public function abs(): NumberValue diff --git a/src/PlainNumbersArray.php b/src/PlainNumbersArray.php index b14cd35..f6139c2 100644 --- a/src/PlainNumbersArray.php +++ b/src/PlainNumbersArray.php @@ -28,7 +28,7 @@ public static function fromArrayable(Arrayable $numbers): self return new self(new PlainArray($numbers)); } - /** @param int|float ...$numbers */ + /** @param int|float|numeric-string|Numberable ...$numbers */ public static function just(...$numbers): self { return self::fromArrayable(new JustNumbers(...$numbers)); @@ -190,7 +190,7 @@ public function push($value): NumbersArray } /** - * @param NumberValue $value + * @param NumberValue|null $value */ public function pop(&$value = null): NumbersArray { diff --git a/src/Wrap.php b/src/Wrap.php index 21feb74..c236fd8 100644 --- a/src/Wrap.php +++ b/src/Wrap.php @@ -67,7 +67,7 @@ public static function stringsArray(array $strings = []): StringsArray } /** - * @param int|float|Numberable $number + * @param int|float|numeric-string|Numberable $number */ public static function number($number): NumberValue { @@ -75,15 +75,11 @@ public static function number($number): NumberValue return $number; } - if ($number instanceof Numberable) { - return new PlainNumber($number); - } - return PlainNumber::from($number); } /** - * @param int[]|float[]|Arrayable|NumbersArray $numbers + * @param array|Arrayable|NumbersArray $numbers */ public static function numbersArray($numbers): NumbersArray { From fd0ca553d9f684893d21f018f5106abfeeb08ad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Sat, 22 May 2021 10:51:11 +0200 Subject: [PATCH 08/20] Fix argument unpacking issue --- src/Numberable/Add.php | 3 ++- src/Wrap.php | 2 +- test.sh | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Numberable/Add.php b/src/Numberable/Add.php index 1bf35ed..2edc7f1 100644 --- a/src/Numberable/Add.php +++ b/src/Numberable/Add.php @@ -4,6 +4,7 @@ use GW\Value\Arrayable\JustArray; use GW\Value\Numberable; +use function array_values; final class Add implements Numberable { @@ -11,7 +12,7 @@ final class Add implements Numberable public function __construct(Numberable $term, Numberable ...$terms) { - $this->sum = new Sum(new JustArray([$term, ...$terms])); + $this->sum = new Sum(new JustArray([$term, ...array_values($terms)])); } /** @return int|float */ diff --git a/src/Wrap.php b/src/Wrap.php index c236fd8..9e818be 100644 --- a/src/Wrap.php +++ b/src/Wrap.php @@ -79,7 +79,7 @@ public static function number($number): NumberValue } /** - * @param array|Arrayable|NumbersArray $numbers + * @param array|Arrayable|NumbersArray $numbers */ public static function numbersArray($numbers): NumbersArray { diff --git a/test.sh b/test.sh index 1ded268..636b527 100755 --- a/test.sh +++ b/test.sh @@ -13,7 +13,7 @@ function stan() { } function psalm() { - ./bin/psalm --show-info=true + ./bin/psalm } function coverage() { From eb8a1a6c77599294d867b364651a78465dced844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Sun, 23 May 2021 22:11:08 +0200 Subject: [PATCH 09/20] Add ArrayValue::toNumbersArray --- spec/PlainArraySpec.php | 11 +++++++++++ spec/PlainStringsArraySpec.php | 9 +++++++++ src/ArrayValue.php | 2 ++ src/NumberValue.php | 14 +++++++------- src/NumbersArray.php | 3 +++ src/PlainArray.php | 6 ++++++ src/PlainNumbersArray.php | 7 ++++++- src/PlainStringsArray.php | 9 ++++++++- src/Stringable/ToNativeString.php | 13 +++++++++++++ 9 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 src/Stringable/ToNativeString.php diff --git a/spec/PlainArraySpec.php b/spec/PlainArraySpec.php index c0c554c..e223054 100644 --- a/spec/PlainArraySpec.php +++ b/spec/PlainArraySpec.php @@ -3,6 +3,8 @@ namespace spec\GW\Value; use GW\Value\Filters; +use GW\Value\Numberable\JustInteger; +use GW\Value\NumbersArray; use GW\Value\PlainArray; use GW\Value\Sorts; use GW\Value\StringsArray; @@ -694,6 +696,15 @@ function it_can_be_converted_to_StringsArray() $stringsArray->toNativeStrings()->shouldBeLike(['one', 'two', 'three']); } + function it_can_be_converted_to_NumbersArray() + { + $this->beConstructedWith(['1', 2.1, new JustInteger(3)]); + + $numbersArray = $this->toNumbersArray(); + $numbersArray->shouldBeAnInstanceOf(NumbersArray::class); + $numbersArray->toNativeNumbers()->shouldBeLike([1, 2.1, 3]); + } + function it_can_tell_if_has_element_or_not() { $this->beConstructedWith(['one', '2', 'three']); diff --git a/spec/PlainStringsArraySpec.php b/spec/PlainStringsArraySpec.php index 42ca689..1892611 100644 --- a/spec/PlainStringsArraySpec.php +++ b/spec/PlainStringsArraySpec.php @@ -569,6 +569,15 @@ function it_returns_PlainArray_containing_regex_matches() $matches->shouldBeLike(new PlainArray([['Lorem', 'Lorem'], ['dolor', 'dolor']])); } + function it_can_be_converted_to_NumbersArray() + { + $this->beConstructedWithStrings('1', '2.1', '3.0'); + + $this->toNumbersArray() + ->toNativeNumbers() + ->shouldBeLike([1, 2.1, 3.0]); + } + private function beConstructedWithStrings(string ...$strings): void { $this->beConstructedWith(Wrap::array($strings)); diff --git a/src/ArrayValue.php b/src/ArrayValue.php index 9fb0b02..72c9445 100644 --- a/src/ArrayValue.php +++ b/src/ArrayValue.php @@ -203,4 +203,6 @@ public function notEmpty(): ArrayValue; public function toAssocValue(): AssocValue; public function toStringsArray(): StringsArray; + + public function toNumbersArray(): NumbersArray; } diff --git a/src/NumberValue.php b/src/NumberValue.php index 3436424..55c7a85 100644 --- a/src/NumberValue.php +++ b/src/NumberValue.php @@ -7,42 +7,42 @@ interface NumberValue extends Value, Numberable // comparators /** - * @param int|float|Numberable $other + * @param int|float|numeric-string|Numberable $other * @return int {-1, 0, 1} */ public function compare($other): int; /** - * @param int|float|Numberable $other + * @param int|float|numeric-string|Numberable $other */ public function equals($other): bool; // basic math /** - * @param int|float|Numberable $other + * @param int|float|numeric-string|Numberable $other */ public function add($other): NumberValue; /** - * @param int|float|Numberable $other + * @param int|float|numeric-string|Numberable $other */ public function subtract($other): NumberValue; /** - * @param int|float|Numberable $other + * @param int|float|numeric-string|Numberable $other */ public function multiply($other): NumberValue; /** - * @param int|float|Numberable $other + * @param int|float|numeric-string|Numberable $other */ public function divide($other): NumberValue; public function abs(): NumberValue; /** - * @param int|float|Numberable $divider + * @param int|float|numeric-string|Numberable $divider */ public function modulo($divider): NumberValue; diff --git a/src/NumbersArray.php b/src/NumbersArray.php index eaf51dd..3cc8aa6 100644 --- a/src/NumbersArray.php +++ b/src/NumbersArray.php @@ -30,6 +30,9 @@ public function unique(?callable $comparator = null): NumbersArray; /** @return NumberValue[] */ public function toArray(): array; + /** @return array */ + public function toNativeNumbers(): array; + /** * @param callable(NumberValue):bool $filter */ diff --git a/src/PlainArray.php b/src/PlainArray.php index 1c32796..5fbe707 100644 --- a/src/PlainArray.php +++ b/src/PlainArray.php @@ -23,6 +23,7 @@ use GW\Value\Arrayable\Splice; use GW\Value\Arrayable\UniqueByComparator; use GW\Value\Arrayable\UniqueByString; +use GW\Value\Numberable\ToNumberValue; use function array_map; use function array_reverse; use function count; @@ -467,4 +468,9 @@ public function toStringsArray(): StringsArray { return Wrap::stringsArray($this->items->toArray()); } + + public function toNumbersArray(): NumbersArray + { + return new PlainNumbersArray($this->map(new ToNumberValue())); + } } diff --git a/src/PlainNumbersArray.php b/src/PlainNumbersArray.php index f6139c2..d523f75 100644 --- a/src/PlainNumbersArray.php +++ b/src/PlainNumbersArray.php @@ -174,7 +174,7 @@ public function unshift($value): NumbersArray } /** - * @param NumberValue $value + * @param NumberValue|null $value */ public function shift(&$value = null): NumbersArray { @@ -313,6 +313,11 @@ public function toStringsArray(): StringsArray return $this->numbers->toStringsArray(); } + public function toNumbersArray(): NumbersArray + { + return $this; + } + public function first(): ?NumberValue { return $this->numbers->first(); diff --git a/src/PlainStringsArray.php b/src/PlainStringsArray.php index 551f6dc..efe777b 100644 --- a/src/PlainStringsArray.php +++ b/src/PlainStringsArray.php @@ -2,6 +2,8 @@ namespace GW\Value; +use GW\Value\Numberable\ToNumberValue; +use GW\Value\Stringable\ToNativeString; use GW\Value\Stringable\ToStringValue; use Traversable; use function in_array; @@ -216,7 +218,7 @@ public function toArray(): array public function toNativeStrings(): array { return $this->strings - ->map(fn(StringValue $item): string => $item->toString()) + ->map(new ToNativeString()) ->toArray(); } @@ -617,4 +619,9 @@ public function toStringsArray(): self { return $this; } + + public function toNumbersArray(): NumbersArray + { + return new PlainNumbersArray($this->strings->map(new ToNativeString())->map(new ToNumberValue())); + } } diff --git a/src/Stringable/ToNativeString.php b/src/Stringable/ToNativeString.php new file mode 100644 index 0000000..2cb7f6f --- /dev/null +++ b/src/Stringable/ToNativeString.php @@ -0,0 +1,13 @@ +toString(); + } +} From 70045ce26b1429b76d1696ff75db3da445bf5975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Fri, 7 Jan 2022 12:29:26 +0100 Subject: [PATCH 10/20] Simplify simple operations and add documentation --- README.md | 48 +++++++++++++++++++ examples/example/NumberValue-add.php | 20 ++++++++ examples/example/NumberValue-calculate.php | 23 +++++++++ examples/example/NumberValue-format.php | 11 +++++ examples/example/NumberValue-round.php | 7 +++ examples/example/NumberValue-subtract.php | 20 ++++++++ examples/example/NumbersArray-average.php | 9 ++++ examples/example/NumbersArray-max.php | 9 ++++ examples/example/NumbersArray-min.php | 9 ++++ .../example/NumbersArray-reduceNumber.php | 15 ++++++ examples/example/NumbersArray-sum.php | 9 ++++ make-examples.php | 4 ++ spec/PlainNumbersArraySpec.php | 22 ++++++++- spec/PlainStringSpec.php | 4 +- src/Associable/SortKeys.php | 1 + src/Numberable/Add.php | 12 ++--- src/Numberable/Ceil.php | 5 +- src/Numberable/Divide.php | 11 ++--- src/Numberable/DivideReducer.php | 23 --------- src/Numberable/Floor.php | 5 +- src/Numberable/JustNumber.php | 2 +- src/Numberable/Math.php | 16 +++---- src/Numberable/Min.php | 1 - src/Numberable/Multiply.php | 15 +++--- src/Numberable/MultiplyReducer.php | 17 ------- src/Numberable/Round.php | 3 +- src/Numberable/Subtract.php | 14 +++--- src/Numberable/SubtractReducer.php | 17 ------- .../{JustNumbers.php => WrapNumbers.php} | 2 +- src/NumbersArray.php | 3 +- src/PlainNumbersArray.php | 14 +++--- 31 files changed, 255 insertions(+), 116 deletions(-) create mode 100644 examples/example/NumberValue-add.php create mode 100644 examples/example/NumberValue-calculate.php create mode 100644 examples/example/NumberValue-format.php create mode 100644 examples/example/NumberValue-round.php create mode 100644 examples/example/NumberValue-subtract.php create mode 100644 examples/example/NumbersArray-average.php create mode 100644 examples/example/NumbersArray-max.php create mode 100644 examples/example/NumbersArray-min.php create mode 100644 examples/example/NumbersArray-reduceNumber.php create mode 100644 examples/example/NumbersArray-sum.php delete mode 100644 src/Numberable/DivideReducer.php delete mode 100644 src/Numberable/MultiplyReducer.php delete mode 100644 src/Numberable/SubtractReducer.php rename src/Numberable/{JustNumbers.php => WrapNumbers.php} (93%) diff --git a/README.md b/README.md index 82845e1..84a775d 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,54 @@ $stringsArray = Wrap::iterable($range(0, 10)) }); ``` +### `NumberValue` + +Object wrapping number (float or int). + +```php +add(20) + ->subtract(1) + ->multiply(2) + ->divide(3) + ->modulo(100) + ->calculate(Math::sqrt()) + ->round(2); +// ... + +$formattedNumber = $number->format(2, '.', ','); + +``` + +### `NumbersArray` + +Object wrapping array of numbers wrapped in NumberValue. + +```php +sum()->format(2)->toString(); +$min = $numbers->min()->format(2)->toString(); +$max = $numbers->max()->format(2)->toString(); +$avg = $numbers->average()->format(2)->toString(); + +$customSum = $numbers->reduceNumber( + fn(NumberValue $sum, NumberValue $next): NumberValue => $sum->add($next->round()), + 0 +); + +``` + ## Documentation For full methods reference and more examples see [here](./docs/examples.md). diff --git a/examples/example/NumberValue-add.php b/examples/example/NumberValue-add.php new file mode 100644 index 0000000..c5a5c77 --- /dev/null +++ b/examples/example/NumberValue-add.php @@ -0,0 +1,20 @@ +add(50)->toNumber(); +echo "\n"; + +echo "100 + 11.22 = "; +echo $number->add(new JustFloat(11.22))->toNumber(); +echo "\n"; + +echo "100 + (10 + 20 + 30) = "; +echo $number->add(new Sum(new WrapNumbers(10, 20, 30)))->toNumber(); +echo "\n"; diff --git a/examples/example/NumberValue-calculate.php b/examples/example/NumberValue-calculate.php new file mode 100644 index 0000000..cdbf221 --- /dev/null +++ b/examples/example/NumberValue-calculate.php @@ -0,0 +1,23 @@ +calculate(fn (Numberable $number): Numberable => new Multiply($number, new JustInteger(12))) + ->toNumber(); +echo "\n"; + +echo "cos(100) = "; +echo $number->calculate(Math::cos())->toNumber(); +echo "\n"; + +echo "√100 = "; +echo $number->calculate(Math::sqrt())->toNumber(); +echo "\n"; diff --git a/examples/example/NumberValue-format.php b/examples/example/NumberValue-format.php new file mode 100644 index 0000000..d592395 --- /dev/null +++ b/examples/example/NumberValue-format.php @@ -0,0 +1,11 @@ +format(2)->toString(); +echo "\n"; + +echo $number->format(3, '.', ' ')->toString(); +echo "\n"; diff --git a/examples/example/NumberValue-round.php b/examples/example/NumberValue-round.php new file mode 100644 index 0000000..418e330 --- /dev/null +++ b/examples/example/NumberValue-round.php @@ -0,0 +1,7 @@ +round(1)->toNumber(); +echo "\n"; diff --git a/examples/example/NumberValue-subtract.php b/examples/example/NumberValue-subtract.php new file mode 100644 index 0000000..833660b --- /dev/null +++ b/examples/example/NumberValue-subtract.php @@ -0,0 +1,20 @@ +subtract(50)->toNumber(); +echo "\n"; + +echo "100 - 11.22 = "; +echo $number->subtract(new JustFloat(11.11))->toNumber(); +echo "\n"; + +echo "100 - (10 + 20 + 30) = "; +echo $number->subtract(new Sum(new WrapNumbers(10, 20, 30)))->toNumber(); +echo "\n"; diff --git a/examples/example/NumbersArray-average.php b/examples/example/NumbersArray-average.php new file mode 100644 index 0000000..d9d0a50 --- /dev/null +++ b/examples/example/NumbersArray-average.php @@ -0,0 +1,9 @@ +average()->toNumber(); +echo "\n"; diff --git a/examples/example/NumbersArray-max.php b/examples/example/NumbersArray-max.php new file mode 100644 index 0000000..668fb0f --- /dev/null +++ b/examples/example/NumbersArray-max.php @@ -0,0 +1,9 @@ +max()->toNumber(); +echo "\n"; diff --git a/examples/example/NumbersArray-min.php b/examples/example/NumbersArray-min.php new file mode 100644 index 0000000..4eb29b8 --- /dev/null +++ b/examples/example/NumbersArray-min.php @@ -0,0 +1,9 @@ +min()->toNumber(); +echo "\n"; diff --git a/examples/example/NumbersArray-reduceNumber.php b/examples/example/NumbersArray-reduceNumber.php new file mode 100644 index 0000000..c86d3ba --- /dev/null +++ b/examples/example/NumbersArray-reduceNumber.php @@ -0,0 +1,15 @@ +reduceNumber( + fn (NumberValue $factorial, NumberValue $next): NumberValue => $factorial->multiply($next), + 1 + ) + ->toNumber(); +echo "\n"; diff --git a/examples/example/NumbersArray-sum.php b/examples/example/NumbersArray-sum.php new file mode 100644 index 0000000..20efeb9 --- /dev/null +++ b/examples/example/NumbersArray-sum.php @@ -0,0 +1,9 @@ +sum()->toNumber(); +echo "\n"; diff --git a/make-examples.php b/make-examples.php index d599276..109bd7a 100644 --- a/make-examples.php +++ b/make-examples.php @@ -4,6 +4,8 @@ use GW\Value\ArrayValue; use GW\Value\AssocValue; use GW\Value\IterableValue; +use GW\Value\NumbersArray; +use GW\Value\NumberValue; use GW\Value\StringsArray; use GW\Value\StringValue; @@ -15,6 +17,8 @@ StringValue::class, StringsArray::class, IterableValue::class, + NumberValue::class, + NumbersArray::class, ]); file_put_contents('docs/examples.md', $markdown->toString()); diff --git a/spec/PlainNumbersArraySpec.php b/spec/PlainNumbersArraySpec.php index 4be3760..e2ad79c 100644 --- a/spec/PlainNumbersArraySpec.php +++ b/spec/PlainNumbersArraySpec.php @@ -23,6 +23,10 @@ use Prophecy\Argument; use function range; +/** + * @method void shouldHaveOffset(int $key) + * @method void shouldNotHaveOffset(int $key) + */ final class PlainNumbersArraySpec extends ObjectBehavior { function it_calculates_sum_of_integers() @@ -517,8 +521,8 @@ function it_has_immutable_array_access() $this[0]->shouldBe($one); $this[1]->shouldBe($two); - $this->offsetExists(0)->shouldBe(true); - $this->offsetExists(2)->shouldBe(false); + $this->shouldHaveOffset(0); + $this->shouldNotHaveOffset(2); $this->shouldThrow(BadMethodCallException::class)->during('offsetSet', [1, PlainNumber::from(3)]); $this->shouldThrow(BadMethodCallException::class)->during('offsetUnset', [1]); @@ -564,6 +568,13 @@ function it_can_be_casted_to_strings_array() $this->toStringsArray()->toNativeStrings()->shouldBe(['1', '2.5', '3.6', '4', '5']); } + function it_returns_self_on_toNumbersArray() + { + $this->beConstructedThrough('just', [1, 2.5, 3.6, 4, 5]); + + $this->toNumbersArray()->shouldBe($this); + } + private function isEven(): Closure { return static fn(NumberValue $number) => $number->toNumber() % 2 === 0; @@ -573,4 +584,11 @@ private function isGreater(int $than): Closure { return static fn(NumberValue $number) => $number->toNumber() > $than; } + + public function getMatchers(): array + { + return [ + 'haveOffset' => fn (PlainNumbersArray $subject, int $key): bool => $subject->offsetExists($key), + ]; + } } diff --git a/spec/PlainStringSpec.php b/spec/PlainStringSpec.php index 9339542..ee0804f 100644 --- a/spec/PlainStringSpec.php +++ b/spec/PlainStringSpec.php @@ -1,4 +1,4 @@ -associable->toAssocArray(); + /** @phpstan-ignore-next-line */ uksort($items, $this->comparator); return $items; diff --git a/src/Numberable/Add.php b/src/Numberable/Add.php index 2edc7f1..c5247dd 100644 --- a/src/Numberable/Add.php +++ b/src/Numberable/Add.php @@ -2,22 +2,22 @@ namespace GW\Value\Numberable; -use GW\Value\Arrayable\JustArray; use GW\Value\Numberable; -use function array_values; final class Add implements Numberable { - private Sum $sum; + private Numberable $leftTerm; + private Numberable $rightTerm; - public function __construct(Numberable $term, Numberable ...$terms) + public function __construct(Numberable $leftTerm, Numberable $rightTerm) { - $this->sum = new Sum(new JustArray([$term, ...array_values($terms)])); + $this->leftTerm = $leftTerm; + $this->rightTerm = $rightTerm; } /** @return int|float */ public function toNumber() { - return $this->sum->toNumber(); + return $this->leftTerm->toNumber() + $this->rightTerm->toNumber(); } } diff --git a/src/Numberable/Ceil.php b/src/Numberable/Ceil.php index 2c17307..bd9c99b 100644 --- a/src/Numberable/Ceil.php +++ b/src/Numberable/Ceil.php @@ -14,9 +14,8 @@ public function __construct(Numberable $numberable) $this->numberable = $numberable; } - /** @return int|float */ - public function toNumber() + public function toNumber(): float { - return ceil($this->numberable->toNumber()); + return (float)ceil($this->numberable->toNumber()); } } diff --git a/src/Numberable/Divide.php b/src/Numberable/Divide.php index 64feac0..b91b0ce 100644 --- a/src/Numberable/Divide.php +++ b/src/Numberable/Divide.php @@ -3,22 +3,21 @@ namespace GW\Value\Numberable; use GW\Value\Numberable; -use function array_reduce; final class Divide implements Numberable { private Numberable $dividend; - /** @var Numberable[] */ - private array $divisors; + private Numberable $divisor; - public function __construct(Numberable $dividend, Numberable ...$divisors) + public function __construct(Numberable $dividend, Numberable $divisor) { $this->dividend = $dividend; - $this->divisors = $divisors; + $this->divisor = $divisor; } + /** @return int|float */ public function toNumber() { - return array_reduce($this->divisors, new DivideReducer(), $this->dividend->toNumber()); + return $this->dividend->toNumber() / $this->divisor->toNumber(); } } diff --git a/src/Numberable/DivideReducer.php b/src/Numberable/DivideReducer.php deleted file mode 100644 index 322883f..0000000 --- a/src/Numberable/DivideReducer.php +++ /dev/null @@ -1,23 +0,0 @@ -toNumber(); - if ($divisorNum === 0) { - throw new DivisionByZeroError('Division by zero'); - } - - return $fraction / $divisorNum; - } -} diff --git a/src/Numberable/Floor.php b/src/Numberable/Floor.php index f3dab98..f097ef0 100644 --- a/src/Numberable/Floor.php +++ b/src/Numberable/Floor.php @@ -14,9 +14,8 @@ public function __construct(Numberable $numberable) $this->numberable = $numberable; } - /** @return int|float */ - public function toNumber() + public function toNumber(): float { - return floor($this->numberable->toNumber()); + return (float)floor($this->numberable->toNumber()); } } diff --git a/src/Numberable/JustNumber.php b/src/Numberable/JustNumber.php index 58a49f1..8d4f73b 100644 --- a/src/Numberable/JustNumber.php +++ b/src/Numberable/JustNumber.php @@ -24,6 +24,6 @@ public static function wrap($number): Numberable /** @return int|float */ public function toNumber() { - return $this->number + 0; + return $this->number; } } diff --git a/src/Numberable/Math.php b/src/Numberable/Math.php index 20f8e2c..150e47f 100644 --- a/src/Numberable/Math.php +++ b/src/Numberable/Math.php @@ -17,42 +17,42 @@ public function __construct(callable $fn) public static function cos(): self { - return new self('cos'); + return new self('\cos'); } public static function acos(): self { - return new self('acos'); + return new self('\acos'); } public static function sin(): self { - return new self('sin'); + return new self('\sin'); } public static function asin(): self { - return new self('asin'); + return new self('\asin'); } public static function tan(): self { - return new self('tan'); + return new self('\tan'); } public static function atan(): self { - return new self('atan'); + return new self('\atan'); } public static function exp(): self { - return new self('exp'); + return new self('\exp'); } public static function sqrt(): self { - return new self('sqrt'); + return new self('\sqrt'); } public function __invoke(Numberable $numberable): Numberable diff --git a/src/Numberable/Min.php b/src/Numberable/Min.php index daac68c..4b1045f 100644 --- a/src/Numberable/Min.php +++ b/src/Numberable/Min.php @@ -5,7 +5,6 @@ use GW\Value\Arrayable; use GW\Value\Arrayable\Map; use GW\Value\Numberable; -use GW\Value\NumberValue; use LogicException; use function count; use function min; diff --git a/src/Numberable/Multiply.php b/src/Numberable/Multiply.php index cc3235a..b2b6e27 100644 --- a/src/Numberable/Multiply.php +++ b/src/Numberable/Multiply.php @@ -3,22 +3,21 @@ namespace GW\Value\Numberable; use GW\Value\Numberable; -use function array_reduce; final class Multiply implements Numberable { - private Numberable $factor; - /** @var Numberable[] */ - private array $factors; + private Numberable $right; + private Numberable $left; - public function __construct(Numberable $factor, Numberable ...$factors) + public function __construct(Numberable $left, Numberable $right) { - $this->factor = $factor; - $this->factors = $factors; + $this->left = $left; + $this->right = $right; } + /** @return int|float */ public function toNumber() { - return array_reduce($this->factors, new MultiplyReducer(), $this->factor->toNumber()); + return $this->left->toNumber() * $this->right->toNumber(); } } diff --git a/src/Numberable/MultiplyReducer.php b/src/Numberable/MultiplyReducer.php deleted file mode 100644 index ef22873..0000000 --- a/src/Numberable/MultiplyReducer.php +++ /dev/null @@ -1,17 +0,0 @@ -toNumber(); - } -} diff --git a/src/Numberable/Round.php b/src/Numberable/Round.php index 833f2a1..d669964 100644 --- a/src/Numberable/Round.php +++ b/src/Numberable/Round.php @@ -19,8 +19,7 @@ public function __construct(Numberable $numberable, int $precision, ?int $mode = $this->mode = $mode; } - /** @return int|float */ - public function toNumber() + public function toNumber(): float { return round($this->numberable->toNumber(), $this->precision, $this->mode ?? PHP_ROUND_HALF_UP); } diff --git a/src/Numberable/Subtract.php b/src/Numberable/Subtract.php index eb602f9..a378466 100644 --- a/src/Numberable/Subtract.php +++ b/src/Numberable/Subtract.php @@ -3,23 +3,21 @@ namespace GW\Value\Numberable; use GW\Value\Numberable; -use function array_reduce; final class Subtract implements Numberable { - private Numberable $term; - /** @var Numberable[] */ - private array $terms; + private Numberable $minuend; + private Numberable $subtrahend; - public function __construct(Numberable $term, Numberable ...$terms) + public function __construct(Numberable $minuend, Numberable $subtrahend) { - $this->term = $term; - $this->terms = $terms; + $this->minuend = $minuend; + $this->subtrahend = $subtrahend; } /** @return int|float */ public function toNumber() { - return array_reduce($this->terms, new SubtractReducer(), $this->term->toNumber()); + return $this->minuend->toNumber() - $this->subtrahend->toNumber(); } } diff --git a/src/Numberable/SubtractReducer.php b/src/Numberable/SubtractReducer.php deleted file mode 100644 index d74e627..0000000 --- a/src/Numberable/SubtractReducer.php +++ /dev/null @@ -1,17 +0,0 @@ -toNumber(); - } -} diff --git a/src/Numberable/JustNumbers.php b/src/Numberable/WrapNumbers.php similarity index 93% rename from src/Numberable/JustNumbers.php rename to src/Numberable/WrapNumbers.php index 122ba97..babf8f7 100644 --- a/src/Numberable/JustNumbers.php +++ b/src/Numberable/WrapNumbers.php @@ -11,7 +11,7 @@ /** * @implements Arrayable */ -final class JustNumbers implements Arrayable +final class WrapNumbers implements Arrayable { /** @var Arrayable */ private Arrayable $numbers; diff --git a/src/NumbersArray.php b/src/NumbersArray.php index 3cc8aa6..c5c72e9 100644 --- a/src/NumbersArray.php +++ b/src/NumbersArray.php @@ -129,8 +129,9 @@ public function reduce(callable $transformer, $start); /** * @param callable(NumberValue $reduced, NumberValue $item):NumberValue $transformer + * @param int|float|numeric-string|Numberable $start */ - public function reduceNumber(callable $transformer, NumberValue $start): NumberValue; + public function reduceNumber(callable $transformer, $start): NumberValue; public function implode(string $glue): StringValue; diff --git a/src/PlainNumbersArray.php b/src/PlainNumbersArray.php index d523f75..b8ed29e 100644 --- a/src/PlainNumbersArray.php +++ b/src/PlainNumbersArray.php @@ -4,7 +4,7 @@ use BadMethodCallException; use GW\Value\Numberable\Average; -use GW\Value\Numberable\JustNumbers; +use GW\Value\Numberable\WrapNumbers; use GW\Value\Numberable\Max; use GW\Value\Numberable\Min; use GW\Value\Numberable\Sum; @@ -31,7 +31,7 @@ public static function fromArrayable(Arrayable $numbers): self /** @param int|float|numeric-string|Numberable ...$numbers */ public static function just(...$numbers): self { - return self::fromArrayable(new JustNumbers(...$numbers)); + return self::fromArrayable(new WrapNumbers(...$numbers)); } public function sum(): NumberValue @@ -143,7 +143,6 @@ public function groupBy(callable $reducer): AssocValue /** * @return ArrayValue> - * @phpstan-ignore-next-line shrug */ public function chunk(int $size): ArrayValue { @@ -220,7 +219,7 @@ public function offsetGet($offset): NumberValue */ public function offsetSet($offset, $value): void { - $this->numbers->offsetSet($offset, $value); + throw new BadMethodCallException('PlainNumbersArray is immutable'); } /** @@ -229,7 +228,7 @@ public function offsetSet($offset, $value): void */ public function offsetUnset($offset): void { - $this->numbers->offsetUnset($offset); + throw new BadMethodCallException('PlainNumbersArray is immutable'); } /** @@ -284,10 +283,11 @@ public function reduce(callable $transformer, $start) /** * @param callable(NumberValue $reduced, NumberValue $item):NumberValue $transformer + * @param int|float|numeric-string|Numberable $start */ - public function reduceNumber(callable $transformer, NumberValue $start): NumberValue + public function reduceNumber(callable $transformer, $start): NumberValue { - return $this->reduce($transformer, $start); + return $this->numbers->reduce($transformer, Wrap::number($start)); } public function implode(string $glue): StringValue From db87fa41046c42f64d5b264a27f9f163f1930fcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Fri, 7 Jan 2022 15:21:41 +0100 Subject: [PATCH 11/20] Pull changes from origin/master --- spec/PlainNumbersArraySpec.php | 3 +++ src/NumbersArray.php | 8 ++++++-- src/PlainNumbersArray.php | 14 +++++++++++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/spec/PlainNumbersArraySpec.php b/spec/PlainNumbersArraySpec.php index e2ad79c..3aacf9e 100644 --- a/spec/PlainNumbersArraySpec.php +++ b/spec/PlainNumbersArraySpec.php @@ -277,8 +277,11 @@ function it_slices_numbers_array() $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); $this->slice(0, 2)->toNativeNumbers()->shouldBe([1, 2]); + $this->take(2)->toNativeNumbers()->shouldBe([1, 2]); $this->slice(1, 2)->toNativeNumbers()->shouldBe([2, 3]); $this->slice(-2, 2)->toNativeNumbers()->shouldBe([4, 5]); + $this->slice(2)->toNativeNumbers()->shouldBe([3, 4, 5]); + $this->skip(2)->toNativeNumbers()->shouldBe([3, 4, 5]); } function it_splices_numbers_array() diff --git a/src/NumbersArray.php b/src/NumbersArray.php index c5c72e9..ac396de 100644 --- a/src/NumbersArray.php +++ b/src/NumbersArray.php @@ -55,7 +55,7 @@ public function map(callable $transformer): ArrayValue; public function flatMap(callable $transformer): ArrayValue; /** - * @template TNewKey + * @template TNewKey of int|string * @param callable(NumberValue):TNewKey $reducer * @return AssocValue> */ @@ -100,7 +100,11 @@ public function offsetUnset($offset): void; */ public function join(ArrayValue $other): NumbersArray; - public function slice(int $offset, int $length): NumbersArray; + public function slice(int $offset, ?int $length = null): NumbersArray; + + public function skip(int $length): NumbersArray; + + public function take(int $length): NumbersArray; /** * @param ArrayValue|null $replacement diff --git a/src/PlainNumbersArray.php b/src/PlainNumbersArray.php index b8ed29e..33cb480 100644 --- a/src/PlainNumbersArray.php +++ b/src/PlainNumbersArray.php @@ -135,9 +135,7 @@ public function groupBy(callable $reducer): AssocValue { // @phpstan-ignore-next-line shrug return $this->numbers - // @phpstan-ignore-next-line shrug ->groupBy($reducer) - // @phpstan-ignore-next-line shrug ->map(static fn(ArrayValue $numbers) => new self($numbers)); } @@ -239,11 +237,21 @@ public function join(ArrayValue $other): NumbersArray return new self($this->numbers->join($other)); } - public function slice(int $offset, int $length): NumbersArray + public function slice(int $offset, ?int $length = null): NumbersArray { return new self($this->numbers->slice($offset, $length)); } + public function skip(int $length): NumbersArray + { + return new self($this->numbers->skip($length)); + } + + public function take(int $length): NumbersArray + { + return new self($this->numbers->take($length)); + } + /** * @param ArrayValue|null $replacement */ From 47c63bbe8b9b3c2afc71c2a1ea21af412ad5d5e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Sat, 8 Jan 2022 11:26:33 +0100 Subject: [PATCH 12/20] Split WrapNumbers to JustNumbers and NumberValues * resolves PHPStan issue that Arrayable not recognized as subtype of Arrayable * fix division by zero warning on PHP 7.4 --- examples/example/NumberValue-add.php | 4 +-- examples/example/NumberValue-subtract.php | 4 +-- src/Associable/SortKeys.php | 1 - src/Numberable/Divide.php | 10 +++++- src/Numberable/JustNumbers.php | 31 ++++++++++++++++ .../{WrapNumbers.php => NumberValues.php} | 2 +- src/Numberable/ToNumberValue.php | 35 ++++++------------- src/Numberable/ToNumberable.php | 35 +++++++++++++++++++ src/PlainNumbersArray.php | 4 +-- 9 files changed, 93 insertions(+), 33 deletions(-) create mode 100644 src/Numberable/JustNumbers.php rename src/Numberable/{WrapNumbers.php => NumberValues.php} (93%) create mode 100644 src/Numberable/ToNumberable.php diff --git a/examples/example/NumberValue-add.php b/examples/example/NumberValue-add.php index c5a5c77..28de856 100644 --- a/examples/example/NumberValue-add.php +++ b/examples/example/NumberValue-add.php @@ -1,8 +1,8 @@ add(new Sum(new WrapNumbers(10, 20, 30)))->toNumber(); +echo $number->add(new Sum(new JustNumbers(10, 20, 30)))->toNumber(); echo "\n"; diff --git a/examples/example/NumberValue-subtract.php b/examples/example/NumberValue-subtract.php index 833660b..8ad326d 100644 --- a/examples/example/NumberValue-subtract.php +++ b/examples/example/NumberValue-subtract.php @@ -1,8 +1,8 @@ subtract(new Sum(new WrapNumbers(10, 20, 30)))->toNumber(); +echo $number->subtract(new Sum(new JustNumbers(10, 20, 30)))->toNumber(); echo "\n"; diff --git a/src/Associable/SortKeys.php b/src/Associable/SortKeys.php index 51ecd9a..1bc1fe3 100644 --- a/src/Associable/SortKeys.php +++ b/src/Associable/SortKeys.php @@ -31,7 +31,6 @@ public function __construct(Associable $associable, callable $comparator) public function toAssocArray(): array { $items = $this->associable->toAssocArray(); - /** @phpstan-ignore-next-line */ uksort($items, $this->comparator); return $items; diff --git a/src/Numberable/Divide.php b/src/Numberable/Divide.php index b91b0ce..c5fe5ce 100644 --- a/src/Numberable/Divide.php +++ b/src/Numberable/Divide.php @@ -2,6 +2,7 @@ namespace GW\Value\Numberable; +use DivisionByZeroError; use GW\Value\Numberable; final class Divide implements Numberable @@ -18,6 +19,13 @@ public function __construct(Numberable $dividend, Numberable $divisor) /** @return int|float */ public function toNumber() { - return $this->dividend->toNumber() / $this->divisor->toNumber(); + $divisor = $this->divisor->toNumber(); + + if ($divisor === 0) { + // bypass warning triggered by PHP 7.4 before throwing DivisionByZeroError + throw new DivisionByZeroError('Division by zero'); + } + + return $this->dividend->toNumber() / $divisor; } } diff --git a/src/Numberable/JustNumbers.php b/src/Numberable/JustNumbers.php new file mode 100644 index 0000000..eb84e06 --- /dev/null +++ b/src/Numberable/JustNumbers.php @@ -0,0 +1,31 @@ + + */ +final class JustNumbers implements Arrayable +{ + /** @var Arrayable */ + private Arrayable $numbers; + + /** @param int|float|numeric-string|Numberable ...$numbers */ + public function __construct(...$numbers) + { + $this->numbers = new Map(new JustArray($numbers), new ToNumberable()); + } + + /** + * @return Numberable[] + */ + public function toArray(): array + { + return $this->numbers->toArray(); + } +} diff --git a/src/Numberable/WrapNumbers.php b/src/Numberable/NumberValues.php similarity index 93% rename from src/Numberable/WrapNumbers.php rename to src/Numberable/NumberValues.php index babf8f7..4069386 100644 --- a/src/Numberable/WrapNumbers.php +++ b/src/Numberable/NumberValues.php @@ -11,7 +11,7 @@ /** * @implements Arrayable */ -final class WrapNumbers implements Arrayable +final class NumberValues implements Arrayable { /** @var Arrayable */ private Arrayable $numbers; diff --git a/src/Numberable/ToNumberValue.php b/src/Numberable/ToNumberValue.php index d4e11ad..e314a02 100644 --- a/src/Numberable/ToNumberValue.php +++ b/src/Numberable/ToNumberValue.php @@ -2,40 +2,27 @@ namespace GW\Value\Numberable; -use GW\Value\Numberable; use GW\Value\NumberValue; use GW\Value\PlainNumber; -use LogicException; -use function is_float; -use function is_int; -use function is_numeric; -use function is_string; final class ToNumberValue { + private ToNumberable $toNumberable; + + public function __construct() + { + $this->toNumberable = new ToNumberable(); + } + /** @param mixed $number */ public function __invoke($number): NumberValue { - if ($number instanceof NumberValue) { - return $number; - } - - if ($number instanceof Numberable) { - return new PlainNumber($number); - } - - if (is_int($number)) { - return new PlainNumber(new JustInteger($number)); - } - - if (is_float($number)) { - return new PlainNumber(new JustFloat($number)); - } + $numberable = ($this->toNumberable)($number); - if (is_string($number) && is_numeric($number)) { - return new PlainNumber(new JustNumber($number + 0)); + if ($numberable instanceof NumberValue) { + return $numberable; } - throw new LogicException('Cannot cast value to NumberValue'); + return new PlainNumber($numberable); } } diff --git a/src/Numberable/ToNumberable.php b/src/Numberable/ToNumberable.php new file mode 100644 index 0000000..abd2e4d --- /dev/null +++ b/src/Numberable/ToNumberable.php @@ -0,0 +1,35 @@ + Date: Sat, 8 Jan 2022 12:22:46 +0100 Subject: [PATCH 13/20] Adjust code style --- examples/example/NumberValue-calculate.php | 2 +- .../example/NumbersArray-reduceNumber.php | 2 +- spec/PlainNumbersArraySpec.php | 120 +++++++++--------- spec/WrapSpec.php | 2 +- src/NumberValue.php | 2 +- src/Numberable/Formula.php | 10 +- src/PlainNumbersArray.php | 4 +- src/Wrap.php | 2 +- 8 files changed, 72 insertions(+), 72 deletions(-) diff --git a/examples/example/NumberValue-calculate.php b/examples/example/NumberValue-calculate.php index cdbf221..1f08ba7 100644 --- a/examples/example/NumberValue-calculate.php +++ b/examples/example/NumberValue-calculate.php @@ -10,7 +10,7 @@ echo "100 * 12 = "; echo $number - ->calculate(fn (Numberable $number): Numberable => new Multiply($number, new JustInteger(12))) + ->calculate(fn(Numberable $number): Numberable => new Multiply($number, new JustInteger(12))) ->toNumber(); echo "\n"; diff --git a/examples/example/NumbersArray-reduceNumber.php b/examples/example/NumbersArray-reduceNumber.php index c86d3ba..ce6f5a1 100644 --- a/examples/example/NumbersArray-reduceNumber.php +++ b/examples/example/NumbersArray-reduceNumber.php @@ -8,7 +8,7 @@ echo "5! = "; echo $numbers ->reduceNumber( - fn (NumberValue $factorial, NumberValue $next): NumberValue => $factorial->multiply($next), + fn(NumberValue $factorial, NumberValue $next): NumberValue => $factorial->multiply($next), 1 ) ->toNumber(); diff --git a/spec/PlainNumbersArraySpec.php b/spec/PlainNumbersArraySpec.php index 3aacf9e..3773b93 100644 --- a/spec/PlainNumbersArraySpec.php +++ b/spec/PlainNumbersArraySpec.php @@ -31,105 +31,105 @@ final class PlainNumbersArraySpec extends ObjectBehavior { function it_calculates_sum_of_integers() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5, 6, 7]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5, 6, 7]); $this->sum()->toNumber()->shouldBe(28); } function it_calculates_sum_of_floats() { - $this->beConstructedThrough('just', [.5, 1.0, 1.5, 2.0, 2.5]); + $this->beConstructedThrough('fromNumbers', [.5, 1.0, 1.5, 2.0, 2.5]); $this->sum()->toNumber()->shouldBeApproximately(7.5, 1.0e-9); } function it_calculates_sum_of_mixed_numerics() { - $this->beConstructedThrough('just', [.5, 1, '1.5', '2', new JustFloat(2.5), PlainNumber::from(2)]); + $this->beConstructedThrough('fromNumbers', [.5, 1, '1.5', '2', new JustFloat(2.5), PlainNumber::from(2)]); $this->sum()->toNumber()->shouldBeApproximately(9.5, 1.0e-9); } function it_fails_to_create_sum_with_non_numeric() { - $this->beConstructedThrough('just', [.5, 1, 'x']); + $this->beConstructedThrough('fromNumbers', [.5, 1, 'x']); $this->sum()->shouldThrow(LogicException::class)->during('toNumber'); } function it_calculates_average_of_integers() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5, 6, 7]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5, 6, 7]); $this->average()->toNumber()->shouldBe(4); } function it_calculates_average_of_floats() { - $this->beConstructedThrough('just', [1.1, 1.2, 1.3, 1.4, 1.5, 2.2]); + $this->beConstructedThrough('fromNumbers', [1.1, 1.2, 1.3, 1.4, 1.5, 2.2]); $this->average()->toNumber()->shouldBeApproximately(1.45, 1.0e-9); } function it_calculates_average_of_numerics() { - $this->beConstructedThrough('just', [1.1, '1.2', 1.3, new JustNumber(1.4), PlainNumber::from(1.7), '2']); + $this->beConstructedThrough('fromNumbers', [1.1, '1.2', 1.3, new JustNumber(1.4), PlainNumber::from(1.7), '2']); $this->average()->toNumber()->shouldBeApproximately(1.45, 1.0e-9); } function it_cannot_calculate_average_from_empty_set() { - $this->beConstructedThrough('just', []); + $this->beConstructedThrough('fromNumbers', []); $this->average()->shouldThrow(DivisionByZeroError::class)->during('toNumber'); } function it_returns_min_of_integers() { - $this->beConstructedThrough('just', [6, 2, 1, 7, -2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [6, 2, 1, 7, -2, 3, 4, 5]); $this->min()->toNumber()->shouldBe(-2); } function it_returns_min_of_floats() { - $this->beConstructedThrough('just', [.6, .2, .1, .7, -.2, .3, .4, .5]); + $this->beConstructedThrough('fromNumbers', [.6, .2, .1, .7, -.2, .3, .4, .5]); $this->min()->toNumber()->shouldBe(-.2); } function it_returns_min_of_numerics() { - $this->beConstructedThrough('just', [2, .1, '-0.2', new Zero(), '11']); + $this->beConstructedThrough('fromNumbers', [2, .1, '-0.2', new Zero(), '11']); $this->min()->toNumber()->shouldBe(-.2); } function it_returns_max_of_integers() { - $this->beConstructedThrough('just', [6, 2, 1, 7, -2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [6, 2, 1, 7, -2, 3, 4, 5]); $this->max()->toNumber()->shouldBe(7); } function it_returns_max_of_floats() { - $this->beConstructedThrough('just', [.6, .2, .1, .7, -.2, .3, .4, .5]); + $this->beConstructedThrough('fromNumbers', [.6, .2, .1, .7, -.2, .3, .4, .5]); $this->max()->toNumber()->shouldBe(.7); } function it_returns_max_of_numerics() { - $this->beConstructedThrough('just', ['0.1', '7', -.2, new Zero()]); + $this->beConstructedThrough('fromNumbers', ['0.1', '7', -.2, new Zero()]); $this->max()->toNumber()->shouldBe(7); } function it_cannot_calculate_min_nor_max_from_empty_set() { - $this->beConstructedThrough('just', []); + $this->beConstructedThrough('fromNumbers', []); $this->min()->shouldThrow(LogicException::class)->during('toNumber'); $this->max()->shouldThrow(LogicException::class)->during('toNumber'); @@ -137,7 +137,7 @@ function it_cannot_calculate_min_nor_max_from_empty_set() function it_filters_numbers() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5, 6, 7]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5, 6, 7]); $even = $this->filter(fn(NumberValue $value): bool => $value->toNumber() % 2 === 0); $even->shouldBeAnInstanceOf(PlainNumbersArray::class); @@ -146,7 +146,7 @@ function it_filters_numbers() function it_filters_zeros_as_empty_elements() { - $this->beConstructedThrough('just', [1, 2, 3, 0, 4, 5, .0]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 0, 4, 5, .0]); $notEmpty = $this->filterEmpty(); $notEmpty->shouldBeAnInstanceOf(PlainNumbersArray::class); @@ -157,7 +157,7 @@ function it_filters_zeros_as_empty_elements() function it_maps_to_ArrayValue() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5, 6, 7]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5, 6, 7]); $mapped = $this->map(fn(NumberValue $number): string => "#{$number->toNumber()}"); $mapped->beAnInstanceOf(PlainArray::class); @@ -166,7 +166,7 @@ function it_maps_to_ArrayValue() function it_maps_flat_to_ArrayValue() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5, 6, 7]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5, 6, 7]); $mapped = $this->flatMap( fn(NumberValue $number): array => ["#{$number->toNumber()}.1", "#{$number->toNumber()}.2"] @@ -194,7 +194,7 @@ function it_maps_flat_to_ArrayValue() function it_groups_numbers_returning_association_of_numbers_array() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $grouped = $this->groupBy(fn(NumberValue $value) => $value->modulo(new JustInteger(2))->toNumber()); @@ -208,7 +208,7 @@ function it_groups_numbers_returning_association_of_numbers_array() function it_chunks_number_values() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $chunked = $this->chunk(2); $chunked->shouldBeAnInstanceOf(PlainArray::class); @@ -229,7 +229,7 @@ function it_chunks_number_values() function it_sorts_numbers() { - $this->beConstructedThrough('just', [6, 2, 1, 7, -2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [6, 2, 1, 7, -2, 3, 4, 5]); $asc = $this->sort(Sorts::asc()); $asc->beAnInstanceOf(PlainNumbersArray::class); @@ -240,14 +240,14 @@ function it_sorts_numbers() function it_reverses_numbers() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $this->reverse()->toNativeNumbers()->shouldBe([5, 4, 3, 2, 1]); } function it_invokes_callback_for_each_number(CallableMock $callback) { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $expect = static fn(int $expected) => Argument::that(fn(NumberValue $n): bool => $n->toNumber() === $expected); $callback->__invoke($expect(1))->shouldBeCalled(); @@ -261,9 +261,9 @@ function it_invokes_callback_for_each_number(CallableMock $callback) function it_joins_numbers_array_value() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); - $this->join(PlainNumbersArray::just(6, 7, 8)) + $this->join(PlainNumbersArray::fromNumbers(6, 7, 8)) ->toNativeNumbers() ->shouldBe([1, 2, 3, 4, 5, 6, 7, 8]); @@ -274,7 +274,7 @@ function it_joins_numbers_array_value() function it_slices_numbers_array() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $this->slice(0, 2)->toNativeNumbers()->shouldBe([1, 2]); $this->take(2)->toNativeNumbers()->shouldBe([1, 2]); @@ -286,26 +286,26 @@ function it_slices_numbers_array() function it_splices_numbers_array() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5, 6, 7]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5, 6, 7]); $this->splice(0, 4)->toNativeNumbers()->shouldBe([5, 6, 7]); $this->splice(2, 3)->toNativeNumbers()->shouldBe([1, 2, 6, 7]); $this->splice(-4, 3)->toNativeNumbers()->shouldBe([1, 2, 3, 7]); - $this->splice(-4, 3, PlainNumbersArray::just(11, 12)) + $this->splice(-4, 3, PlainNumbersArray::fromNumbers(11, 12)) ->toNativeNumbers() ->shouldBe([1, 2, 3, 11, 12, 7]); } function it_resolves_unique_numbers() { - $this->beConstructedThrough('just', [1, 2, 2, 3, 3.0, 4, 4, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 2, 3, 3.0, 4, 4, 4, 5]); $this->unique()->toNativeNumbers()->shouldBe([1, 2, 3, 3.0, 4, 5]); } function it_resolves_unique_numbers_with_comparator() { - $this->beConstructedThrough('just', [1, 2, 2, 3, 3.0, 4, 4, 4.3, 5, 5.9]); + $this->beConstructedThrough('fromNumbers', [1, 2, 2, 3, 3.0, 4, 4, 4.3, 5, 5.9]); $this->unique(CompareAsInt::asc()) ->toNativeNumbers() @@ -314,30 +314,30 @@ function it_resolves_unique_numbers_with_comparator() function it_resolves_diff() { - $this->beConstructedThrough('just', [1, 2, 2, 3, 3.0, 4, 4, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 2, 3, 3.0, 4, 4, 4, 5]); - $this->diff(PlainNumbersArray::just(2, 3, 4))->toNativeNumbers()->shouldBe([1, 3.0, 5]); + $this->diff(PlainNumbersArray::fromNumbers(2, 3, 4))->toNativeNumbers()->shouldBe([1, 3.0, 5]); } function it_resolves_diff_by_comparator() { - $this->beConstructedThrough('just', [1, 2, 2, 3, 3.0, 4, 4, 4.6, 5, 5.9]); + $this->beConstructedThrough('fromNumbers', [1, 2, 2, 3, 3.0, 4, 4, 4.6, 5, 5.9]); - $this->diff(PlainNumbersArray::just(2, 3, 4), CompareAsInt::desc())->toNativeNumbers()->shouldBe([1, 5, 5.9]); + $this->diff(PlainNumbersArray::fromNumbers(2, 3, 4), CompareAsInt::desc())->toNativeNumbers()->shouldBe([1, 5, 5.9]); } function it_resolves_intersect() { - $this->beConstructedThrough('just', [1, 2, 2, 3, 3.0, 4, 4, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 2, 3, 3.0, 4, 4, 4, 5]); - $this->intersect(PlainNumbersArray::just(2, 3, 4))->toNativeNumbers()->shouldBe([2, 2, 3, 4, 4, 4]); + $this->intersect(PlainNumbersArray::fromNumbers(2, 3, 4))->toNativeNumbers()->shouldBe([2, 2, 3, 4, 4, 4]); } function it_resolves_intersect_by_comparator() { - $this->beConstructedThrough('just', [1, 2, 2, 3, 3.0, 4, 4, 4.6, 5, 5.9]); + $this->beConstructedThrough('fromNumbers', [1, 2, 2, 3, 3.0, 4, 4, 4.6, 5, 5.9]); - $this->intersect(PlainNumbersArray::just(2, 3, 4), CompareAsInt::asc()) + $this->intersect(PlainNumbersArray::fromNumbers(2, 3, 4), CompareAsInt::asc()) ->toNativeNumbers() ->shouldBe([2, 2, 3, 3.0, 4, 4, 4.6]); } @@ -345,7 +345,7 @@ function it_resolves_intersect_by_comparator() function it_shuffles_numbers() { $numbers = range(1, 1000); - $this->beConstructedThrough('just', [...$numbers]); + $this->beConstructedThrough('fromNumbers', [...$numbers]); $shuffled = $this->shuffle(); $shuffled->beAnInstanceOf(PlainNumbersArray::class); @@ -355,7 +355,7 @@ function it_shuffles_numbers() function it_unshift_value() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $new = $this->unshift(PlainNumber::from(6)); $new->toNativeNumbers()->shouldBe([6, 1, 2, 3, 4, 5]); @@ -363,7 +363,7 @@ function it_unshift_value() function it_shifts_value() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $new = $this->getWrappedObject()->shift($value); @@ -378,7 +378,7 @@ function it_shifts_value() function it_pushes_value() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $new = $this->push(PlainNumber::from(6)); $new->toNativeNumbers()->shouldBe([1, 2, 3, 4, 5, 6]); @@ -386,7 +386,7 @@ function it_pushes_value() function it_pops_value() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $new = $this->getWrappedObject()->pop($value); @@ -401,7 +401,7 @@ function it_pops_value() function it_reduces_numbers() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $this->reduce(fn(int $sum, NumberValue $number) => $sum + $number->toNumber(), 0) ->shouldBe(15); @@ -409,7 +409,7 @@ function it_reduces_numbers() function it_reduces_to_number_value() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $this ->reduceNumber( @@ -422,21 +422,21 @@ function it_reduces_to_number_value() function it_returns_first() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $this->first()->toNumber()->shouldBe(1); } function it_returns_last() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $this->last()->toNumber()->shouldBe(5); } function it_returns_null_as_first_and_last_when_empty() { - $this->beConstructedThrough('just', []); + $this->beConstructedThrough('fromNumbers', []); $this->first()->shouldBeNull(); $this->last()->shouldBeNull(); @@ -444,21 +444,21 @@ function it_returns_null_as_first_and_last_when_empty() function it_finds_first() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $this->find($this->isEven())->toNumber()->shouldBe(2); } function it_finds_last() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $this->findLast($this->isEven())->toNumber()->shouldBe(4); } function it_returns_null_when_not_found() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $this->find($this->isGreater(5))->shouldBeNull(); $this->findLast($this->isGreater(5))->shouldBeNull(); @@ -466,7 +466,7 @@ function it_returns_null_when_not_found() function it_resolves_any_and_every_condition() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $this->any($this->isEven())->shouldBe(true); $this->any($this->isGreater(4))->shouldBe(true); @@ -490,7 +490,7 @@ function it_resolves_that_array_has_element_strict() function it_is_countable() { - $this->beConstructedThrough('just', [1, 2, 3, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2, 3, 4, 5]); $this->count()->shouldBe(5); } @@ -533,21 +533,21 @@ function it_has_immutable_array_access() function it_implodes_to_string_value() { - $this->beConstructedThrough('just', [1, 2.5, 3.6, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2.5, 3.6, 4, 5]); $this->implode(', ')->toString()->shouldBe('1, 2.5, 3.6, 4, 5'); } function it_is_not_empty_when_has_number() { - $this->beConstructedThrough('just', [0]); + $this->beConstructedThrough('fromNumbers', [0]); $this->isEmpty()->shouldBe(false); } function it_is_empty_when_no_numbers() { - $this->beConstructedThrough('just', []); + $this->beConstructedThrough('fromNumbers', []); $this->isEmpty()->shouldBe(true); } @@ -566,14 +566,14 @@ function it_can_be_casted_to_association() function it_can_be_casted_to_strings_array() { - $this->beConstructedThrough('just', [1, 2.5, 3.6, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2.5, 3.6, 4, 5]); $this->toStringsArray()->toNativeStrings()->shouldBe(['1', '2.5', '3.6', '4', '5']); } function it_returns_self_on_toNumbersArray() { - $this->beConstructedThrough('just', [1, 2.5, 3.6, 4, 5]); + $this->beConstructedThrough('fromNumbers', [1, 2.5, 3.6, 4, 5]); $this->toNumbersArray()->shouldBe($this); } @@ -591,7 +591,7 @@ private function isGreater(int $than): Closure public function getMatchers(): array { return [ - 'haveOffset' => fn (PlainNumbersArray $subject, int $key): bool => $subject->offsetExists($key), + 'haveOffset' => fn(PlainNumbersArray $subject, int $key): bool => $subject->offsetExists($key), ]; } } diff --git a/spec/WrapSpec.php b/spec/WrapSpec.php index 9139da5..a035904 100644 --- a/spec/WrapSpec.php +++ b/spec/WrapSpec.php @@ -78,7 +78,7 @@ function it_should_return_NumbersArray_of_numberable() function it_should_return_NumbersArray_of_itself() { - $this->beConstructedThrough('numbersArray', [PlainNumbersArray::just(1, 2, 3.0)]); + $this->beConstructedThrough('numbersArray', [PlainNumbersArray::fromNumbers(1, 2, 3.0)]); $this->shouldHaveType(NumbersArray::class); } } diff --git a/src/NumberValue.php b/src/NumberValue.php index 55c7a85..9e13909 100644 --- a/src/NumberValue.php +++ b/src/NumberValue.php @@ -64,7 +64,7 @@ public function isEmpty(): bool; // casting - public function format(int $decimals = 0, string $separator = '.' , string $thousandsSeparator = ','): StringValue; + public function format(int $decimals = 0, string $separator = '.', string $thousandsSeparator = ','): StringValue; public function toStringValue(): StringValue; diff --git a/src/Numberable/Formula.php b/src/Numberable/Formula.php index 9455ffb..44a27c9 100644 --- a/src/Numberable/Formula.php +++ b/src/Numberable/Formula.php @@ -8,20 +8,20 @@ final class Formula implements Numberable { private Numberable $numberable; /** @var callable(int|float):(int|float) */ - private $fn; + private $calculation; /** - * @param callable(int|float):(int|float) $fn + * @param callable(int|float):(int|float) $calculation */ - public function __construct(Numberable $numberable, callable $fn) + public function __construct(Numberable $numberable, callable $calculation) { $this->numberable = $numberable; - $this->fn = $fn; + $this->calculation = $calculation; } /** @return int|float */ public function toNumber() { - return ($this->fn)($this->numberable->toNumber()); + return ($this->calculation)($this->numberable->toNumber()); } } diff --git a/src/PlainNumbersArray.php b/src/PlainNumbersArray.php index e751db3..b666a54 100644 --- a/src/PlainNumbersArray.php +++ b/src/PlainNumbersArray.php @@ -29,7 +29,7 @@ public static function fromArrayable(Arrayable $numbers): self } /** @param int|float|numeric-string|Numberable ...$numbers */ - public static function just(...$numbers): self + public static function fromNumbers(...$numbers): self { return self::fromArrayable(new NumberValues(...$numbers)); } @@ -54,7 +54,7 @@ public function max(): NumberValue return new PlainNumber(new Max($this)); } - /** @return int[]|float[] */ + /** @return (int|float)[] */ public function toNativeNumbers(): array { return $this->map(new ToScalarNumber())->toArray(); diff --git a/src/Wrap.php b/src/Wrap.php index 9e818be..152d21f 100644 --- a/src/Wrap.php +++ b/src/Wrap.php @@ -91,7 +91,7 @@ public static function numbersArray($numbers): NumbersArray return PlainNumbersArray::fromArrayable($numbers); } - return PlainNumbersArray::just(...$numbers); + return PlainNumbersArray::fromNumbers(...$numbers); } private function __construct() From 2d819b10d2e3e5d6be80e55e2f20a173026df5ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Sat, 8 Jan 2022 13:34:19 +0100 Subject: [PATCH 14/20] Adjust code style --- src/Numberable/Math.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Numberable/Math.php b/src/Numberable/Math.php index 150e47f..085e8a9 100644 --- a/src/Numberable/Math.php +++ b/src/Numberable/Math.php @@ -7,12 +7,12 @@ final class Math { /** @var callable(int|float):(int|float) */ - private $fn; + private $function; /** @param callable(int|float):(int|float) $fn */ - public function __construct(callable $fn) + public function __construct(callable $function) { - $this->fn = $fn; + $this->function = $function; } public static function cos(): self @@ -57,6 +57,6 @@ public static function sqrt(): self public function __invoke(Numberable $numberable): Numberable { - return new Formula($numberable, $this->fn); + return new Formula($numberable, $this->function); } } From 5402fb36edc1df2b070d2eff3309d1027ba2e5c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Sat, 8 Jan 2022 13:48:41 +0100 Subject: [PATCH 15/20] Adjust code style --- src/Numberable/Math.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Numberable/Math.php b/src/Numberable/Math.php index 085e8a9..197fe89 100644 --- a/src/Numberable/Math.php +++ b/src/Numberable/Math.php @@ -9,7 +9,7 @@ final class Math /** @var callable(int|float):(int|float) */ private $function; - /** @param callable(int|float):(int|float) $fn */ + /** @param callable(int|float):(int|float) $function */ public function __construct(callable $function) { $this->function = $function; From 9a0176f7cbba2c0b552859bdf1bcd41e2c6b501f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Sat, 8 Jan 2022 16:26:04 +0100 Subject: [PATCH 16/20] Change `NumberValue::calculate` signature --- README.md | 3 +- examples/example/NumberValue-calculate.php | 8 +-- spec/PlainNumberSpec.php | 22 ++++---- src/NumberValue.php | 2 +- src/Numberable/Calculate.php | 27 ++++++++++ src/Numberable/Formula.php | 27 ---------- src/Numberable/Math.php | 62 ---------------------- src/PlainNumber.php | 5 +- 8 files changed, 47 insertions(+), 109 deletions(-) create mode 100644 src/Numberable/Calculate.php delete mode 100644 src/Numberable/Formula.php delete mode 100644 src/Numberable/Math.php diff --git a/README.md b/README.md index 84a775d..1035701 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,6 @@ Object wrapping number (float or int). ```php multiply(2) ->divide(3) ->modulo(100) - ->calculate(Math::sqrt()) + ->calculate('sqrt') ->round(2); // ... diff --git a/examples/example/NumberValue-calculate.php b/examples/example/NumberValue-calculate.php index 1f08ba7..16caa26 100644 --- a/examples/example/NumberValue-calculate.php +++ b/examples/example/NumberValue-calculate.php @@ -2,7 +2,7 @@ use GW\Value\Numberable; use GW\Value\Numberable\JustInteger; -use GW\Value\Numberable\Math; +use GW\Value\Numberable\JustNumber; use GW\Value\Numberable\Multiply; use GW\Value\Wrap; @@ -10,14 +10,14 @@ echo "100 * 12 = "; echo $number - ->calculate(fn(Numberable $number): Numberable => new Multiply($number, new JustInteger(12))) + ->calculate(fn($number): Numberable => new Multiply(new JustNumber($number), new JustInteger(12))) ->toNumber(); echo "\n"; echo "cos(100) = "; -echo $number->calculate(Math::cos())->toNumber(); +echo $number->calculate('cos')->toNumber(); echo "\n"; echo "√100 = "; -echo $number->calculate(Math::sqrt())->toNumber(); +echo $number->calculate('sqrt')->toNumber(); echo "\n"; diff --git a/spec/PlainNumberSpec.php b/spec/PlainNumberSpec.php index 4ba43ab..3a795cd 100644 --- a/spec/PlainNumberSpec.php +++ b/spec/PlainNumberSpec.php @@ -7,7 +7,7 @@ use GW\Value\Numberable\Divide; use GW\Value\Numberable\JustFloat; use GW\Value\Numberable\JustInteger; -use GW\Value\Numberable\Math; +use GW\Value\Numberable\JustNumber; use GW\Value\Numberable\Add; use GW\Value\Numberable\Zero; use GW\Value\PlainNumber; @@ -376,8 +376,8 @@ function it_calculates_custom_formula() { $this->beConstructedWith(new JustInteger(100)); - $formula = fn(Numberable $number): Numberable => new Divide( - new Add($number, new JustInteger(700)), + $formula = fn(int $number): Numberable => new Divide( + new Add(new JustNumber($number), new JustInteger(700)), new JustInteger(2) ); @@ -388,13 +388,13 @@ function it_calculates_math_formulas() { $this->beConstructedWith(new JustInteger(90)); - $this->calculate(Math::cos())->toNumber()->shouldBe(cos(90)); - $this->divide(new JustInteger(100))->calculate(Math::acos())->toNumber()->shouldBe(acos(.90)); - $this->calculate(Math::sin())->toNumber()->shouldBe(sin(90)); - $this->divide(new JustInteger(100))->calculate(Math::asin())->toNumber()->shouldBe(asin(.90)); - $this->calculate(Math::tan())->toNumber()->shouldBe(tan(90)); - $this->divide(new JustInteger(100))->calculate(Math::atan())->toNumber()->shouldBe(atan(.90)); - $this->calculate(Math::exp())->toNumber()->shouldBe(exp(90)); - $this->calculate(Math::sqrt())->toNumber()->shouldBe(sqrt(90)); + $this->calculate('cos')->toNumber()->shouldBe(cos(90)); + $this->divide(new JustInteger(100))->calculate('acos')->toNumber()->shouldBe(acos(.90)); + $this->calculate('sin')->toNumber()->shouldBe(sin(90)); + $this->divide(new JustInteger(100))->calculate('asin')->toNumber()->shouldBe(asin(.90)); + $this->calculate('tan')->toNumber()->shouldBe(tan(90)); + $this->divide(new JustInteger(100))->calculate('atan')->toNumber()->shouldBe(atan(.90)); + $this->calculate('exp')->toNumber()->shouldBe(exp(90)); + $this->calculate('sqrt')->toNumber()->shouldBe(sqrt(90)); } } diff --git a/src/NumberValue.php b/src/NumberValue.php index 9e13909..186aa6b 100644 --- a/src/NumberValue.php +++ b/src/NumberValue.php @@ -54,7 +54,7 @@ public function floor(): NumberValue; public function ceil(): NumberValue; - /** @param callable(Numberable):Numberable $formula */ + /** @param callable(int|float):(int|float|Numberable) $formula */ public function calculate(callable $formula): NumberValue; // value diff --git a/src/Numberable/Calculate.php b/src/Numberable/Calculate.php new file mode 100644 index 0000000..405bbc5 --- /dev/null +++ b/src/Numberable/Calculate.php @@ -0,0 +1,27 @@ +numberable = $numberable; + $this->formula = $formula; + } + + /** @return int|float */ + public function toNumber() + { + $result = ($this->formula)($this->numberable->toNumber()); + + return JustNumber::wrap($result)->toNumber(); + } +} diff --git a/src/Numberable/Formula.php b/src/Numberable/Formula.php deleted file mode 100644 index 44a27c9..0000000 --- a/src/Numberable/Formula.php +++ /dev/null @@ -1,27 +0,0 @@ -numberable = $numberable; - $this->calculation = $calculation; - } - - /** @return int|float */ - public function toNumber() - { - return ($this->calculation)($this->numberable->toNumber()); - } -} diff --git a/src/Numberable/Math.php b/src/Numberable/Math.php deleted file mode 100644 index 197fe89..0000000 --- a/src/Numberable/Math.php +++ /dev/null @@ -1,62 +0,0 @@ -function = $function; - } - - public static function cos(): self - { - return new self('\cos'); - } - - public static function acos(): self - { - return new self('\acos'); - } - - public static function sin(): self - { - return new self('\sin'); - } - - public static function asin(): self - { - return new self('\asin'); - } - - public static function tan(): self - { - return new self('\tan'); - } - - public static function atan(): self - { - return new self('\atan'); - } - - public static function exp(): self - { - return new self('\exp'); - } - - public static function sqrt(): self - { - return new self('\sqrt'); - } - - public function __invoke(Numberable $numberable): Numberable - { - return new Formula($numberable, $this->function); - } -} diff --git a/src/PlainNumber.php b/src/PlainNumber.php index a51362e..e8dea49 100644 --- a/src/PlainNumber.php +++ b/src/PlainNumber.php @@ -4,6 +4,7 @@ use GW\Value\Numberable\Absolute; use GW\Value\Numberable\Add; +use GW\Value\Numberable\Calculate; use GW\Value\Numberable\Ceil; use GW\Value\Numberable\Divide; use GW\Value\Numberable\Floor; @@ -98,10 +99,10 @@ public function ceil(): NumberValue return new self(new Ceil($this->number)); } - /** @param callable(Numberable):Numberable $formula */ + /** @param callable(int|float):(int|float|Numberable) $formula */ public function calculate(callable $formula): NumberValue { - return new self($formula($this->number)); + return new self(new Calculate($this->number, $formula)); } public function isEmpty(): bool From e8373de605bceee4209d4741830ddd5e3a8735f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Sat, 8 Jan 2022 22:23:50 +0100 Subject: [PATCH 17/20] Tweak test coverage --- spec/PlainNumberSpec.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spec/PlainNumberSpec.php b/spec/PlainNumberSpec.php index 3a795cd..b3d5f47 100644 --- a/spec/PlainNumberSpec.php +++ b/spec/PlainNumberSpec.php @@ -4,11 +4,12 @@ use DivisionByZeroError; use GW\Value\Numberable; +use GW\Value\Numberable\Add; use GW\Value\Numberable\Divide; use GW\Value\Numberable\JustFloat; use GW\Value\Numberable\JustInteger; -use GW\Value\Numberable\JustNumber; -use GW\Value\Numberable\Add; +use GW\Value\Numberable\JustNumbers; +use GW\Value\Numberable\Sum; use GW\Value\Numberable\Zero; use GW\Value\PlainNumber; use PhpSpec\ObjectBehavior; @@ -377,7 +378,7 @@ function it_calculates_custom_formula() $this->beConstructedWith(new JustInteger(100)); $formula = fn(int $number): Numberable => new Divide( - new Add(new JustNumber($number), new JustInteger(700)), + new Sum(new JustNumbers($number, 300, 400)), new JustInteger(2) ); From d6dbcc36b2574408962e8545173c33d9c6efa000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Sun, 26 Feb 2023 17:46:28 +0100 Subject: [PATCH 18/20] Adjust code to PHP 8.0 --- docs/examples.md | 875 +++++++++++++++++++++++++++++- spec/PlainNumberSpec.php | 7 - src/NumberValue.php | 30 +- src/Numberable.php | 5 +- src/Numberable/Absolute.php | 11 +- src/Numberable/Add.php | 14 +- src/Numberable/Average.php | 14 +- src/Numberable/Calculate.php | 11 +- src/Numberable/Ceil.php | 8 +- src/Numberable/CompareAsInt.php | 8 +- src/Numberable/Divide.php | 14 +- src/Numberable/Floor.php | 8 +- src/Numberable/JustFloat.php | 8 +- src/Numberable/JustInteger.php | 8 +- src/Numberable/JustNumber.php | 28 +- src/Numberable/JustNumbers.php | 4 +- src/Numberable/Max.php | 3 +- src/Numberable/Min.php | 3 +- src/Numberable/Modulo.php | 11 +- src/Numberable/Multiply.php | 14 +- src/Numberable/NumberValues.php | 4 +- src/Numberable/NumericString.php | 27 + src/Numberable/Round.php | 18 +- src/Numberable/Subtract.php | 14 +- src/Numberable/Sum.php | 14 +- src/Numberable/SumReducer.php | 6 +- src/Numberable/ToNumberValue.php | 3 +- src/Numberable/ToNumberable.php | 5 +- src/Numberable/ToScalarNumber.php | 3 +- src/NumbersArray.php | 5 +- src/PlainNumber.php | 38 +- src/PlainNumbersArray.php | 23 +- src/Wrap.php | 8 +- 33 files changed, 1039 insertions(+), 213 deletions(-) create mode 100644 src/Numberable/NumericString.php diff --git a/docs/examples.md b/docs/examples.md index ccb4ae6..d6af72c 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -348,6 +348,7 @@ array ( ```php $size * @phpstan-return ArrayValue> */ public function chunk(int $size): ArrayValue; @@ -650,9 +651,9 @@ public function offsetExists($offset): bool; $other - * @param (callable(TValue,TValue):int)|null $comparator + * @param (callable(TValue,TValue):int<-1,1>)|null $comparator * @phpstan-return ArrayValue */ public function diff(ArrayValue $other, ?callable $comparator = null): ArrayValue; @@ -1154,6 +1155,14 @@ array ( public function toStringsArray(): StringsArray; ``` +### ArrayValue::toNumbersArray + +```php +> + * @return ArrayValue> */ public function matchAllPatterns($pattern): ArrayValue; ``` @@ -2782,7 +2791,7 @@ public function offsetExists($offset): bool; /** * @param int $offset */ -public function offsetGet($offset): StringValue; +public function offsetGet($offset): ?StringValue; ``` ### StringsArray::offsetSet @@ -2853,7 +2862,7 @@ public function splice(int $offset, int $length, ?StringsArray $replacement = nu ```php )|null $comparator */ public function diff(StringsArray $other, ?callable $comparator = null): StringsArray; ``` @@ -3657,6 +3666,7 @@ array ( ```php $size * @phpstan-return ArrayValue> */ public function chunk(int $size): ArrayValue; @@ -3715,7 +3725,7 @@ public function positionLast($needle): ?int; > + * @return ArrayValue> */ public function matchAllPatterns($pattern): ArrayValue; ``` @@ -4466,4 +4476,851 @@ array ( *(definition not available)* +## NumberValue + +### NumberValue::compare + +```php + + */ +public function compare(float|int|string|Numberable $other): int; +``` + +### NumberValue::equals + +```php +add(50)->toNumber(); +echo "\n"; + +echo "100 + 11.22 = "; +echo $number->add(new JustFloat(11.22))->toNumber(); +echo "\n"; + +echo "100 + (10 + 20 + 30) = "; +echo $number->add(new Sum(new JustNumbers(10, 20, 30)))->toNumber(); +echo "\n"; +``` + +``` +100 + 50 = 150 +100 + 11.22 = 111.22 +100 + (10 + 20 + 30) = 160 +``` + +### NumberValue::subtract + +```php +subtract(50)->toNumber(); +echo "\n"; + +echo "100 - 11.22 = "; +echo $number->subtract(new JustFloat(11.11))->toNumber(); +echo "\n"; + +echo "100 - (10 + 20 + 30) = "; +echo $number->subtract(new Sum(new JustNumbers(10, 20, 30)))->toNumber(); +echo "\n"; +``` + +``` +100 - 50 = 50 +100 - 11.22 = 88.89 +100 - (10 + 20 + 30) = 40 +``` + +### NumberValue::multiply + +```php +round(1)->toNumber(); +echo "\n"; +``` + +``` +round(22.55, 1) = 22.6 +``` + +### NumberValue::floor + +```php +calculate(fn($number): Numberable => new Multiply(new JustNumber($number), new JustInteger(12))) + ->toNumber(); +echo "\n"; + +echo "cos(100) = "; +echo $number->calculate('cos')->toNumber(); +echo "\n"; + +echo "√100 = "; +echo $number->calculate('sqrt')->toNumber(); +echo "\n"; +``` + +``` +100 * 12 = 1200 +cos(100) = 0.86231887228768 +√100 = 10 +``` + +### NumberValue::isEmpty + +```php +format(2)->toString(); +echo "\n"; + +echo $number->format(3, '.', ' ')->toString(); +echo "\n"; +``` + +``` +1,000.11 +1 000.111 +``` + +### NumberValue::toStringValue + +```php +sum()->toNumber(); +echo "\n"; +``` + +``` +1 + 2.5 + 5 + 10 = 18.5 +``` + +### NumbersArray::average + +```php +average()->toNumber(); +echo "\n"; +``` + +``` +avg(1, 3, 5, 10) = 4.75 +``` + +### NumbersArray::min + +```php +min()->toNumber(); +echo "\n"; +``` + +``` +min(100, 10, 50, 80) = 10 +``` + +### NumbersArray::max + +```php +max()->toNumber(); +echo "\n"; +``` + +``` +max(100, 10, 50, 80) = 100 +``` + +### NumbersArray::each + +```php + */ +public function toNativeNumbers(): array; +``` + +### NumbersArray::filter + +```php + + */ +public function map(callable $transformer): ArrayValue; +``` + +### NumbersArray::flatMap + +```php + $transformer + * @return ArrayValue + */ +public function flatMap(callable $transformer): ArrayValue; +``` + +### NumbersArray::groupBy + +```php +> + */ +public function groupBy(callable $reducer): AssocValue; +``` + +### NumbersArray::sort + +```php + $other + */ +public function join(ArrayValue $other): NumbersArray; +``` + +### NumbersArray::slice + +```php +|null $replacement + */ +public function splice(int $offset, int $length, ?ArrayValue $replacement = null): NumbersArray; +``` + +### NumbersArray::diff + +```php + $other + * @param (callable(NumberValue,NumberValue):int)|null $comparator + */ +public function diff(ArrayValue $other, ?callable $comparator = null): NumbersArray; +``` + +### NumbersArray::intersect + +```php + $other + * @param (callable(NumberValue,NumberValue):int)|null $comparator + */ +public function intersect(ArrayValue $other, ?callable $comparator = null): NumbersArray; +``` + +### NumbersArray::reduce + +```php +reduceNumber( + fn(NumberValue $factorial, NumberValue $next): NumberValue => $factorial->multiply($next), + 1 + ) + ->toNumber(); +echo "\n"; +``` + +``` +5! = 120 +``` + +### NumbersArray::implode + +```php + + */ +public function toAssocValue(): AssocValue; +``` + +### NumbersArray::toStringsArray + +```php + $size + * @phpstan-return ArrayValue> + */ +public function chunk(int $size): ArrayValue; +``` + +### NumbersArray::toNumbersArray + +```php +toNumber()->shouldBe(123.66); } - function it_can_be_created_from_numeric_string() - { - $this->beConstructedThrough('from', ['123.66']); - - $this->toNumber()->shouldBe(123.66); - } - function it_compares_int_with_numbers_just_like_scalars() { $this->beConstructedWith(new JustInteger(123)); diff --git a/src/NumberValue.php b/src/NumberValue.php index 186aa6b..a85e7b0 100644 --- a/src/NumberValue.php +++ b/src/NumberValue.php @@ -7,44 +7,44 @@ interface NumberValue extends Value, Numberable // comparators /** - * @param int|float|numeric-string|Numberable $other - * @return int {-1, 0, 1} + * @param float|int|numeric-string|Numberable $other + * @return int<-1,1> */ - public function compare($other): int; + public function compare(float|int|string|Numberable $other): int; /** - * @param int|float|numeric-string|Numberable $other + * @param float|int|numeric-string|Numberable $other */ - public function equals($other): bool; + public function equals(float|int|string|Numberable $other): bool; // basic math /** - * @param int|float|numeric-string|Numberable $other + * @param float|int|numeric-string|Numberable $other */ - public function add($other): NumberValue; + public function add(float|int|string|Numberable $other): NumberValue; /** - * @param int|float|numeric-string|Numberable $other + * @param float|int|numeric-string|Numberable $other */ - public function subtract($other): NumberValue; + public function subtract(float|int|string|Numberable $other): NumberValue; /** - * @param int|float|numeric-string|Numberable $other + * @param float|int|numeric-string|Numberable $other */ - public function multiply($other): NumberValue; + public function multiply(float|int|string|Numberable $other): NumberValue; /** - * @param int|float|numeric-string|Numberable $other + * @param float|int|numeric-string|Numberable $other */ - public function divide($other): NumberValue; + public function divide(float|int|string|Numberable $other): NumberValue; public function abs(): NumberValue; /** - * @param int|float|numeric-string|Numberable $divider + * @param float|int|numeric-string|Numberable $divider */ - public function modulo($divider): NumberValue; + public function modulo(float|int|string|Numberable $divider): NumberValue; // rounding diff --git a/src/Numberable.php b/src/Numberable.php index 726ff91..988e160 100644 --- a/src/Numberable.php +++ b/src/Numberable.php @@ -4,8 +4,5 @@ interface Numberable { - /** - * @return int|float - */ - public function toNumber(); + public function toNumber(): float|int; } diff --git a/src/Numberable/Absolute.php b/src/Numberable/Absolute.php index e87f956..dc7c95e 100644 --- a/src/Numberable/Absolute.php +++ b/src/Numberable/Absolute.php @@ -7,15 +7,12 @@ final class Absolute implements Numberable { - private Numberable $numberable; - - public function __construct(Numberable $numberable) - { - $this->numberable = $numberable; + public function __construct( + private Numberable $numberable, + ) { } - /** @return int|float */ - public function toNumber() + public function toNumber(): float|int { return abs($this->numberable->toNumber()); } diff --git a/src/Numberable/Add.php b/src/Numberable/Add.php index c5247dd..a3a7200 100644 --- a/src/Numberable/Add.php +++ b/src/Numberable/Add.php @@ -6,17 +6,13 @@ final class Add implements Numberable { - private Numberable $leftTerm; - private Numberable $rightTerm; - - public function __construct(Numberable $leftTerm, Numberable $rightTerm) - { - $this->leftTerm = $leftTerm; - $this->rightTerm = $rightTerm; + public function __construct( + private Numberable $leftTerm, + private Numberable $rightTerm, + ) { } - /** @return int|float */ - public function toNumber() + public function toNumber(): float|int { return $this->leftTerm->toNumber() + $this->rightTerm->toNumber(); } diff --git a/src/Numberable/Average.php b/src/Numberable/Average.php index 59b0a1b..475d5ce 100644 --- a/src/Numberable/Average.php +++ b/src/Numberable/Average.php @@ -9,17 +9,13 @@ final class Average implements Numberable { - /** @var Arrayable */ - private Arrayable $terms; - - /** @param Arrayable $terms */ - public function __construct(Arrayable $terms) - { - $this->terms = $terms; + public function __construct( + /** @var Arrayable */ + private Arrayable $terms, + ) { } - /** @return int|float */ - public function toNumber() + public function toNumber(): float|int { $terms = $this->terms->toArray(); $count = count($terms); diff --git a/src/Numberable/Calculate.php b/src/Numberable/Calculate.php index 405bbc5..915232e 100644 --- a/src/Numberable/Calculate.php +++ b/src/Numberable/Calculate.php @@ -6,19 +6,18 @@ final class Calculate implements Numberable { - private Numberable $numberable; /** @var callable(int|float):(int|float|Numberable) */ private $formula; /** @param callable(int|float):(int|float|Numberable) $formula */ - public function __construct(Numberable $numberable, callable $formula) - { - $this->numberable = $numberable; + public function __construct( + private Numberable $numberable, + callable $formula, + ) { $this->formula = $formula; } - /** @return int|float */ - public function toNumber() + public function toNumber(): float|int { $result = ($this->formula)($this->numberable->toNumber()); diff --git a/src/Numberable/Ceil.php b/src/Numberable/Ceil.php index bd9c99b..cf0474a 100644 --- a/src/Numberable/Ceil.php +++ b/src/Numberable/Ceil.php @@ -7,11 +7,9 @@ final class Ceil implements Numberable { - private Numberable $numberable; - - public function __construct(Numberable $numberable) - { - $this->numberable = $numberable; + public function __construct( + private Numberable $numberable, + ) { } public function toNumber(): float diff --git a/src/Numberable/CompareAsInt.php b/src/Numberable/CompareAsInt.php index 99fd403..f8d723d 100644 --- a/src/Numberable/CompareAsInt.php +++ b/src/Numberable/CompareAsInt.php @@ -6,11 +6,9 @@ final class CompareAsInt { - private int $direction; - - private function __construct(int $direction) - { - $this->direction = $direction; + private function __construct( + private int $direction, + ) { } public static function asc(): self diff --git a/src/Numberable/Divide.php b/src/Numberable/Divide.php index c5fe5ce..f714217 100644 --- a/src/Numberable/Divide.php +++ b/src/Numberable/Divide.php @@ -7,17 +7,13 @@ final class Divide implements Numberable { - private Numberable $dividend; - private Numberable $divisor; - - public function __construct(Numberable $dividend, Numberable $divisor) - { - $this->dividend = $dividend; - $this->divisor = $divisor; + public function __construct( + private Numberable $dividend, + private Numberable $divisor, + ) { } - /** @return int|float */ - public function toNumber() + public function toNumber(): float|int { $divisor = $this->divisor->toNumber(); diff --git a/src/Numberable/Floor.php b/src/Numberable/Floor.php index f097ef0..d5bd66b 100644 --- a/src/Numberable/Floor.php +++ b/src/Numberable/Floor.php @@ -7,11 +7,9 @@ final class Floor implements Numberable { - private Numberable $numberable; - - public function __construct(Numberable $numberable) - { - $this->numberable = $numberable; + public function __construct( + private Numberable $numberable, + ) { } public function toNumber(): float diff --git a/src/Numberable/JustFloat.php b/src/Numberable/JustFloat.php index 702a6a3..92b462a 100644 --- a/src/Numberable/JustFloat.php +++ b/src/Numberable/JustFloat.php @@ -6,11 +6,9 @@ final class JustFloat implements Numberable { - private float $float; - - public function __construct(float $float) - { - $this->float = $float; + public function __construct( + private float $float, + ) { } public function toNumber(): float diff --git a/src/Numberable/JustInteger.php b/src/Numberable/JustInteger.php index 6c589f0..a4c16d2 100644 --- a/src/Numberable/JustInteger.php +++ b/src/Numberable/JustInteger.php @@ -6,11 +6,9 @@ final class JustInteger implements Numberable { - private int $integer; - - public function __construct(int $integer) - { - $this->integer = $integer; + public function __construct( + private int $integer, + ) { } public function toNumber(): int diff --git a/src/Numberable/JustNumber.php b/src/Numberable/JustNumber.php index 8d4f73b..9a3a3c8 100644 --- a/src/Numberable/JustNumber.php +++ b/src/Numberable/JustNumber.php @@ -3,26 +3,30 @@ namespace GW\Value\Numberable; use GW\Value\Numberable; +use function is_string; final class JustNumber implements Numberable { - /** @var int|float */ - private $number; - - /** @param int|float|numeric-string $number */ - public function __construct($number) - { - $this->number = $number + 0; + public function __construct( + private float|int $number, + ) { } - /** @param int|float|numeric-string|Numberable $number */ - public static function wrap($number): Numberable + /** @param float|int|numeric-string|Numberable $number */ + public static function wrap(float|int|string|Numberable $number): Numberable { - return $number instanceof Numberable ? $number : new self($number); + if (is_string($number)) { + return new NumericString($number); + } + + if ($number instanceof Numberable) { + return $number; + } + + return new self($number); } - /** @return int|float */ - public function toNumber() + public function toNumber(): float|int { return $this->number; } diff --git a/src/Numberable/JustNumbers.php b/src/Numberable/JustNumbers.php index eb84e06..8163380 100644 --- a/src/Numberable/JustNumbers.php +++ b/src/Numberable/JustNumbers.php @@ -15,8 +15,8 @@ final class JustNumbers implements Arrayable /** @var Arrayable */ private Arrayable $numbers; - /** @param int|float|numeric-string|Numberable ...$numbers */ - public function __construct(...$numbers) + /** @param float|int|numeric-string|Numberable ...$numbers */ + public function __construct(float|int|string|Numberable ...$numbers) { $this->numbers = new Map(new JustArray($numbers), new ToNumberable()); } diff --git a/src/Numberable/Max.php b/src/Numberable/Max.php index af46445..6b8109f 100644 --- a/src/Numberable/Max.php +++ b/src/Numberable/Max.php @@ -19,8 +19,7 @@ public function __construct(Arrayable $numbers) $this->numbers = new Map($numbers, new ToScalarNumber()); } - /** @return int|float */ - public function toNumber() + public function toNumber(): float|int { $numbers = $this->numbers->toArray(); if (count($numbers) === 0) { diff --git a/src/Numberable/Min.php b/src/Numberable/Min.php index 4b1045f..93bac7d 100644 --- a/src/Numberable/Min.php +++ b/src/Numberable/Min.php @@ -20,8 +20,7 @@ public function __construct(Arrayable $numbers) $this->numbers = new Map($numbers, new ToScalarNumber()); } - /** @return int|float */ - public function toNumber() + public function toNumber(): float|int { $numbers = $this->numbers->toArray(); if (count($numbers) === 0) { diff --git a/src/Numberable/Modulo.php b/src/Numberable/Modulo.php index ef9a091..e99fe2c 100644 --- a/src/Numberable/Modulo.php +++ b/src/Numberable/Modulo.php @@ -6,13 +6,10 @@ final class Modulo implements Numberable { - private Numberable $dividend; - private Numberable $divisor; - - public function __construct(Numberable $dividend, Numberable $divisor) - { - $this->dividend = $dividend; - $this->divisor = $divisor; + public function __construct( + private Numberable $dividend, + private Numberable $divisor, + ) { } public function toNumber(): int diff --git a/src/Numberable/Multiply.php b/src/Numberable/Multiply.php index b2b6e27..8757a06 100644 --- a/src/Numberable/Multiply.php +++ b/src/Numberable/Multiply.php @@ -6,17 +6,13 @@ final class Multiply implements Numberable { - private Numberable $right; - private Numberable $left; - - public function __construct(Numberable $left, Numberable $right) - { - $this->left = $left; - $this->right = $right; + public function __construct( + private Numberable $left, + private Numberable $right, + ) { } - /** @return int|float */ - public function toNumber() + public function toNumber(): float|int { return $this->left->toNumber() * $this->right->toNumber(); } diff --git a/src/Numberable/NumberValues.php b/src/Numberable/NumberValues.php index 4069386..936e736 100644 --- a/src/Numberable/NumberValues.php +++ b/src/Numberable/NumberValues.php @@ -16,8 +16,8 @@ final class NumberValues implements Arrayable /** @var Arrayable */ private Arrayable $numbers; - /** @param int|float|numeric-string|Numberable ...$numbers */ - public function __construct(...$numbers) + /** @param float|int|numeric-string|Numberable ...$numbers */ + public function __construct(float|int|string|Numberable ...$numbers) { $this->numbers = new Map(new JustArray($numbers), new ToNumberValue()); } diff --git a/src/Numberable/NumericString.php b/src/Numberable/NumericString.php new file mode 100644 index 0000000..698ba35 --- /dev/null +++ b/src/Numberable/NumericString.php @@ -0,0 +1,27 @@ +number = $number + 0; + } + + public function toNumber(): int|float + { + return $this->number; + } +} diff --git a/src/Numberable/Round.php b/src/Numberable/Round.php index d669964..102d0d3 100644 --- a/src/Numberable/Round.php +++ b/src/Numberable/Round.php @@ -8,19 +8,19 @@ final class Round implements Numberable { - private Numberable $numberable; - private int $precision; - private ?int $mode; + /** @var int<1,4> */ + private int $mode; - public function __construct(Numberable $numberable, int $precision, ?int $mode = null) - { - $this->numberable = $numberable; - $this->precision = $precision; - $this->mode = $mode; + public function __construct( + private Numberable $numberable, + private int $precision, + ?int $mode = null, + ) { + $this->mode = $mode >= 1 && $mode <= 4 ? $mode : PHP_ROUND_HALF_UP; } public function toNumber(): float { - return round($this->numberable->toNumber(), $this->precision, $this->mode ?? PHP_ROUND_HALF_UP); + return round($this->numberable->toNumber(), $this->precision, $this->mode); } } diff --git a/src/Numberable/Subtract.php b/src/Numberable/Subtract.php index a378466..951116f 100644 --- a/src/Numberable/Subtract.php +++ b/src/Numberable/Subtract.php @@ -6,17 +6,13 @@ final class Subtract implements Numberable { - private Numberable $minuend; - private Numberable $subtrahend; - - public function __construct(Numberable $minuend, Numberable $subtrahend) - { - $this->minuend = $minuend; - $this->subtrahend = $subtrahend; + public function __construct( + private Numberable $minuend, + private Numberable $subtrahend, + ) { } - /** @return int|float */ - public function toNumber() + public function toNumber(): float|int { return $this->minuend->toNumber() - $this->subtrahend->toNumber(); } diff --git a/src/Numberable/Sum.php b/src/Numberable/Sum.php index 986fe49..04501e0 100644 --- a/src/Numberable/Sum.php +++ b/src/Numberable/Sum.php @@ -8,17 +8,13 @@ final class Sum implements Numberable { - /** @var Arrayable */ - private Arrayable $terms; - - /** @param Arrayable $terms */ - public function __construct(Arrayable $terms) - { - $this->terms = $terms; + public function __construct( + /** @var Arrayable */ + private Arrayable $terms, + ) { } - /** @return int|float */ - public function toNumber() + public function toNumber(): float|int { return array_reduce($this->terms->toArray(), new SumReducer(), 0); } diff --git a/src/Numberable/SumReducer.php b/src/Numberable/SumReducer.php index 053c49e..10bcfb6 100644 --- a/src/Numberable/SumReducer.php +++ b/src/Numberable/SumReducer.php @@ -6,11 +6,7 @@ final class SumReducer { - /** - * @param int|float $sum - * @return int|float - */ - public function __invoke($sum, Numberable $next) + public function __invoke(float|int $sum, Numberable $next): float|int { return $sum + $next->toNumber(); } diff --git a/src/Numberable/ToNumberValue.php b/src/Numberable/ToNumberValue.php index e314a02..0a6dc5f 100644 --- a/src/Numberable/ToNumberValue.php +++ b/src/Numberable/ToNumberValue.php @@ -14,8 +14,7 @@ public function __construct() $this->toNumberable = new ToNumberable(); } - /** @param mixed $number */ - public function __invoke($number): NumberValue + public function __invoke(mixed $number): NumberValue { $numberable = ($this->toNumberable)($number); diff --git a/src/Numberable/ToNumberable.php b/src/Numberable/ToNumberable.php index abd2e4d..b9ef794 100644 --- a/src/Numberable/ToNumberable.php +++ b/src/Numberable/ToNumberable.php @@ -11,8 +11,7 @@ final class ToNumberable { - /** @param mixed $number */ - public function __invoke($number): Numberable + public function __invoke(mixed $number): Numberable { if ($number instanceof Numberable) { return $number; @@ -27,7 +26,7 @@ public function __invoke($number): Numberable } if (is_string($number) && is_numeric($number)) { - return new JustNumber($number); + return new NumericString($number); } throw new LogicException('Cannot cast value to Numberable'); diff --git a/src/Numberable/ToScalarNumber.php b/src/Numberable/ToScalarNumber.php index 8f08bdb..14cc0b6 100644 --- a/src/Numberable/ToScalarNumber.php +++ b/src/Numberable/ToScalarNumber.php @@ -6,8 +6,7 @@ final class ToScalarNumber { - /** @return int|float */ - public function __invoke(Numberable $value) + public function __invoke(Numberable $value): float|int { return $value->toNumber(); } diff --git a/src/NumbersArray.php b/src/NumbersArray.php index ac396de..8802bce 100644 --- a/src/NumbersArray.php +++ b/src/NumbersArray.php @@ -89,7 +89,7 @@ public function pop(&$value = null): NumbersArray; public function offsetExists($offset): bool; - public function offsetGet($offset): NumberValue; + public function offsetGet($offset): ?NumberValue; public function offsetSet($offset, $value): void; @@ -133,9 +133,8 @@ public function reduce(callable $transformer, $start); /** * @param callable(NumberValue $reduced, NumberValue $item):NumberValue $transformer - * @param int|float|numeric-string|Numberable $start */ - public function reduceNumber(callable $transformer, $start): NumberValue; + public function reduceNumber(callable $transformer, float|int|Numberable $start): NumberValue; public function implode(string $glue): StringValue; diff --git a/src/PlainNumber.php b/src/PlainNumber.php index e8dea49..06395e6 100644 --- a/src/PlainNumber.php +++ b/src/PlainNumber.php @@ -26,8 +26,8 @@ public function __construct(Numberable $number) $this->number = $number; } - /** @param int|float|numeric-string|Numberable $number */ - public static function from($number): self + /** @param float|int|numeric-string|Numberable $number */ + public static function from(float|int|string|Numberable $number): self { return new self(JustNumber::wrap($number)); } @@ -37,44 +37,47 @@ public function format(int $decimals = 0, string $separator = '.', string $thous return Wrap::string(number_format($this->number->toNumber(), $decimals, $separator, $thousandsSeparator)); } - /** @param int|float|numeric-string|Numberable $other */ - public function compare($other): int + /** + * @param float|int|numeric-string|Numberable $other + * @return int<-1,1> + */ + public function compare(float|int|string|Numberable $other): int { return $this->toNumber() <=> JustNumber::wrap($other)->toNumber(); } - /** @param int|float|numeric-string|Numberable $other */ - public function equals($other): bool + /** @param float|int|numeric-string|Numberable $other */ + public function equals(float|int|string|Numberable $other): bool { return $this->compare($other) === 0; } - /** @param int|float|numeric-string|Numberable $other */ - public function add($other): NumberValue + /** @param float|int|numeric-string|Numberable $other */ + public function add(float|int|string|Numberable $other): NumberValue { return new self(new Add($this->number, JustNumber::wrap($other))); } - /** @param int|float|numeric-string|Numberable $other */ - public function subtract($other): NumberValue + /** @param float|int|numeric-string|Numberable $other */ + public function subtract(float|int|string|Numberable $other): NumberValue { return new self(new Subtract($this->number, JustNumber::wrap($other))); } - /** @param int|float|numeric-string|Numberable $other */ - public function multiply($other): NumberValue + /** @param float|int|numeric-string|Numberable $other */ + public function multiply(float|int|string|Numberable $other): NumberValue { return new self(new Multiply($this->number, JustNumber::wrap($other))); } - /** @param int|float|numeric-string|Numberable $other */ - public function divide($other): NumberValue + /** @param float|int|numeric-string|Numberable $other */ + public function divide(float|int|string|Numberable $other): NumberValue { return new self(new Divide($this->number, JustNumber::wrap($other))); } - /** @param int|float|numeric-string|Numberable $divider */ - public function modulo($divider): NumberValue + /** @param float|int|numeric-string|Numberable $divider */ + public function modulo(float|int|string|Numberable $divider): NumberValue { return new self(new Modulo($this->number, JustNumber::wrap($divider))); } @@ -110,8 +113,7 @@ public function isEmpty(): bool return $this->toFloat() === 0.0; } - /** @return int|float */ - public function toNumber() + public function toNumber(): float|int { return $this->number->toNumber(); } diff --git a/src/PlainNumbersArray.php b/src/PlainNumbersArray.php index b666a54..27c533b 100644 --- a/src/PlainNumbersArray.php +++ b/src/PlainNumbersArray.php @@ -13,13 +13,10 @@ final class PlainNumbersArray implements NumbersArray { - /** @var ArrayValue */ - private ArrayValue $numbers; - - /** @param ArrayValue $numbers */ - public function __construct(ArrayValue $numbers) - { - $this->numbers = $numbers; + public function __construct( + /** @var ArrayValue */ + private ArrayValue $numbers, + ) { } /** @param Arrayable $numbers */ @@ -28,8 +25,8 @@ public static function fromArrayable(Arrayable $numbers): self return new self(new PlainArray($numbers)); } - /** @param int|float|numeric-string|Numberable ...$numbers */ - public static function fromNumbers(...$numbers): self + /** @param float|int|numeric-string|Numberable ...$numbers */ + public static function fromNumbers(float|int|string|Numberable ...$numbers): self { return self::fromArrayable(new NumberValues(...$numbers)); } @@ -205,7 +202,7 @@ public function offsetExists($offset): bool /** * @param int $offset */ - public function offsetGet($offset): NumberValue + public function offsetGet($offset): ?NumberValue { return $this->numbers->offsetGet($offset); } @@ -262,7 +259,7 @@ public function splice(int $offset, int $length, ?ArrayValue $replacement = null /** * @param ArrayValue $other - * @param (callable(NumberValue,NumberValue):int)|null $comparator + * @param (callable(NumberValue,NumberValue):int<-1,1>)|null $comparator */ public function diff(ArrayValue $other, ?callable $comparator = null): NumbersArray { @@ -291,9 +288,9 @@ public function reduce(callable $transformer, $start) /** * @param callable(NumberValue $reduced, NumberValue $item):NumberValue $transformer - * @param int|float|numeric-string|Numberable $start + * @param float|int|Numberable $start */ - public function reduceNumber(callable $transformer, $start): NumberValue + public function reduceNumber(callable $transformer, float|int|Numberable $start): NumberValue { return $this->numbers->reduce($transformer, Wrap::number($start)); } diff --git a/src/Wrap.php b/src/Wrap.php index 152d21f..bc62075 100644 --- a/src/Wrap.php +++ b/src/Wrap.php @@ -67,9 +67,9 @@ public static function stringsArray(array $strings = []): StringsArray } /** - * @param int|float|numeric-string|Numberable $number + * @param float|int|numeric-string|Numberable $number */ - public static function number($number): NumberValue + public static function number(float|int|string|Numberable $number): NumberValue { if ($number instanceof NumberValue) { return $number; @@ -79,9 +79,9 @@ public static function number($number): NumberValue } /** - * @param array|Arrayable|NumbersArray $numbers + * @param array|Arrayable|NumbersArray $numbers */ - public static function numbersArray($numbers): NumbersArray + public static function numbersArray(array|Arrayable|NumbersArray $numbers): NumbersArray { if ($numbers instanceof NumbersArray) { return $numbers; From 7a3290c12430393351dbf2280c12e2f0c6beedf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Mon, 27 Feb 2023 19:41:05 +0100 Subject: [PATCH 19/20] Fix matchAllPatterns PHPStan analysis --- src/PlainString.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PlainString.php b/src/PlainString.php index d07d534..ee098ac 100644 --- a/src/PlainString.php +++ b/src/PlainString.php @@ -257,6 +257,7 @@ public function matchAllPatterns($pattern): ArrayValue throw new RuntimeException("Failed to match regexp: {$pattern}"); } + /** @var array> $matches */ return Wrap::array($matches); } From 234acac45785fa9a6d7ba553e1ec46032a65eb5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Zaprza=C5=82ek?= Date: Sat, 30 Sep 2023 13:25:14 +0200 Subject: [PATCH 20/20] Fix example --- examples/example/NumberValue-subtract.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example/NumberValue-subtract.php b/examples/example/NumberValue-subtract.php index 8ad326d..34de615 100644 --- a/examples/example/NumberValue-subtract.php +++ b/examples/example/NumberValue-subtract.php @@ -11,7 +11,7 @@ echo $number->subtract(50)->toNumber(); echo "\n"; -echo "100 - 11.22 = "; +echo "100 - 11.11 = "; echo $number->subtract(new JustFloat(11.11))->toNumber(); echo "\n";