Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AssocValue flip and swap functions #60

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions spec/AssocArraySpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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']);
}
}
24 changes: 24 additions & 0 deletions spec/InfiniteIterableValueSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
29 changes: 28 additions & 1 deletion src/AssocArray.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -216,6 +217,32 @@ public function withoutElement($value): AssocArray
return $this->filter(Filters::notEqual($value));
}

/**
* @phpstan-return IterableValue<TKey, TValue>
*/
public function toIterableValue(): IterableValue
{
return new InfiniteIterableValue($this->toAssocArray());
}

/**
* @phpstan-return IterableValue<TValue, TKey>
*/
public function flip(): IterableValue
{
return $this->toIterableValue()->flip();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not through GW\Value\Associable\Flip? I don't think that AssocArray should return IterableValue.

Copy link
Member Author

@bronek89 bronek89 Jun 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is hard to flip AssocValue, because TKey must be int|string and TValue is mixed - therefore it produces phpstan type errors. I think it can be fully fixed only after rewrite AssocValue to some kind of map with mixed key and mixed value.

For now I've changed it to:

    /**
     * @phpstan-return AssocValue<int|string, TKey>
     */
    public function flip(): AssocValue;

it will be AssocValue - not AssocValue<TValue,TKey> but AssocValue<int|string, TKey>

in other words - you loose type for key - from Wrap::assoc([0 => 'xxx']) as AssocValue<int,string> flip makes AssocValue<int|string,int>, not AssocValue<string,int>

}

/**
* @param TKey $keyA
* @param TKey $keyB
* @phpstan-return AssocArray<TKey, TValue>
*/
public function swap($keyA, $keyB): AssocArray
{
return new self(new Swap($this->items, $keyA, $keyB));
}

/**
* @template TNewValue
* @param callable(TNewValue,TValue,TKey):TNewValue $transformer
Expand Down
13 changes: 13 additions & 0 deletions src/AssocValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace GW\Value;

use BadMethodCallException;
use GW\Value\Associable\Flip;
use IteratorAggregate;
use ArrayAccess;

Expand Down Expand Up @@ -143,6 +144,18 @@ public function only(...$keys): AssocValue;
*/
public function withoutElement($value): AssocValue;

/**
* @phpstan-return IterableValue<TValue, TKey>
*/
public function flip(): IterableValue;

/**
* @param TKey $keyA
* @param TKey $keyB
* @phpstan-return AssocArray<TKey, TValue>
*/
public function swap($keyA, $keyB): AssocArray;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can have type-hint int|string for arguments, and return should be AssocValue


/**
* @deprecated use join() or replace() instead
* @phpstan-param AssocValue<TKey, TValue> $other
Expand Down
31 changes: 31 additions & 0 deletions src/Associable/Flip.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php declare(strict_types=1);

namespace GW\Value\Associable;

use GW\Value\Associable;
use function array_flip;

/**
* @template TKey of int|string
* @template TValue of int|string
* @implements Associable<TKey,TValue>
*/
final class Flip implements Associable
{
/** @var Associable<TValue,TKey> */
private Associable $associable;

/**
* @param Associable<TValue,TKey> $associable
*/
public function __construct(Associable $associable)
{
$this->associable = $associable;
}

/** @return array<TKey,TValue> */
public function toAssocArray(): array
{
return array_flip($this->associable->toAssocArray());
}
}
45 changes: 45 additions & 0 deletions src/Associable/Swap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php declare(strict_types=1);

namespace GW\Value\Associable;

use GW\Value\Associable;
use InvalidArgumentException;

/**
* @template TKey of int|string
* @template TValue
* @implements Associable<TKey,TValue>
*/
final class Swap implements Associable
{
/** @var Associable<TKey,TValue> */
private Associable $associable;
/** @var TKey */
private $keyA;
/** @var TKey */
private $keyB;

/**
* @param Associable<TKey,TValue> $associable
* @param TKey $keyA
* @param TKey $keyB
*/
public function __construct(Associable $associable, $keyA, $keyB)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

int|string here too :)

{
$this->associable = $associable;
$this->keyA = $keyA;
$this->keyB = $keyB;
}

/** @return array<TKey,TValue> */
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;
}
}
37 changes: 37 additions & 0 deletions src/InfiniteIterableValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Traversable;
use function count;
use function var_dump;
bronek89 marked this conversation as resolved.
Show resolved Hide resolved
use const PHP_INT_MAX;

/**
Expand Down Expand Up @@ -512,6 +513,24 @@ public function last()
return $value;
}

/**
* @phpstan-return InfiniteIterableValue<int, TValue>
*/
public function values(): InfiniteIterableValue
{
return self::fromStack($this->stack->push(
/**
* @phpstan-param iterable<TKey, TValue> $iterable
* @phpstan-return iterable<int, TValue>
*/
static function (iterable $iterable): iterable {
foreach ($iterable as $value) {
yield $value;
}
}
));
}

/**
* @phpstan-return InfiniteIterableValue<int, TKey>
*/
Expand All @@ -530,6 +549,24 @@ static function (iterable $iterable): iterable {
));
}

/**
* @phpstan-return IterableValue<TValue, TKey>
*/
public function flip(): IterableValue
{
return self::fromStack($this->stack->push(
/**
* @phpstan-param iterable<TKey, TValue> $iterable
* @phpstan-return iterable<TValue, TKey>
*/
static function (iterable $iterable): iterable {
foreach ($iterable as $key => $item) {
yield $item => $key;
}
}
));
}

/**
* @phpstan-return Traversable<TKey, TValue>
*/
Expand Down
10 changes: 10 additions & 0 deletions src/IterableValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,14 @@ public function findLast(callable $filter);
* @phpstan-return IterableValue<int, TKey>
*/
public function keys(): IterableValue;

/**
* @phpstan-return IterableValue<int, TValue>
*/
public function values(): IterableValue;

/**
* @phpstan-return IterableValue<TValue, TKey>
*/
public function flip(): IterableValue;
}