Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F24893965
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
40 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/omnitools/src/Collections/WithCollection.php b/omnitools/src/Collections/WithCollection.php
index 45baae3..7920ff9 100644
--- a/omnitools/src/Collections/WithCollection.php
+++ b/omnitools/src/Collections/WithCollection.php
@@ -1,100 +1,99 @@
<?php
namespace Keruald\OmniTools\Collections;
+use Keruald\OmniTools\DataTypes\Option\None;
+use Keruald\OmniTools\DataTypes\Option\Option;
+use Keruald\OmniTools\DataTypes\Option\Some;
use Keruald\OmniTools\Reflection\CallableElement;
use InvalidArgumentException;
-use OutOfRangeException;
trait WithCollection {
abstract function count () : int;
abstract function toArray() : array;
- public function first () : mixed {
+ public function first () : Option {
foreach ($this->toArray() as $item) {
- return $item;
+ return new Some($item);
}
- throw new OutOfRangeException("The collection is empty.");
+ return new None;
}
public function firstOr (mixed $default) : mixed {
- return match ($this->count()) {
- 0 => $default,
- default => $this->first(),
- };
+ return $this->first()->getValueOr($default);
}
///
/// HOF
///
/**
* Determines if at least an element of the collection satisfies a condition.
*
* The execution of callbacks stop after a callable returned true.
*
* @param callable $callable A method returning a boolean with key and value
* or only value as arguments.
*
* @return bool True if callback is true for at least one of the elements
* @throws \ReflectionException if the callable does not exist.
*/
public function any (callable $callable) : bool {
$argc = (new CallableElement($callable))->countArguments();
$items = $this->toArray();
foreach ($items as $key => $value) {
$result = match($argc) {
0 => throw new InvalidArgumentException(self::CB_ZERO_ARG),
1 => $callable($value),
default => $callable($key, $value),
};
// PHP standard or extensions functions can sometimes throw
// mixed result, for example true or a constant. Any other
// result than the boolean true is interpreted as falsy.
if ($result === true) {
return true;
}
}
return false;
}
/**
* Determines if all elements of the collection satisfies a condition.
*
* The execution of callbacks stop after a callable returned false.
*
* @param callable $callable A method returning a boolean with key and value
* or only value as arguments.
*
* @return bool True if callback is true for all the elements
* @throws \ReflectionException if the callable does not exist.
*/
public function all (callable $callable) : bool {
$argc = (new CallableElement($callable))->countArguments();
$items = $this->toArray();
foreach ($items as $key => $value) {
$result = match($argc) {
0 => throw new InvalidArgumentException(self::CB_ZERO_ARG),
1 => $callable($value),
default => $callable($key, $value),
};
// PHP standard or extensions functions can sometimes throw
// mixed result, for example true or a constant. Any other
// result than the boolean true is interpreted as falsy.
if ($result !== true) {
return false;
}
}
return true;
}
}
diff --git a/omnitools/tests/Collections/BitsVectorTest.php b/omnitools/tests/Collections/BitsVectorTest.php
index 1bcde23..62c6622 100644
--- a/omnitools/tests/Collections/BitsVectorTest.php
+++ b/omnitools/tests/Collections/BitsVectorTest.php
@@ -1,262 +1,264 @@
<?php
namespace Keruald\OmniTools\Tests\Collections;
use InvalidArgumentException;
use OutOfRangeException;
use Keruald\OmniTools\Collections\BitsVector;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
class BitsVectorTest extends TestCase {
///
/// Constructors
///
public function testConstructorWithInvalidIterable () : void {
$this->expectException(InvalidArgumentException::class);
new BitsVector([1, 2, 3, 4]);
}
public function testNew () : void {
$bits = BitsVector::new(8);
$this->assertEquals(8, $bits->count());
}
public function testNewWhenCapacityIsEmpty () : void {
$bits = BitsVector::new(0);
$this->assertEquals(0, $bits->count());
}
public function testNewWhenCapacityIsNegative () : void {
$this->expectException(InvalidArgumentException::class);
BitsVector::new(-8);
}
public function testFromInteger () : void {
$bits = BitsVector::fromInteger(4);
$this->assertEquals([1, 0, 0], $bits->toArray());
}
public function testFromIntegerWhenValueIsNegative () : void {
$bits = BitsVector::fromInteger(-1);
$expected = array_fill(0, 64, 1);
$this->assertEquals($expected, $bits->toArray());
}
public function testFromBinString () : void {
$bits = BitsVector::fromBinaryString("1001");
$this->assertEquals([1, 0, 0, 1], $bits->toArray());
}
public function testFromHexString () : void {
$bits = BitsVector::fromHexString("337362ea");
$this->assertEquals("337362ea", $bits->toHexString());
}
public function testFromDecoratedHexString () : void {
$bits = BitsVector::fromDecoratedHexString("cc4eca23-b825-11ec-ab20-a81e84f35d9c");
$this->assertEquals("cc4eca23b82511ecab20a81e84f35d9c", $bits->toHexString());
}
public function testFromString () : void {
// Exemple based on pack() documentation
$binaryData = pack("nvc*", 0x1234, 0x5678, 65, 66);
$bits = BitsVector::fromString($binaryData);
$this->assertEquals(
[0x12, 0x34, 0x78, 0x56, 0x41, 0x42],
$bits->toBytesArray(),
);
}
public function testBytesArray () : void {
$bits = BitsVector::new(16)
->copyInteger(1, 0, 4)
->copyInteger(2, 4, 4)
->copyInteger(3, 8, 4)
->copyInteger(4, 12, 4);
$this->assertEquals(
[0x12, 0x34],
$bits->toBytesArray(),
);
}
public static function provideLengths () : iterable {
yield [1];
yield [2];
yield [8];
yield [500];
yield [5000];
yield [0];
}
#[DataProvider('provideLengths')]
public function testRandom($length) : void {
$bits = BitsVector::random($length);
$this->assertEquals($length, $bits->count());
}
public function testRandomWithNegativeLength() : void {
$this->expectException(InvalidArgumentException::class);
BitsVector::random(-1);
}
public function testBytesArrayWithBadLength () : void {
$this->expectException(InvalidArgumentException::class);
$bits = new BitsVector([1, 1, 1]); // 3 bits isn't a byte
$bits->toBytesArray();
}
public function testToBinaryString () : void {
$bits = new BitsVector([1, 0, 0, 1]);
$this->assertEquals("1001", $bits->toBinaryString());
}
public function testToInteger () : void {
$bits = new BitsVector([1, 0, 0, 1]);
$this->assertEquals(9, $bits->toInteger());
}
public function testToIntegerWhenThereIsTooMuchBits () : void {
$this->expectException(InvalidArgumentException::class);
BitsVector::new(66)->toInteger();
}
public function testPad () : void {
$bits = new BitsVector([1, 0, 0, 1]);
$bits->pad(8);
$this->assertEquals([0, 0, 0, 0, 1, 0, 0, 1], $bits->toArray());
}
public function testPadWithLargeEnoughCount () : void {
$bits = new BitsVector([1, 0, 0, 1]);
$bits->pad(4);
$this->assertEquals([1, 0, 0, 1], $bits->toArray());
}
public function testTruncate () : void {
$bits = new BitsVector([1, 0, 0, 1, 0, 0, 0, 0]);
$bits->truncate(4);
$this->assertEquals([1, 0, 0, 1], $bits->toArray());
}
public function testTruncateWithSmallEnoughCount () : void {
$bits = new BitsVector([1, 0, 0, 1]);
$bits->truncate(4);
$this->assertEquals([1, 0, 0, 1], $bits->toArray());
}
public static function provideShapeArrays () : iterable {
yield [[1, 0, 0, 1, 0, 0, 0, 0], 4, [1, 0, 0, 1]];
yield [[1, 0, 0, 1], 4, [1, 0, 0, 1]];
yield [[1, 0, 0, 1], 3, [1, 0, 0]];
yield [[1, 0, 0, 1], 0, []];
yield [[], 0, []];
yield [[], 4, [0, 0, 0, 0]];
}
#[DataProvider('provideShapeArrays')]
public function testShapeCapacity (array $initial, int $length, array $final) : void {
$bits = new BitsVector($initial);
$bits->shapeCapacity($length);
$this->assertEquals($final, $bits->toArray());
}
public function testCopyInteger() : void {
$bits = BitsVector::new(8);
$bits->copyInteger(5, 2, 3);
$this->assertEquals([0, 0, 1, 0, 1, 0, 0, 0], $bits->toArray());
}
///
/// BaseVector overrides
///
public function testSet () : void {
$bits = BitsVector::new(4);
$bits->set(2, 1);
$this->assertEquals([0, 0, 1, 0], $bits->toArray());
}
public function testContains () : void {
$bits = BitsVector::new(4);
$this->assertFalse($bits->contains(1));
}
public function testPush () : void {
$bits = BitsVector::new(4);
$bits->push(1);
$this->assertEquals([0, 0, 0, 0, 1], $bits->toArray());
}
public function testAppend () : void {
$bits = BitsVector::new(4);
$bits->append([1, 1]);
$this->assertEquals([0, 0, 0, 0, 1, 1], $bits->toArray());
}
public function testUpdate () : void {
$bits = BitsVector::new(4);
$bits->update([1, 0, 1, 0]); // 0 already exists, we'll add ONE 1
$this->assertEquals([0, 0, 0, 0, 1], $bits->toArray());
}
public function testOffsetSet () : void {
$bits = BitsVector::new(4);
$bits[2] = 1;
$this->assertEquals([0, 0, 1, 0], $bits->toArray());
}
///
/// WithCollection trait
///
public function testFirst () : void {
$bits = BitsVector::new(4);
$bits[2] = 1;
- $this->assertEquals(0, $bits->first());
+ $firstBit = $bits->first();
+ $this->assertTrue($firstBit->isSome());
+ $this->assertEquals(0, $firstBit->getValue());
}
public function testFirstWhenEmpty () : void {
$bits = BitsVector::new(0);
- $this->expectException(OutOfRangeException::class);
- $bits->first();
+ $firstBit = $bits->first();
+ $this->assertTrue($firstBit->isNone());
}
public function testFirstOr () : void {
$bits = BitsVector::new(4);
$bits[2] = 1;
$this->assertEquals(0, $bits->firstOr(2));
}
public function testFirstOrWhenEmpty () : void {
$bits = BitsVector::new(0);
$this->assertEquals(2, $bits->firstOr(2));
}
}
diff --git a/omnitools/tests/Collections/HashMapTest.php b/omnitools/tests/Collections/HashMapTest.php
index 4d39415..efdceba 100644
--- a/omnitools/tests/Collections/HashMapTest.php
+++ b/omnitools/tests/Collections/HashMapTest.php
@@ -1,556 +1,559 @@
<?php
namespace Keruald\OmniTools\Tests\Collections;
use Keruald\OmniTools\Collections\BitsVector;
use Keruald\OmniTools\Collections\HashMap;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use InvalidArgumentException;
use IteratorAggregate;
use OutOfRangeException;
use Traversable;
class HashMapTest extends TestCase {
///
/// Test set up
///
private HashMap $map;
const MAP_CONTENT = [
// Some sci-fi civilizations and author
"The Culture" => "Iain Banks",
"Radchaai Empire" => "Ann Leckie",
"Barrayar" => "Lois McMaster Bujold",
"Hainish" => "Ursula K. Le Guin",
];
protected function setUp () : void {
$this->map = new HashMap(self::MAP_CONTENT);
}
///
/// Constructors
///
public function testConstructorWithArray () {
$this->assertSame(self::MAP_CONTENT, $this->map->toArray());
}
public function testConstructorWithTraversable () {
$expected = [
"color" => "blue",
"material" => "glass",
"shape" => "sphere",
];
$iterable = new class implements IteratorAggregate {
function getIterator () : Traversable {
yield "color" => "blue";
yield "material" => "glass";
yield "shape" => "sphere";
}
};
$map = new HashMap($iterable);
$this->assertSame($expected, $map->toArray());
}
public static function provideDeepArrays() : iterable {
yield [self::MAP_CONTENT, self::MAP_CONTENT];
yield [[], []];
yield [null, []];
$caps = new \stdClass;
$caps->color = "red";
$caps->logo = "HCKR";
yield [$caps, [
"color" => "red",
"logo" => "HCKR",
]];
$sizedCaps = clone $caps;
$sizedCaps->size = new \stdClass;
$sizedCaps->size->h = 8;
$sizedCaps->size->r = 20;
yield [$sizedCaps, [
"color" => "red",
"logo" => "HCKR",
"size" => ["h" => 8, "r" => 20],
]];
}
#[DataProvider('provideDeepArrays')]
public function testFrom($from, array $expected) : void {
$map = HashMap::from($from);
$this->assertEquals($expected, $map->toArray());
}
///
/// Getters and setters
///
public function testGet () {
$this->assertSame("Iain Banks", $this->map->get("The Culture"));
}
public function testGetWhenKeyIsNotFound () {
$this->expectException(InvalidArgumentException::class);
$this->map->get("Quuxians");
}
public function testGetOr () {
$actual = $this->map
->getOr("The Culture", "Another author");
$this->assertSame("Iain Banks", $actual);
}
public function testGetOrWhenKeyIsNotFound () {
$actual = $this->map
->getOr("Quuxians", "Another author");
$this->assertSame("Another author", $actual);
}
public function testSetWithNewKey () {
$this->map->set("Thélème", "François Rabelais");
$this->assertSame("François Rabelais",
$this->map->get("Thélème"));
}
public function testSetWithExistingKey () {
$this->map->set("The Culture", "Iain M. Banks");
$this->assertSame("Iain M. Banks",
$this->map->get("The Culture"));
}
public function testUnset() {
$this->map->unset("The Culture");
$this->assertFalse($this->map->contains("Iain Banks"));
}
public function testUnsetNotExistingKey() {
$this->map->unset("Not existing");
$this->assertEquals(4, $this->map->count());
}
public function testHas () {
$this->assertTrue($this->map->has("The Culture"));
$this->assertFalse($this->map->has("Not existing key"));
}
public function testContains () {
$this->assertTrue($this->map->contains("Iain Banks"));
$this->assertFalse($this->map->contains("Not existing value"));
}
///
/// Collection method
///
public function testCount () {
$this->assertSame(4, $this->map->count());
}
public function testClear () {
$this->map->clear();
$this->assertSame(0, $this->map->count());
}
public function testIsEmpty () : void {
$this->map->clear();
$this->assertTrue($this->map->isEmpty());
}
public function testMerge () {
$iterable = [
"The Culture" => "Iain M. Banks", // existing key
"Thélème" => "François Rabelais", // new key
];
$expected = [
// The original map
"The Culture" => "Iain Banks", // Old value should be kept
"Radchaai Empire" => "Ann Leckie",
"Barrayar" => "Lois McMaster Bujold",
"Hainish" => "Ursula K. Le Guin",
// The entries with a new key
"Thélème" => "François Rabelais",
];
$this->map->merge($iterable);
$this->assertSame($expected, $this->map->toArray());
}
public function testUpdate () {
$iterable = [
"The Culture" => "Iain M. Banks", // existing key
"Thélème" => "François Rabelais", // new key
];
$expected = [
// The original map
"The Culture" => "Iain M. Banks", // Old value should be updated
"Radchaai Empire" => "Ann Leckie",
"Barrayar" => "Lois McMaster Bujold",
"Hainish" => "Ursula K. Le Guin",
// The entries with a new key
"Thélème" => "François Rabelais",
];
$this->map->update($iterable);
$this->assertSame($expected, $this->map->toArray());
}
public function testToArray () {
$this->assertEquals(self::MAP_CONTENT, $this->map->toArray());
}
///
/// High order functions
///
public function testMap () {
$callback = function ($value) {
return "author='" . $value . "'";
};
$expected = [
"The Culture" => "author='Iain Banks'",
"Radchaai Empire" => "author='Ann Leckie'",
"Barrayar" => "author='Lois McMaster Bujold'",
"Hainish" => "author='Ursula K. Le Guin'",
];
$actual = $this->map->map($callback)->toArray();
$this->assertEquals($expected, $actual);
}
public function testMapKeys () {
$callback = function ($key) {
return "civ::" . $key;
};
$expected = [
// Some sci-fi civilizations and author
"civ::The Culture" => "Iain Banks",
"civ::Radchaai Empire" => "Ann Leckie",
"civ::Barrayar" => "Lois McMaster Bujold",
"civ::Hainish" => "Ursula K. Le Guin",
];
$actual = $this->map->mapKeys($callback)->toArray();
$this->assertEquals($expected, $actual);
}
public function testMapKeysAndValues () : void {
$callback = function ($civilization, $author) {
return [$author[0], "$author, $civilization"];
};
$expected = [
// Some sci-fi civilizations and author
"I" => "Iain Banks, The Culture",
"A" => "Ann Leckie, Radchaai Empire",
"L" => "Lois McMaster Bujold, Barrayar",
"U"=> "Ursula K. Le Guin, Hainish",
];
$actual = $this->map->mapValuesAndKeys($callback)->toArray();
$this->assertEquals($expected, $actual);
}
public function testMapKeysAndValuesForVectors () : void {
$callback = function ($author) {
return [$author[0], "author:" . $author];
};
$expected = [
// Some sci-fi civilizations and author
"I" => "author:Iain Banks",
"A" => "author:Ann Leckie",
"L" => "author:Lois McMaster Bujold",
"U" => "author:Ursula K. Le Guin",
];
$actual = $this->map->mapValuesAndKeys($callback)->toArray();
$this->assertEquals($expected, $actual);
}
public function testMapKeysAndValuesWithCallbackWithoutArgument() : void {
$this->expectException(InvalidArgumentException::class);
$callback = function () {};
$this->map->mapValuesAndKeys($callback);
}
public function testFlatMap(): void {
$callback = function ($key, $value) {
$items = explode(" ", $value);
foreach ($items as $item) {
yield $item => $key;
}
};
$expected = [
"Iain" => "The Culture",
"Banks" => "The Culture",
"Ann" => "Radchaai Empire",
"Leckie" => "Radchaai Empire",
"Lois" => "Barrayar",
"McMaster" => "Barrayar",
"Bujold" => "Barrayar",
"Ursula"=> "Hainish",
"K."=> "Hainish",
"Le"=> "Hainish",
"Guin"=> "Hainish",
];
$actual = $this->map->flatMap($callback)->toArray();
$this->assertEquals($expected, $actual);
}
public function testFlatMapForVectors() : void {
$callback = function ($value) {
$items = explode(" ", $value);
foreach ($items as $item) {
yield $item => $value;
}
};
$expected = [
"Iain" => "Iain Banks",
"Banks" => "Iain Banks",
"Ann" => "Ann Leckie",
"Leckie" => "Ann Leckie",
"Lois" => "Lois McMaster Bujold",
"McMaster" => "Lois McMaster Bujold",
"Bujold" => "Lois McMaster Bujold",
"Ursula"=> "Ursula K. Le Guin",
"K."=> "Ursula K. Le Guin",
"Le"=> "Ursula K. Le Guin",
"Guin"=> "Ursula K. Le Guin",
];
$actual = $this->map->flatMap($callback)->toArray();
$this->assertEquals($expected, $actual);
}
public function testFlatMapWithCallbackWithoutArgument() : void {
$this->expectException(InvalidArgumentException::class);
$callback = function () {};
$this->map->flatMap($callback);
}
public function testMapToVector() : void {
$expected = [
"The Culture by Iain Banks",
"Radchaai Empire by Ann Leckie",
"Barrayar by Lois McMaster Bujold",
"Hainish by Ursula K. Le Guin",
];
$fn = fn($key, $value) => "$key by $value";
$this->assertEquals($expected, $this->map->mapToVector($fn)->toArray());
}
public function testMapToVectorWithOnlyValueParameter() : void {
$expected = [
"Author: Iain Banks",
"Author: Ann Leckie",
"Author: Lois McMaster Bujold",
"Author: Ursula K. Le Guin",
];
$fn = fn($value) => "Author: $value";
$this->assertEquals($expected, $this->map->mapToVector($fn)->toArray());
}
public function testMapToVectorWithoutParameter() : void {
$this->expectException(InvalidArgumentException::class);
$fn = fn() => "";
$this->map->mapToVector($fn);
}
public function testFilter () {
// Let's filter to keep names with 3 parts or more
$callback = function ($value) : bool {
return str_word_count($value) > 2;
};
$expected = [
// Some sci-fi civilizations and author
"Barrayar" => "Lois McMaster Bujold",
"Hainish" => "Ursula K. Le Guin",
];
$actual = $this->map->filter($callback)->toArray();
$this->assertEquals($expected, $actual);
}
public function testFilterWithKeyValueCallback () {
// Let's find civilization AND author with e inside
$expected = [
// Some sci-fi civilizations and author
"Radchaai Empire" => "Ann Leckie",
];
$callback = function ($key, $value) : bool {
return str_contains($key, "e") && str_contains($value, "e");
};
$actual = $this->map->filter($callback)->toArray();
$this->assertEquals($expected, $actual);
}
public function testFilterWithCallbackWithoutArgument() {
$this->expectException(InvalidArgumentException::class);
$callback = function () : bool { // No argument
return true;
};
$this->map->filter($callback);
}
public function testFilterKeys () {
// Let's filter to keep short civilization names
$callback = function ($key) : bool {
return str_word_count($key) == 1;
};
$expected = [
// Some sci-fi civilizations and author
"Barrayar" => "Lois McMaster Bujold",
"Hainish" => "Ursula K. Le Guin",
];
$actual = $this->map->filterKeys($callback)->toArray();
$this->assertEquals($expected, $actual);
}
///
/// HOF methods - WithCollection
///
public function testAny () : void {
$author_is_Iain_Banks = fn($author) => $author === "Iain Banks";
$this->assertTrue($this->map->any($author_is_Iain_Banks));
}
public function testAnyWithAllFalseValues () : void {
$author_is_Isaac_Asimov = fn($author) => $author === "Isaac Asimov";
$this->assertFalse($this->map->any($author_is_Isaac_Asimov));
}
public function testAll () : void {
$author_contains_space = fn($author) => str_contains($author, " ");
$this->assertTrue($this->map->all($author_contains_space));
}
public function testAllWithFalseValue () : void {
$author_is_Iain_Banks = fn($author) => $author === "Iain Banks";
$this->assertFalse($this->map->all($author_is_Iain_Banks));
}
///
/// ArrayAccess
///
public function testOffsetExists () : void {
$this->assertTrue(isset($this->map["The Culture"]));
$this->assertFalse(isset($this->map["Not existing"]));
}
public function testOffsetSetWithoutOffset () : void {
$this->expectException(InvalidArgumentException::class);
$this->map[] = "Another Author";
}
public function testOffsetSet () : void {
$this->map["The Culture"] = "Iain M. Banks";
$this->assertEquals("Iain M. Banks", $this->map["The Culture"]);
}
public function testOffsetUnset () : void {
unset($this->map["Barrayar"]);
$expected = [
"The Culture" => "Iain Banks",
"Radchaai Empire" => "Ann Leckie",
// "Barrayar" => "Lois McMaster Bujold", UNSET ENTRY
"Hainish" => "Ursula K. Le Guin",
];
$this->assertEquals($expected, $this->map->toArray());
}
///
/// IteratorAggregate
///
public function testGetIterator () : void {
$this->assertEquals(self::MAP_CONTENT, iterator_to_array($this->map));
}
///
/// WithCollection trait
///
public function testFirst () : void {
- $this->assertEquals("Iain Banks", $this->map->first());
+ $author = $this->map->first();
+
+ $this->assertTrue($author->isSome());
+ $this->assertEquals("Iain Banks", $author->getValue());
}
public function testFirstWhenEmpty () : void {
$map = new HashMap();
+ $author = $map->first();
- $this->expectException(OutOfRangeException::class);
- $map->first();
+ $this->assertTrue($author->isNone());
}
public function testFirstOr () : void {
$bits = BitsVector::new(4);
$bits[2] = 1;
$this->assertEquals("Iain Banks", $this->map->firstOr("Anonymous"));
}
public function testFirstOrWhenEmpty () : void {
$map = new HashMap();
$this->assertEquals("Anonymous", $map->firstOr("Anonymous"));
}
}
diff --git a/omnitools/tests/Collections/VectorTest.php b/omnitools/tests/Collections/VectorTest.php
index 98883e9..f9c9a42 100644
--- a/omnitools/tests/Collections/VectorTest.php
+++ b/omnitools/tests/Collections/VectorTest.php
@@ -1,456 +1,457 @@
<?php
declare(strict_types=1);
namespace Keruald\OmniTools\Tests\Collections;
use Keruald\OmniTools\Collections\BaseVector;
use Keruald\OmniTools\Collections\HashMap;
use Keruald\OmniTools\Collections\Vector;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use InvalidArgumentException;
use IteratorAggregate;
use OutOfRangeException;
use Traversable;
#[CoversClass(Vector::class)]
#[CoversClass(BaseVector::class)]
class VectorTest extends TestCase {
private Vector $vector;
protected function setUp () : void {
$this->vector = new Vector([1, 2, 3, 4, 5]);
}
public function testConstructorWithIterable () : void {
$iterable = new class implements IteratorAggregate {
public function getIterator () : Traversable {
yield 42;
yield 100;
}
};
$vector = new Vector($iterable);
$this->assertEquals([42, 100], $vector->toArray());
}
public function testFrom () : void {
$this->assertEquals([42, 100], Vector::from([42, 100])->toArray());
}
public function testGet () : void {
$vector = new Vector(["a", "b", "c"]);
$this->assertEquals("b", $vector->get(1));
}
public function testGetOverflow () : void {
$this->expectException(InvalidArgumentException::class);
$this->vector->get(800);
}
public function testGetOr () : void {
$vector = new Vector(["a", "b", "c"]);
$this->assertEquals("X", $vector->getOr(800, "X"));
}
public function testSet () : void {
$vector = new Vector(["a", "b", "c"]);
$vector->set(1, "x"); // should replace "b"
$this->assertEquals(["a", "x", "c"], $vector->toArray());
}
public function testContains () : void {
$this->assertTrue($this->vector->contains(2));
$this->assertFalse($this->vector->contains(666));
}
public function testCount () : void {
$this->assertEquals(5, $this->vector->count());
$this->assertEquals(0, (new Vector)->count());
}
public function testClear () : void {
$this->vector->clear();
$this->assertEquals(0, $this->vector->count());
}
public function testIsEmpty () : void {
$this->vector->clear();
$this->assertTrue($this->vector->isEmpty());
}
public function testPush () : void {
$this->vector->push(6);
$this->assertEquals([1, 2, 3, 4, 5, 6], $this->vector->toArray());
}
public function testAppend () : void {
$this->vector->append([6, 7, 8]);
$this->assertEquals([1, 2, 3, 4, 5, 6, 7 ,8], $this->vector->toArray());
}
public function testUpdate () : void {
$this->vector->update([5, 5, 5, 6, 7, 8]); // 5 already exists
$this->assertEquals([1, 2, 3, 4, 5, 6, 7 ,8], $this->vector->toArray());
}
public function testMap () : void {
$actual = $this->vector
->map(function ($x) { return $x * $x; })
->toArray();
$this->assertEquals([1, 4, 9, 16, 25], $actual);
}
public function testMapKeys () : void {
$vector = new Vector(["foo", "bar", "quux", "xizzy"]);
$filter = function ($key) {
return 0; // Let's collapse our array
};
$actual = $vector->mapKeys($filter)->toArray();
$this->assertEquals(["xizzy"], $actual);
}
public function testFlatMap () : void {
$expected = [
// Squares and cubes
1, 1,
4, 8,
9, 27,
16, 64,
25, 125
];
$callback = function ($n) {
yield $n * $n;
yield $n * $n * $n;
};
$actual = $this->vector->flatMap($callback)->toArray();
$this->assertEquals($expected, $actual);
}
public function testFlatMapWithKeyValueCallback() : void {
$vector = new Vector(["foo", "bar", "quux", "xizzy"]);
$callback = function (int $key, string $value) {
yield "$key::$value";
yield "$value ($key)";
};
$expected = [
"0::foo",
"foo (0)",
"1::bar",
"bar (1)",
"2::quux",
"quux (2)",
"3::xizzy",
"xizzy (3)",
];
$actual = $vector->flatMap($callback)->toArray();
$this->assertEquals($expected, $actual);
}
public function testFlatMapWithCallbackWithoutArgument() : void {
$this->expectException(InvalidArgumentException::class);
$callback = function () {};
$this->vector->flatMap($callback);
}
public function testMapToHashMap () : void {
$expected = [
1 => 1,
2 => 4,
3 => 9,
4 => 16,
5 => 25,
];
$fn = fn($value) => [$value, $value * $value];
$map = $this->vector->mapToHashMap($fn);
$this->assertInstanceOf(HashMap::class, $map);
$this->assertEquals($expected, $map->toArray());
}
public function testMapToHashMapWithCallbackWithoutArgument() : void {
$this->expectException(InvalidArgumentException::class);
$callback = function () {};
$this->vector->mapToHashMap($callback);
}
public function testMapToHashMapWithBadCallback () : void {
$this->expectException(InvalidArgumentException::class);
$callback = fn($key, $value) : bool => false; // not an array
$this->vector->mapToHashMap($callback);
}
public function testFilter () : void {
$vector = new Vector(["foo", "bar", "quux", "xizzy"]);
$filter = function ($item) {
return strlen($item) === 3; // Let's keep 3-letters words
};
$actual = $vector->filter($filter)->toArray();
$this->assertEquals(["foo", "bar"], $actual);
}
public function testFilterWithBadCallback () : void {
$this->expectException(InvalidArgumentException::class);
$badFilter = function () {};
$this->vector->filter($badFilter);
}
public function testFilterKeys () : void {
$filter = function ($key) {
return $key % 2 === 0; // Let's keep even indices
};
$actual = $this->vector
->filterKeys($filter)
->toArray();
$this->assertEquals([0, 2, 4], array_keys($actual));
}
public function testAny () : void {
$is_even = fn(int $item) : bool => $item % 2 === 0;
$this->assertTrue($this->vector->any($is_even));
}
public function testAnyWithAllFalseValues () : void {
$is_zero = fn(int $item) : bool => $item === 0;
$this->assertFalse($this->vector->any($is_zero));
}
public function testAll () : void {
$under_ten = fn(int $item) : bool => $item < 10;
$this->assertTrue($this->vector->all($under_ten));
}
public function testAllWithFalseValue () : void {
$is_even = fn(int $item) : bool => $item % 2 === 0;
$this->assertFalse($this->vector->all($is_even));
}
public function testChunk () : void {
$vector = new Vector([1, 2, 3, 4, 5, 6]);
$this->assertEquals(
[[1, 2], [3, 4], [5, 6]],
$vector->chunk(2)->toArray()
);
}
public function testSlice () : void {
$actual = $this->vector->slice(2, 3);
$this->assertEquals([3, 4, 5], $actual->toArray());
}
public function testImplode() : void {
$actual = (new Vector(["a", "b", "c"]))
->implode(".")
->__toString();
$this->assertEquals("a.b.c", $actual);
}
public function testImplodeWithoutDelimiter() : void {
$actual = (new Vector(["a", "b", "c"]))
->implode("")
->__toString();
$this->assertEquals("abc", $actual);
}
public function testExplode() : void {
$actual = Vector::explode(".", "a.b.c");
$this->assertEquals(["a", "b", "c"], $actual->toArray());
}
public function testExplodeWithoutDelimiter() : void {
$actual = Vector::explode("", "a.b.c");
$this->assertEquals(["a.b.c"], $actual->toArray());
}
///
/// n-grams
///
public function testBigrams() : void {
$expected = Vector::from([
[1, 2],
[2, 3],
[3, 4],
[4, 5],
]);
$this->assertEquals($expected, $this->vector->bigrams());
}
public function testTrigrams() : void {
$expected = Vector::from([
[1, 2, 3],
[2, 3, 4],
[3, 4, 5],
]);
$this->assertEquals($expected, $this->vector->trigrams());
}
public function testNgrams() : void {
$expected = Vector::from([
[1, 2, 3, 4],
[2, 3, 4, 5],
]);
$this->assertEquals($expected, $this->vector->ngrams(4));
}
public function testNgramsWithN1 () : void {
$expected = Vector::from([
[1],
[2],
[3],
[4],
[5],
]);
$this->assertEquals($expected, $this->vector->ngrams(1));
}
public static function provideLowN () : iterable {
yield [0];
yield [-1];
yield [PHP_INT_MIN];
}
#[DataProvider('provideLowN')]
public function testNgramsWithTooLowN ($n) : void {
$this->expectException(InvalidArgumentException::class);
$this->vector->ngrams($n);
}
public static function provideLargeN () : iterable {
yield [5];
yield [6];
yield [PHP_INT_MAX];
}
#[DataProvider('provideLargeN')]
public function testNgramsWithTooLargeN ($n) : void {
$expected = Vector::from([
[1, 2, 3, 4, 5],
]);
$this->assertEquals($expected, $this->vector->ngrams($n));
}
///
/// ArrayAccess
///
public function testArrayAccessFailsWithStringKey () : void {
$this->expectException(InvalidArgumentException::class);
$this->vector["foo"];
}
public function testOffsetExists () : void {
$this->assertTrue(isset($this->vector[0]));
$this->assertFalse(isset($this->vector[8]));
}
public function testOffsetSetWithoutOffset () : void {
$this->vector[] = 6;
$this->assertEquals(6, $this->vector[5]);
}
public function testOffsetSet () : void {
$this->vector[0] = 9;
$this->assertEquals(9, $this->vector[0]);
}
public function testOffsetUnset () : void {
unset($this->vector[2]);
$expected = [
0 => 1,
1 => 2,
// vector[2] has been unset
3 => 4,
4 => 5,
];
$this->assertEquals($expected, $this->vector->toArray());
}
///
/// IteratorAggregate
///
public function testGetIterator () : void {
$this->assertEquals([1, 2, 3, 4, 5], iterator_to_array($this->vector));
}
///
/// WithCollection trait
///
public function testFirst () : void {
- $this->assertEquals(1, $this->vector->first());
+ $item = $this->vector->first();
+ $this->assertEquals(1, $item->getValue());
}
public function testFirstWhenEmpty () : void {
$vector = new Vector;
- $this->expectException(OutOfRangeException::class);
- $vector->first();
+ $item = $vector->first();
+ $this->assertTrue($item->isNone());
}
public function testFirstOr () : void {
$this->assertEquals(1, $this->vector->firstOr(2));
}
public function testFirstOrWhenEmpty () : void {
$vector = new Vector;
$this->assertEquals(2, $vector->firstOr(2));
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Wed, Mar 18, 12:32 (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3539654
Default Alt Text
(40 KB)
Attached To
Mode
rKERUALD Keruald libraries development repository
Attached
Detach File
Event Timeline
Log In to Comment