diff --git a/spec/AssocArraySpec.php b/spec/AssocArraySpec.php index b6fc008..06e262b 100644 --- a/spec/AssocArraySpec.php +++ b/spec/AssocArraySpec.php @@ -5,6 +5,7 @@ use GW\Value\Filters; use GW\Value\Wrap; use GW\Value\AssocArray; +use InvalidArgumentException; use PhpSpec\ObjectBehavior; use PHPUnit\Framework\Assert; @@ -517,4 +518,52 @@ function it_handles_numeric_strings_key_as_int_from_array() $this->map(fn(string $val, int $key): string => $val) ->keys()->toArray()->shouldEqual([0, 1]); } + + function it_can_be_flipped() + { + $this->beConstructedWith(['foo' => 'zero', 'bar' => 'one']); + $this->flip() + ->keys()->toArray()->shouldEqual(['zero', 'one']); + $this->flip() + ->values()->toArray()->shouldEqual(['foo', 'bar']); + } + + function it_can_be_flipped_numeric_keys() + { + $this->beConstructedWith(['0' => 'zero', '1' => 'one']); + $this->flip() + ->keys()->toArray()->shouldEqual(['zero', 'one']); + $this->flip() + ->values()->toArray()->shouldEqual([0, 1]); + } + + function it_is_possible_to_swap_keys() + { + $this->beConstructedWith(['foo' => 'zero', 'bar' => 'one']); + $this->swap('foo', 'bar') + ->toAssocArray()->shouldEqual(['foo' => 'one', 'bar' => 'zero']); + } + + function it_is_impossible_to_swap_keys_undefined_a() + { + $this->beConstructedWith(['foo' => 'zero', 'bar' => 'one']); + $swapped = $this->swap('baz', 'bar'); + $swapped->shouldThrow(InvalidArgumentException::class) + ->during('toAssocArray'); + } + + function it_is_impossible_to_swap_keys_undefined_b() + { + $this->beConstructedWith(['foo' => 'zero', 'bar' => 'one']); + $swapped = $this->swap('bar', 'baz'); + $swapped->shouldThrow(InvalidArgumentException::class) + ->during('toAssocArray'); + } + + function it_is_possible_to_swap_keys_numeric_arrays() + { + $this->beConstructedWith(['0' => 'zero', '1' => 'one']); + $this->swap(0, 1) + ->toAssocArray()->shouldEqual([0 => 'one', 1 => 'zero']); + } } diff --git a/spec/InfiniteIterableValueSpec.php b/spec/InfiniteIterableValueSpec.php index 01958ea..afd3fa2 100644 --- a/spec/InfiniteIterableValueSpec.php +++ b/spec/InfiniteIterableValueSpec.php @@ -676,6 +676,30 @@ function it_handles_object_keys() ->keys()->toArray()->shouldBeLike([(object)['foo' => 'bar'], (object)['foo' => 'baz']]); } + function it_can_be_flipped() + { + $this->beConstructedWith(['foo' => 'zero', 'bar' => 'one']); + $this->flip() + ->keys()->toArray()->shouldEqual(['zero', 'one']); + $this->flip() + ->values()->toArray()->shouldEqual(['foo', 'bar']); + } + + function it_can_be_flipped_numeric_keys() + { + $pairs = [['0', 'zero'], ['1', 'one']]; + + $iterator = function () use ($pairs) { + foreach ($pairs as [$key, $item]) { + yield $key => $item; + } + }; + + $this->beConstructedWith($iterator()); + $this->flip() + ->values()->toArray()->shouldEqual(['0', '1']); + } + private function entityComparator(): \Closure { return function (DummyEntity $entityA, DummyEntity $entityB): int { diff --git a/src/AssocArray.php b/src/AssocArray.php index d87b424..11fdcac 100644 --- a/src/AssocArray.php +++ b/src/AssocArray.php @@ -2,10 +2,10 @@ namespace GW\Value; -use ArrayIterator; use BadMethodCallException; use GW\Value\Associable\Cache; use GW\Value\Associable\Filter; +use GW\Value\Associable\Flip; use GW\Value\Associable\Join; use GW\Value\Associable\JustAssoc; use GW\Value\Associable\Keys; @@ -18,6 +18,7 @@ use GW\Value\Associable\Shuffle; use GW\Value\Associable\Sort; use GW\Value\Associable\SortKeys; +use GW\Value\Associable\Swap; use GW\Value\Associable\UniqueByComparator; use GW\Value\Associable\UniqueByString; use GW\Value\Associable\Values; @@ -216,6 +217,32 @@ public function withoutElement($value): AssocArray return $this->filter(Filters::notEqual($value)); } + /** + * @phpstan-return IterableValue + */ + public function toIterableValue(): IterableValue + { + return new InfiniteIterableValue($this->toAssocArray()); + } + + /** + * @phpstan-return AssocValue + */ + public function flip(): AssocValue + { + return new self(new Flip($this->items)); + } + + /** + * @param TKey $keyA + * @param TKey $keyB + * @phpstan-return AssocArray + */ + public function swap($keyA, $keyB): AssocArray + { + return new self(new Swap($this->items, $keyA, $keyB)); + } + /** * @template TNewValue * @param callable(TNewValue,TValue,TKey):TNewValue $transformer diff --git a/src/AssocValue.php b/src/AssocValue.php index 682b24e..02d2fa0 100644 --- a/src/AssocValue.php +++ b/src/AssocValue.php @@ -3,6 +3,7 @@ namespace GW\Value; use BadMethodCallException; +use GW\Value\Associable\Flip; use IteratorAggregate; use ArrayAccess; @@ -143,6 +144,18 @@ public function only(...$keys): AssocValue; */ public function withoutElement($value): AssocValue; + /** + * @phpstan-return AssocValue + */ + public function flip(): AssocValue; + + /** + * @param TKey $keyA + * @param TKey $keyB + * @phpstan-return AssocArray + */ + public function swap($keyA, $keyB): AssocArray; + /** * @deprecated use join() or replace() instead * @phpstan-param AssocValue $other diff --git a/src/Associable/Flip.php b/src/Associable/Flip.php new file mode 100644 index 0000000..2a71df3 --- /dev/null +++ b/src/Associable/Flip.php @@ -0,0 +1,31 @@ + + */ +final class Flip implements Associable +{ + /** @var Associable */ + private Associable $associable; + + /** + * @param Associable $associable + */ + public function __construct(Associable $associable) + { + $this->associable = $associable; + } + + /** @return array */ + public function toAssocArray(): array + { + return array_flip($this->associable->toAssocArray()); + } +} diff --git a/src/Associable/Swap.php b/src/Associable/Swap.php new file mode 100644 index 0000000..ee28ab5 --- /dev/null +++ b/src/Associable/Swap.php @@ -0,0 +1,45 @@ + + */ +final class Swap implements Associable +{ + /** @var Associable */ + private Associable $associable; + /** @var TKey */ + private $keyA; + /** @var TKey */ + private $keyB; + + /** + * @param Associable $associable + * @param TKey $keyA + * @param TKey $keyB + */ + public function __construct(Associable $associable, $keyA, $keyB) + { + $this->associable = $associable; + $this->keyA = $keyA; + $this->keyB = $keyB; + } + + /** @return array */ + public function toAssocArray(): array + { + $items = $this->associable->toAssocArray(); + $valueA = $items[$this->keyA] ?? throw new InvalidArgumentException("Undefined key {$this->keyA}"); + $valueB = $items[$this->keyB] ?? throw new InvalidArgumentException("Undefined key {$this->keyB}"); + $items[$this->keyA] = $valueB; + $items[$this->keyB] = $valueA; + + return $items; + } +} diff --git a/src/InfiniteIterableValue.php b/src/InfiniteIterableValue.php index 51296f2..6ba0753 100644 --- a/src/InfiniteIterableValue.php +++ b/src/InfiniteIterableValue.php @@ -512,6 +512,24 @@ public function last() return $value; } + /** + * @phpstan-return InfiniteIterableValue + */ + public function values(): InfiniteIterableValue + { + return self::fromStack($this->stack->push( + /** + * @phpstan-param iterable $iterable + * @phpstan-return iterable + */ + static function (iterable $iterable): iterable { + foreach ($iterable as $value) { + yield $value; + } + } + )); + } + /** * @phpstan-return InfiniteIterableValue */ @@ -530,6 +548,24 @@ static function (iterable $iterable): iterable { )); } + /** + * @phpstan-return IterableValue + */ + public function flip(): IterableValue + { + return self::fromStack($this->stack->push( + /** + * @phpstan-param iterable $iterable + * @phpstan-return iterable + */ + static function (iterable $iterable): iterable { + foreach ($iterable as $key => $item) { + yield $item => $key; + } + } + )); + } + /** * @phpstan-return Traversable */ diff --git a/src/IterableValue.php b/src/IterableValue.php index a3f4704..1a76a70 100644 --- a/src/IterableValue.php +++ b/src/IterableValue.php @@ -177,4 +177,14 @@ public function findLast(callable $filter); * @phpstan-return IterableValue */ public function keys(): IterableValue; + + /** + * @phpstan-return IterableValue + */ + public function values(): IterableValue; + + /** + * @phpstan-return IterableValue + */ + public function flip(): IterableValue; } diff --git a/src/PlainString.php b/src/PlainString.php index d07d534..e793c9d 100644 --- a/src/PlainString.php +++ b/src/PlainString.php @@ -247,7 +247,7 @@ public function endsWith($pattern): bool /** * @param string|StringValue $pattern - * @return ArrayValue> + * @return ArrayValue> */ public function matchAllPatterns($pattern): ArrayValue { diff --git a/src/PlainStringsArray.php b/src/PlainStringsArray.php index 551f6dc..0a4e430 100644 --- a/src/PlainStringsArray.php +++ b/src/PlainStringsArray.php @@ -499,7 +499,7 @@ public function positionLast($needle): ?int /** * @param string|StringValue $pattern - * @return ArrayValue> + * @return ArrayValue> */ public function matchAllPatterns($pattern): ArrayValue { diff --git a/src/StringValue.php b/src/StringValue.php index b8bee1c..2950817 100644 --- a/src/StringValue.php +++ b/src/StringValue.php @@ -148,7 +148,7 @@ public function positionLast($needle): ?int; /** * @param string|StringValue $pattern - * @return ArrayValue> + * @return ArrayValue> */ public function matchAllPatterns($pattern): ArrayValue;