Skip to content

Commit 5f5ddb6

Browse files
Add entries and from_entries, inspired by JavaScript's Object.entries (and Python's enumerate) and Object.from_entries, respectively (#243)
1 parent 6012d7b commit 5f5ddb6

File tree

8 files changed

+215
-0
lines changed

8 files changed

+215
-0
lines changed

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"src/Functional/DropFirst.php",
4747
"src/Functional/DropLast.php",
4848
"src/Functional/Each.php",
49+
"src/Functional/Entries.php",
4950
"src/Functional/Equal.php",
5051
"src/Functional/ErrorToException.php",
5152
"src/Functional/Every.php",
@@ -57,6 +58,7 @@
5758
"src/Functional/FlatMap.php",
5859
"src/Functional/Flatten.php",
5960
"src/Functional/Flip.php",
61+
"src/Functional/FromEntries.php",
6062
"src/Functional/GreaterThan.php",
6163
"src/Functional/GreaterThanOrEqual.php",
6264
"src/Functional/Group.php",

docs/functional-php.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,20 @@ use Functional\intersperse;
951951
intersperse(['a', 'b', 'c'], '-'); // ['a', '-', 'b', '-', 'c'];
952952
```
953953

954+
## entries() & from_entries()
955+
956+
Inspired by JavaScript’s `Object.entries()` and `Object.from_entries()` and Python’s `enumerate()`, convert a key-value
957+
map into an array of key-value pairs, respectively.
958+
959+
```php
960+
use function Functional\entries;
961+
use function Functional\from_entries;
962+
963+
$map = ['one' => 1, 'two' => 2, 'three' => 3];
964+
$pairs = entries($map); // [['one', 1], ['two', 2], ['three', 3]]
965+
$map2 = from_entries($pairs); // $map === $map2
966+
```
967+
954968
## Other
955969

956970
`array Functional\unique(array|Traversable $collection[, callback $indexer[, bool $strict = true]])`

src/Functional/Entries.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/**
4+
* @package Functional-php
5+
* @author Hugo Sales <hugo@hsal.es>
6+
* @copyright 2021 Lars Strojny
7+
* @license https://opensource.org/licenses/MIT MIT
8+
* @link https://github.com/lstrojny/functional-php
9+
*/
10+
11+
namespace Functional;
12+
13+
use Functional\Exceptions\InvalidArgumentException;
14+
use Traversable;
15+
16+
/**
17+
* Inspired by JavaScript’s `Object.entries`, and Python’s `enumerate`,
18+
* convert a key-value map into an array of key-value pairs
19+
*
20+
* @see Functional\from_entries
21+
* @param Traversable|array $collection
22+
* @param int $start
23+
* @return array
24+
* @no-named-arguments
25+
*/
26+
function entries($collection, int $start = 0)
27+
{
28+
InvalidArgumentException::assertCollection($collection, __FUNCTION__, 1);
29+
30+
$aggregation = [];
31+
foreach ($collection as $key => $value) {
32+
$aggregation[$start++] = [$key, $value];
33+
}
34+
35+
return $aggregation;
36+
}

src/Functional/Exceptions/InvalidArgumentException.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,13 @@ public static function assertNonZeroInteger($value, $callee)
288288
}
289289
}
290290

291+
public static function assertPair($pair, $callee, $position): void
292+
{
293+
if (!(\is_array($pair) || $pair instanceof ArrayAccess) || !isset($pair[0], $pair[1])) {
294+
throw new static(\sprintf('%s() expects paramter %d to be a pair (array with two elements)', $callee, $position));
295+
}
296+
}
297+
291298
private static function getType($value)
292299
{
293300
return \is_object($value) ? \get_class($value) : \gettype($value);

src/Functional/FromEntries.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
/**
4+
* @package Functional-php
5+
* @author Hugo Sales <hugo@hsal.es>
6+
* @copyright 2021 Lars Strojny
7+
* @license https://opensource.org/licenses/MIT MIT
8+
* @link https://github.com/lstrojny/functional-php
9+
*/
10+
11+
namespace Functional;
12+
13+
use Functional\Exceptions\InvalidArgumentException;
14+
use Traversable;
15+
16+
/**
17+
* Inspired by JavaScript’s `Object.fromEntries`,
18+
* convert an array of key-value pairs into a key-value map
19+
*
20+
* @see Functional\entries
21+
* @param Traversable|array $collection
22+
* @return array
23+
* @no-named-arguments
24+
*/
25+
function from_entries($collection)
26+
{
27+
InvalidArgumentException::assertCollection($collection, __FUNCTION__, 1);
28+
29+
$aggregation = [];
30+
foreach ($collection as $entry) {
31+
InvalidArgumentException::assertPair($entry, __FUNCTION__, 1);
32+
[$key, $value] = $entry;
33+
InvalidArgumentException::assertValidArrayKey($key, __FUNCTION__, 1);
34+
$aggregation[$key] = $value;
35+
}
36+
37+
return $aggregation;
38+
}

src/Functional/Functional.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ final class Functional
9898
*/
9999
const each = '\Functional\each';
100100

101+
/**
102+
* @see \Functional\entries
103+
*/
104+
const entries = '\Functional\entries';
105+
101106
/**
102107
* @see \Functional\equal
103108
*/
@@ -153,6 +158,11 @@ final class Functional
153158
*/
154159
const flip = '\Functional\flip';
155160

161+
/**
162+
* @see \Functional\from_entries
163+
*/
164+
const from_entries = '\Functional\from_entries';
165+
156166
/**
157167
* @see \Functional\greater_than
158168
*/
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
/**
4+
* @package Functional-php
5+
* @author Hugo Sales <hugo@hsal.es>
6+
* @copyright 2021 Lars Strojny
7+
* @license https://opensource.org/licenses/MIT MIT
8+
* @link https://github.com/lstrojny/functional-php
9+
*/
10+
11+
namespace Functional\Tests;
12+
13+
use ArrayIterator;
14+
use PHPUnit\Framework\MockObject\MockObject;
15+
16+
use function Functional\entries;
17+
use function Functional\from_entries;
18+
19+
class EntriesFromEntriesTest extends AbstractTestCase
20+
{
21+
protected function setUp(): void
22+
{
23+
parent::setUp();
24+
25+
$this->list = ['value0', 'value1', 'value2', 'value3'];
26+
$this->listIterator = new ArrayIterator($this->list);
27+
$this->hash = ['k0' => 'value0', 'k1' => 'value1', 'k2' => 'value2', 'k3' => 'value3'];
28+
$this->hashIterator = new ArrayIterator($this->hash);
29+
}
30+
31+
public function testArray(): void
32+
{
33+
$res = entries($this->list);
34+
self::assertSame(\array_keys($res), \range(0, \count($this->list) - 1));
35+
self::assertSame(from_entries($res), $this->list);
36+
}
37+
38+
public function testIterator(): void
39+
{
40+
$res = entries($this->listIterator);
41+
self::assertSame(\array_keys($res), \range(0, \count($this->listIterator) - 1));
42+
self::assertSame(from_entries($res), $this->listIterator->getArrayCopy());
43+
}
44+
45+
public function testHash(): void
46+
{
47+
$res = entries($this->hash);
48+
self::assertSame(\array_keys($res), \range(0, \count($this->hash) - 1));
49+
self::assertSame(from_entries($res), $this->hash);
50+
}
51+
52+
public function testHashIterator(): void
53+
{
54+
$res = entries($this->hashIterator);
55+
self::assertSame(\array_keys($res), \range(0, \count($this->hashIterator) - 1));
56+
self::assertSame(from_entries($res), $this->hashIterator->getArrayCopy());
57+
}
58+
59+
public function testHashWithStart(): void
60+
{
61+
$res = entries($this->hash, 42);
62+
self::assertSame(\array_keys($res), \range(42, 42 + \count($this->hash) - 1));
63+
self::assertSame(from_entries($res), $this->hash);
64+
}
65+
66+
public function testPassNoCollection(): void
67+
{
68+
$this->expectArgumentError('Functional\entries() expects parameter 1 to be array or instance of Traversable');
69+
entries('invalidCollection');
70+
}
71+
}

tests/Functional/Exceptions/InvalidArgumentExceptionTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,41 @@ public function testAssertBooleanAccessWithObject(): void
158158
$this->expectExceptionMessage('func() expects parameter 4 to be boolean, stdClass given');
159159
InvalidArgumentException::assertBoolean(new \stdClass(), "func", 4);
160160
}
161+
162+
public function testAssertPairWithPair(): void
163+
{
164+
$this->expectNotToPerformAssertions();
165+
InvalidArgumentException::assertPair([1, 2], "func", 1);
166+
InvalidArgumentException::assertPair(['1', 2], "func", 1);
167+
InvalidArgumentException::assertPair([1, '2'], "func", 1);
168+
InvalidArgumentException::assertPair([new \stdClass(), '2'], "func", 1);
169+
}
170+
171+
public function testAssertPairWithEmptyArray(): void
172+
{
173+
$this->expectException(InvalidArgumentException::class);
174+
$this->expectExceptionMessage('func() expects paramter 1 to be a pair (array with two elements)');
175+
InvalidArgumentException::assertPair([], "func", 1);
176+
}
177+
178+
public function testAssertPairWithInvalidArray(): void
179+
{
180+
$this->expectException(InvalidArgumentException::class);
181+
$this->expectExceptionMessage('func() expects paramter 1 to be a pair (array with two elements)');
182+
InvalidArgumentException::assertPair(['one'], "func", 1);
183+
}
184+
185+
public function testAssertPairWithTwoCharacterString(): void
186+
{
187+
$this->expectException(InvalidArgumentException::class);
188+
$this->expectExceptionMessage('func() expects paramter 1 to be a pair (array with two elements)');
189+
InvalidArgumentException::assertPair('ab', "func", 1);
190+
}
191+
192+
public function testAssertPairWithThreeCharacterString(): void
193+
{
194+
$this->expectException(InvalidArgumentException::class);
195+
$this->expectExceptionMessage('func() expects paramter 1 to be a pair (array with two elements)');
196+
InvalidArgumentException::assertPair('abc', "func", 1);
197+
}
161198
}

0 commit comments

Comments
 (0)