Skip to content

Commit

Permalink
Add entries and from_entries, inspired by JavaScript's `Object.en…
Browse files Browse the repository at this point in the history
…tries` (and Python's `enumerate`) and `Object.from_entries`, respectively (lstrojny#243)
  • Loading branch information
someonewithpc authored Nov 29, 2021
1 parent 6012d7b commit 5f5ddb6
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 0 deletions.
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"src/Functional/DropFirst.php",
"src/Functional/DropLast.php",
"src/Functional/Each.php",
"src/Functional/Entries.php",
"src/Functional/Equal.php",
"src/Functional/ErrorToException.php",
"src/Functional/Every.php",
Expand All @@ -57,6 +58,7 @@
"src/Functional/FlatMap.php",
"src/Functional/Flatten.php",
"src/Functional/Flip.php",
"src/Functional/FromEntries.php",
"src/Functional/GreaterThan.php",
"src/Functional/GreaterThanOrEqual.php",
"src/Functional/Group.php",
Expand Down
14 changes: 14 additions & 0 deletions docs/functional-php.md
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,20 @@ use Functional\intersperse;
intersperse(['a', 'b', 'c'], '-'); // ['a', '-', 'b', '-', 'c'];
```

## entries() & from_entries()

Inspired by JavaScript’s `Object.entries()` and `Object.from_entries()` and Python’s `enumerate()`, convert a key-value
map into an array of key-value pairs, respectively.

```php
use function Functional\entries;
use function Functional\from_entries;

$map = ['one' => 1, 'two' => 2, 'three' => 3];
$pairs = entries($map); // [['one', 1], ['two', 2], ['three', 3]]
$map2 = from_entries($pairs); // $map === $map2
```

## Other

`array Functional\unique(array|Traversable $collection[, callback $indexer[, bool $strict = true]])`
Expand Down
36 changes: 36 additions & 0 deletions src/Functional/Entries.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

/**
* @package Functional-php
* @author Hugo Sales <[email protected]>
* @copyright 2021 Lars Strojny
* @license https://opensource.org/licenses/MIT MIT
* @link https://github.com/lstrojny/functional-php
*/

namespace Functional;

use Functional\Exceptions\InvalidArgumentException;
use Traversable;

/**
* Inspired by JavaScript’s `Object.entries`, and Python’s `enumerate`,
* convert a key-value map into an array of key-value pairs
*
* @see Functional\from_entries
* @param Traversable|array $collection
* @param int $start
* @return array
* @no-named-arguments
*/
function entries($collection, int $start = 0)
{
InvalidArgumentException::assertCollection($collection, __FUNCTION__, 1);

$aggregation = [];
foreach ($collection as $key => $value) {
$aggregation[$start++] = [$key, $value];
}

return $aggregation;
}
7 changes: 7 additions & 0 deletions src/Functional/Exceptions/InvalidArgumentException.php
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,13 @@ public static function assertNonZeroInteger($value, $callee)
}
}

public static function assertPair($pair, $callee, $position): void
{
if (!(\is_array($pair) || $pair instanceof ArrayAccess) || !isset($pair[0], $pair[1])) {
throw new static(\sprintf('%s() expects paramter %d to be a pair (array with two elements)', $callee, $position));
}
}

