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 all 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 AssocValue<int|string, TKey>
Copy link
Member

@dazet dazet Oct 1, 2023

Choose a reason for hiding this comment

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

It can't be AssocValue<TValue, TKey>?
I know it doesn't make sense if TValue isn't int|string, but AssocValue<int, string> flipped should be rather AssocValue<string, int> than AssocValue<int|string, int>

*/
public function flip(): AssocValue
{
return new self(new Flip($this->items));
}

/**
* @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 AssocValue<int|string, TKey>
*/
public function flip(): AssocValue;

/**
* @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
* @template TValue of int|string
* @implements Associable<int|string,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<int|string,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;
}
}
36 changes: 36 additions & 0 deletions src/InfiniteIterableValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,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 +548,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;
}
2 changes: 1 addition & 1 deletion src/PlainString.php
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ public function endsWith($pattern): bool

/**
* @param string|StringValue $pattern
* @return ArrayValue<array<int|string, string>>
* @return ArrayValue<array<string>>
*/
public function matchAllPatterns($pattern): ArrayValue
{
Expand Down
2 changes: 1 addition & 1 deletion src/PlainStringsArray.php
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ public function positionLast($needle): ?int

/**
* @param string|StringValue $pattern
* @return ArrayValue<array<int|string, string>>
* @return ArrayValue<array<string>>
*/
public function matchAllPatterns($pattern): ArrayValue
{
Expand Down
2 changes: 1 addition & 1 deletion src/StringValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ public function positionLast($needle): ?int;

/**
* @param string|StringValue $pattern
* @return ArrayValue<array<int|string, string>>
* @return ArrayValue<array<string>>
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 be array<int|string, string> when there's named subpattern
(?P<name>lorem) => [0 => 'lorem', 'name' => 'lorem', 1 => 'lorem']

Copy link
Member

Choose a reason for hiding this comment

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

I think I has similar problem on my branch and resolved it like this 😏

*/
public function matchAllPatterns($pattern): ArrayValue;

Expand Down
Loading