Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
25 changes: 16 additions & 9 deletions src/RunOpenCode/Component/Dataset/src/Operator/Map.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@

use RunOpenCode\Component\Dataset\AbstractStream;
use RunOpenCode\Component\Dataset\Contract\OperatorInterface;
use RunOpenCode\Component\Dataset\Exception\LogicException;

/**
* Map operator.
*
* Map operator iterates over given collection and yields transformed items.
*
* User must provide a callable to transform each item value. Additionally,
* user may provide a callable to transform each item key. If key transform
* callable is not provided, original keys are preserved.
* User must provide a callable to transform each item value, or callable to
* transform each item key, or both.
*
* Where transforming function is not provided, original values will be
* preserved.

Copilot AI Dec 19, 2025

Copy link

Choose a reason for hiding this comment

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

The documentation states "original values will be preserved" but should be more accurate by stating "original values and/or keys will be preserved" since either transformation can be omitted independently. The current wording could be misinterpreted to suggest that only values are preserved when transformations are omitted.

Suggested change
* Where transforming function is not provided, original values will be
* preserved.
* Where transforming function is not provided, original values and/or keys
* will be preserved.

Copilot uses AI. Check for mistakes.
*
* Example usage:
*
Expand Down Expand Up @@ -47,18 +50,22 @@ final class Map extends AbstractStream implements OperatorInterface
private readonly \Closure $keyTransform;

/**
* @param iterable<TKey, TValue> $collection Collection to iterate over.
* @param ValueTransformCallable $valueTransform User defined callable to transform item values.
* @param KeyTransformCallable|null $keyTransform User defined callable to transform item keys. If null, original keys are preserved.
* @param iterable<TKey, TValue> $collection Collection to iterate over.
* @param ValueTransformCallable|null $valueTransform User defined callable to transform item values. If null, original values are preserved.
* @param KeyTransformCallable|null $keyTransform User defined callable to transform item keys. If null, original keys are preserved.
*/
public function __construct(
private readonly iterable $collection,
callable $valueTransform,
?callable $valueTransform = null,
?callable $keyTransform = null
) {
if (null === $valueTransform && null === $keyTransform) {
throw new LogicException('At least one transforming function must be provided, either for key or for value.');
}

parent::__construct($this->collection);
$this->valueTransform = $valueTransform(...);
$this->keyTransform = ($keyTransform ?? static fn($key, $value): mixed => $key)(...);
$this->valueTransform = ($valueTransform ?? static fn(mixed $value, mixed $key): mixed => $value)(...);
$this->keyTransform = ($keyTransform ?? static fn(mixed $key, mixed $value): mixed => $key)(...);
}

/**
Expand Down
12 changes: 6 additions & 6 deletions src/RunOpenCode/Component/Dataset/src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -236,18 +236,18 @@ function if_empty(iterable $collection, \Exception|callable $action): Stream
* @template TModifiedKey
* @template TModifiedValue
*
* @param iterable<TKey, TValue> $collection Collection to iterate over.
* @param callable(TValue, TKey=): TModifiedValue $valueTransform User defined callable to be called on each item.
* @param callable(TKey, TValue=): TModifiedKey|null $keyTransform User defined callable to be called on each item key. If null, original keys are preserved.
* @param iterable<TKey, TValue> $collection Collection to iterate over.
* @param callable(TValue, TKey=): TModifiedValue|null $valueTransform User defined callable to be called on each item. If null, original values are preserved.
* @param callable(TKey, TValue=): TModifiedKey|null $keyTransform User defined callable to be called on each item key. If null, original keys are preserved.
*
* @return Stream<($keyTransform is null ? TModifiedKey : TKey), TModifiedValue>
* @return Stream<($keyTransform is null ? TKey : TModifiedKey), ($valueTransform is null ? TValue : TModifiedValue)>
*
* @see Operator\Map
*/
function map(iterable $collection, callable $valueTransform, ?callable $keyTransform = null): Stream
function map(iterable $collection, ?callable $valueTransform = null, ?callable $keyTransform = null): Stream
{
/**
* @var StreamInterface<($keyTransform is null ? TKey : TModifiedKey), TModifiedValue> $map
* @var StreamInterface<($keyTransform is null ? TKey : TModifiedKey), ($valueTransform is null ? TValue : TModifiedValue)> $map
*/
$map = new Operator\Map($collection, $valueTransform, $keyTransform);

Expand Down
59 changes: 55 additions & 4 deletions src/RunOpenCode/Component/Dataset/tests/Operator/MapTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use RunOpenCode\Component\Dataset\Exception\LogicException;

use function RunOpenCode\Component\Dataset\map;

Expand All @@ -16,10 +17,10 @@ public function maps(): void
{
$operator = map(
[
'a' => 1,
'b' => 2,
'c' => 3,
],
'a' => 1,
'b' => 2,
'c' => 3,
],
static fn(int $value): int => $value * 2,
static fn(string $key): string => \sprintf('mapped_%s', $key),
);
Expand All @@ -30,4 +31,54 @@ public function maps(): void
'mapped_c' => 6,
], \iterator_to_array($operator));
}

#[Test]
public function map_keys(): void
{
$operator = map(
[
'a' => 1,
'b' => 2,
'c' => 3,
],
keyTransform: static fn(string $key): string => \sprintf('mapped_%s', $key),
);

$this->assertSame([
'mapped_a' => 1,
'mapped_b' => 2,
'mapped_c' => 3,
], \iterator_to_array($operator));
}

#[Test]
public function map_values(): void
{
$operator = map(
[
'a' => 1,
'b' => 2,
'c' => 3,
],
valueTransform: static fn(int $value): int => $value * 2,
);

$this->assertSame([
'a' => 2,
'b' => 4,
'c' => 6,
], \iterator_to_array($operator));
}

#[Test]
public function map_throws_exception_when_transform_function_missing(): void
{
$this->expectException(LogicException::class);

map([
'a' => 1,
'b' => 2,
'c' => 3,
]);
}
}