private static function getType($value)
{
return \is_object($value) ? \get_class($value) : \gettype($value);
Expand Down
38 changes: 38 additions & 0 deletions src/Functional/FromEntries.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

/**
* @package Functional-php
* @author Hugo Sales <[email protected]>
* @copyright 2021 Lars Strojny
* @license https://opensource.org/licenses/MIT MIT
* @link https://github.com/lstrojny/functional-php
*/

namespace Functional;

use Functional\Exceptions\InvalidArgumentException;
use Traversable;

/**
* Inspired by JavaScript’s `Object.fromEntries`,
* convert an array of key-value pairs into a key-value map
*
* @see Functional\entries
* @param Traversable|array $collection
* @return array
* @no-named-arguments
*/
function from_entries($collection)
{
InvalidArgumentException::assertCollection($collection, __FUNCTION__, 1);

$aggregation = [];
foreach ($collection as $entry) {
InvalidArgumentException::assertPair($entry, __FUNCTION__, 1);
[$key, $value] = $entry;
InvalidArgumentException::assertValidArrayKey($key, __FUNCTION__, 1);
$aggregation[$key] = $value;
}

return $aggregation;
}
10 changes: 10 additions & 0 deletions src/Functional/Functional.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ final class Functional
*/
const each = '\Functional\each';

/**
* @see \Functional\entries
*/
const entries = '\Functional\entries';

/**
* @see \Functional\equal
*/
Expand Down Expand Up @@ -153,6 +158,11 @@ final class Functional
*/
const flip = '\Functional\flip';

/**
* @see \Functional\from_entries
*/
const from_entries = '\Functional\from_entries';

/**
* @see \Functional\greater_than
*/
Expand Down
71 changes: 71 additions & 0 deletions tests/Functional/EntriesFromEntriesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

/**
* @package Functional-php
* @author Hugo Sales <[email protected]>
* @copyright 2021 Lars Strojny
* @license https://opensource.org/licenses/MIT MIT
* @link https://github.com/lstrojny/functional-php
*/

namespace Functional\Tests;

use ArrayIterator;
use PHPUnit\Framework\MockObject\MockObject;

use function Functional\entries;
use function Functional\from_entries;

class EntriesFromEntriesTest extends AbstractTestCase
{
protected function setUp(): void
{
parent::setUp();

$this->list = ['value0', 'value1', 'value2', 'value3'];
$this->listIterator = new ArrayIterator($this->list);
$this->hash = ['k0' => 'value0', 'k1' => 'value1', 'k2' => 'value2', 'k3' => 'value3'];
$this->hashIterator = new ArrayIterator($this->hash);
}

public function testArray(): void
{
$res = entries($this->list);
self::assertSame(\array_keys($res), \range(0, \count($this->list) - 1));
self::assertSame(from_entries($res), $this->list);
}

public function testIterator(): void
{
$res = entries($this->listIterator);
self::assertSame(\array_keys($res), \range(0, \count($this->listIterator) - 1));
self::assertSame(from_entries($res), $this->listIterator->getArrayCopy());
}

public function testHash(): void
{
$res = entries($this->hash);
self::assertSame(\array_keys($res), \range(0, \count($this->hash) - 1));
self::assertSame(from_entries($res), $this->hash);
}

public function testHashIterator(): void
{
$res = entries($this->hashIterator);
self::assertSame(\array_keys($res), \range(0, \count($this->hashIterator) - 1));
self::assertSame(from_entries($res), $this->hashIterator->getArrayCopy());
}

public function testHashWithStart(): void
{
$res = entries($this->hash, 42);
self::assertSame(\array_keys($res), \range(42, 42 + \count($this->hash) - 1));
self::assertSame(from_entries($res), $this->hash);
}

public function testPassNoCollection(): void
{
$this->expectArgumentError('Functional\entries() expects parameter 1 to be array or instance of Traversable');
entries('invalidCollection');
}
}
37 changes: 37 additions & 0 deletions tests/Functional/Exceptions/InvalidArgumentExceptionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,41 @@ public function testAssertBooleanAccessWithObject(): void
$this->expectExceptionMessage('func() expects parameter 4 to be boolean, stdClass given');
InvalidArgumentException::assertBoolean(new \stdClass(), "func", 4);
}

public function testAssertPairWithPair(): void
{
$this->expectNotToPerformAssertions();
InvalidArgumentException::assertPair([1, 2], "func", 1);
InvalidArgumentException::assertPair(['1', 2], "func", 1);
InvalidArgumentException::assertPair([1, '2'], "func", 1);
InvalidArgumentException::assertPair([new \stdClass(), '2'], "func", 1);
}

public function testAssertPairWithEmptyArray(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('func() expects paramter 1 to be a pair (array with two elements)');
InvalidArgumentException::assertPair([], "func", 1);
}

public function testAssertPairWithInvalidArray(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('func() expects paramter 1 to be a pair (array with two elements)');
InvalidArgumentException::assertPair(['one'], "func", 1);
}

public function testAssertPairWithTwoCharacterString(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('func() expects paramter 1 to be a pair (array with two elements)');
InvalidArgumentException::assertPair('ab', "func", 1);
}

public function testAssertPairWithThreeCharacterString(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('func() expects paramter 1 to be a pair (array with two elements)');
InvalidArgumentException::assertPair('abc', "func", 1);
}
}

0 comments on commit 5f5ddb6

Please sign in to comment.