Fix multimap implementation and add tests

This commit is contained in:
Dennis Eichhorn 2018-09-09 20:06:18 +02:00
parent 5b3178e049
commit 105d4f355b
3 changed files with 203 additions and 71 deletions

View File

@ -90,7 +90,7 @@ class MultiMap implements \Countable
$inserted = false; $inserted = false;
if ($this->keyType !== KeyType::SINGLE) { if ($this->keyType !== KeyType::SINGLE) {
$keys = [implode(':', $keys)]; $keys = [\implode(':', $keys)];
} }
foreach ($keys as $key) { foreach ($keys as $key) {
@ -202,7 +202,7 @@ class MultiMap implements \Countable
{ {
if (\is_array($key)) { if (\is_array($key)) {
if ($this->orderType === OrderType::LOOSE) { if ($this->orderType === OrderType::LOOSE) {
$keys = Permutation::permut($key); $keys = Permutation::permut($key, [], false);
foreach ($keys as $key => $value) { foreach ($keys as $key => $value) {
$key = \implode(':', $value); $key = \implode(':', $value);
@ -231,7 +231,7 @@ class MultiMap implements \Countable
*/ */
public function set($key, $value) : bool public function set($key, $value) : bool
{ {
if ($this->keyType === KeyType::MULTIPLE && is_array($key)) { if ($this->keyType === KeyType::MULTIPLE && \is_array($key)) {
return $this->setMultiple($key, $value); return $this->setMultiple($key, $value);
} }
@ -250,19 +250,17 @@ class MultiMap implements \Countable
*/ */
private function setMultiple($key, $value) : bool private function setMultiple($key, $value) : bool
{ {
if ($this->orderType !== OrderType::LOOSE) { if ($this->orderType !== OrderType::STRICT) {
$permutation = Permutation::permut($key); $permutation = Permutation::permut($key, [], false);
foreach ($permutation as $permut) { foreach ($permutation as $permut) {
if ($this->set(implode(':', $permut), $value)) { if ($this->set(\implode(':', $permut), $value)) {
return true; return true;
} }
} }
} else { } else {
return $this->set(implode(':', $key), $value); return $this->set(\implode(':', $key), $value);
} }
return false;
} }
/** /**
@ -297,7 +295,7 @@ class MultiMap implements \Countable
*/ */
public function remove($key) : bool public function remove($key) : bool
{ {
if ($this->keyType === KeyType::MULTIPLE && is_array($key)) { if ($this->keyType === KeyType::MULTIPLE && \is_array($key)) {
return $this->removeMultiple($key); return $this->removeMultiple($key);
} }
@ -316,17 +314,17 @@ class MultiMap implements \Countable
private function removeMultiple($key) : bool private function removeMultiple($key) : bool
{ {
if ($this->orderType !== OrderType::LOOSE) { if ($this->orderType !== OrderType::LOOSE) {
return $this->remove(implode(':', $key)); return $this->remove(\implode(':', $key));
} }
$keys = Permutation::permut($key); $keys = Permutation::permut($key, [], false);
$found = true; $found = false;
foreach ($keys as $key => $value) { foreach ($keys as $key => $value) {
$allFound = $this->remove(implode(':', $value)); $allFound = $this->remove(\implode(':', $value));
if (!$allFound) { if ($allFound) {
$found = false; $found = true;
} }
} }
@ -399,43 +397,11 @@ class MultiMap implements \Countable
*/ */
public function removeKey($key) : bool public function removeKey($key) : bool
{ {
if ($this->keyType === KeyType::MULTIPLE && is_array($key)) { if ($this->keyType === KeyType::MULTIPLE) {
return $this->removeKeyMultiple($key); return false;
} else {
return $this->removeKeySingle($key);
} }
}
/** return $this->removeKeySingle($key);
* Remove key.
*
* This only removes the value if no other key exists for this value.
*
* @param mixed $key Key used to identify value
*
* @return bool
*
* @since 1.0.0
*/
private function removeKeyMultiple($key) : bool
{
if ($this->orderType === OrderType::LOOSE) {
$keys = Permutation::permut($key);
$removed = false;
foreach ($keys as $key => $value) {
$removed = $this->removeKey(implode(':', $value));
if (!$removed) {
return false;
}
}
return $removed;
} else {
return $this->removeKey(implode(':', $key));
}
} }
/** /**
@ -493,7 +459,9 @@ class MultiMap implements \Countable
public function getSiblingsMultiple($key) : array public function getSiblingsMultiple($key) : array
{ {
if ($this->orderType === OrderType::LOOSE) { if ($this->orderType === OrderType::LOOSE) {
return Permutation::permut($key); $key = \is_array($key) ? $key : [$key];
return Permutation::permut($key, [], false);
} }
return []; return [];

View File

@ -45,12 +45,12 @@ final class Permutation
* *
* @since 1.0.0 * @since 1.0.0
*/ */
public static function permut(array $toPermute, array $result = []) : array public static function permut(array $toPermute, array $result = [], bool $concat = true) : array
{ {
$permutations = []; $permutations = [];
if (empty($toPermute)) { if (empty($toPermute)) {
$permutations[] = \implode('', $result); $permutations[] = $concat ? \implode('', $result) : $result;
} else { } else {
foreach ($toPermute as $key => $val) { foreach ($toPermute as $key => $val) {
$newArr = $toPermute; $newArr = $toPermute;
@ -59,7 +59,7 @@ final class Permutation
unset($newArr[$key]); unset($newArr[$key]);
$permutations = array_merge($permutations, self::permut($newArr, $newres)); $permutations = \array_merge($permutations, self::permut($newArr, $newres, $concat));
} }
} }
@ -78,7 +78,7 @@ final class Permutation
*/ */
public static function isPermutation(string $a, string $b) : bool public static function isPermutation(string $a, string $b) : bool
{ {
return count_chars($a, 1) === count_chars($b, 1); return \count_chars($a, 1) === \count_chars($b, 1);
} }
/** /**
@ -93,7 +93,7 @@ final class Permutation
*/ */
public static function isPalindrome(string $a, string $filter = 'a-zA-Z0-9') : bool public static function isPalindrome(string $a, string $filter = 'a-zA-Z0-9') : bool
{ {
$a = \strtolower(preg_replace('/[^' . $filter . ']/', '', $a)); $a = \strtolower(\preg_replace('/[^' . $filter . ']/', '', $a));
return $a === \strrev($a); return $a === \strrev($a);
} }
@ -112,11 +112,11 @@ final class Permutation
*/ */
public static function permutate($toPermute, array $key) public static function permutate($toPermute, array $key)
{ {
if (!is_array($toPermute) && !is_string($toPermute)) { if (!\is_array($toPermute) && !\is_string($toPermute)) {
throw new \InvalidArgumentException('Parameter has to be array or string'); throw new \InvalidArgumentException('Parameter has to be array or string');
} }
$length = is_array($toPermute) ? \count($toPermute) : \strlen($toPermute); $length = \is_array($toPermute) ? \count($toPermute) : \strlen($toPermute);
if (\count($key) > $length) { if (\count($key) > $length) {
throw new \InvalidArgumentException('There mustn not be more keys than permutation elements.'); throw new \InvalidArgumentException('There mustn not be more keys than permutation elements.');

View File

@ -14,6 +14,8 @@
namespace phpOMS\tests\Stdlib\Map; namespace phpOMS\tests\Stdlib\Map;
use phpOMS\Stdlib\Map\MultiMap; use phpOMS\Stdlib\Map\MultiMap;
use phpOMS\Stdlib\Map\KeyType;
use phpOMS\Stdlib\Map\OrderType;
class MultiMapTest extends \PHPUnit\Framework\TestCase class MultiMapTest extends \PHPUnit\Framework\TestCase
{ {
@ -43,7 +45,7 @@ class MultiMapTest extends \PHPUnit\Framework\TestCase
self::assertFalse($map->remove('someKey')); self::assertFalse($map->remove('someKey'));
} }
public function testBasicAdd() public function testBasicAddAny()
{ {
$map = new MultiMap(); $map = new MultiMap();
@ -54,7 +56,7 @@ class MultiMapTest extends \PHPUnit\Framework\TestCase
self::assertEquals('val1', $map->get('b')); self::assertEquals('val1', $map->get('b'));
} }
public function testOverwrite() public function testOverwriteAny()
{ {
$map = new MultiMap(); $map = new MultiMap();
@ -66,7 +68,7 @@ class MultiMapTest extends \PHPUnit\Framework\TestCase
self::assertEquals('val2', $map->get('b')); self::assertEquals('val2', $map->get('b'));
} }
public function testOverwritePartialFalse() public function testOverwritePartialFalseAny()
{ {
$map = new MultiMap(); $map = new MultiMap();
@ -79,7 +81,7 @@ class MultiMapTest extends \PHPUnit\Framework\TestCase
self::assertEquals('val3', $map->get('c')); self::assertEquals('val3', $map->get('c'));
} }
public function testOverwriteFalseFalse() public function testOverwriteFalseFalseAny()
{ {
$map = new MultiMap(); $map = new MultiMap();
@ -93,7 +95,7 @@ class MultiMapTest extends \PHPUnit\Framework\TestCase
self::assertEquals('val3', $map->get('c')); self::assertEquals('val3', $map->get('c'));
} }
public function testSet() public function testSetAny()
{ {
$map = new MultiMap(); $map = new MultiMap();
@ -112,7 +114,7 @@ class MultiMapTest extends \PHPUnit\Framework\TestCase
self::assertEquals('val3', $map->get('c')); self::assertEquals('val3', $map->get('c'));
} }
public function testRemap() public function testRemapAny()
{ {
$map = new MultiMap(); $map = new MultiMap();
@ -142,7 +144,7 @@ class MultiMapTest extends \PHPUnit\Framework\TestCase
self::assertEquals('val3', $map->get('c')); self::assertEquals('val3', $map->get('c'));
} }
public function testMapInfo() public function testMapInfoAny()
{ {
$map = new MultiMap(); $map = new MultiMap();
@ -159,7 +161,7 @@ class MultiMapTest extends \PHPUnit\Framework\TestCase
self::assertTrue(\is_array($map->values())); self::assertTrue(\is_array($map->values()));
} }
public function testSiblings() public function testSiblingsAny()
{ {
$map = new MultiMap(); $map = new MultiMap();
@ -177,7 +179,7 @@ class MultiMapTest extends \PHPUnit\Framework\TestCase
self::assertEquals(['a'], $siblings); self::assertEquals(['a'], $siblings);
} }
public function testRemove() public function testRemoveAny()
{ {
$map = new MultiMap(); $map = new MultiMap();
@ -190,9 +192,6 @@ class MultiMapTest extends \PHPUnit\Framework\TestCase
$removed = $map->remove('d'); $removed = $map->remove('d');
self::assertFalse($removed); self::assertFalse($removed);
$removed = $map->remove('d');
self::assertFalse($removed);
$removed = $map->remove('c'); $removed = $map->remove('c');
self::assertTrue($removed); self::assertTrue($removed);
self::assertEquals(2, \count($map->keys())); self::assertEquals(2, \count($map->keys()));
@ -206,4 +205,169 @@ class MultiMapTest extends \PHPUnit\Framework\TestCase
self::assertEquals(1, \count($map->keys())); self::assertEquals(1, \count($map->keys()));
self::assertEquals(1, \count($map->values())); self::assertEquals(1, \count($map->values()));
} }
public function testBasicAddExact()
{
$map = new MultiMap(KeyType::MULTIPLE);
$inserted = $map->add(['a', 'b'], 'val1');
self::assertEquals(1, $map->count());
self::assertTrue($inserted);
self::assertEquals('val1', $map->get(['a', 'b']));
self::assertEquals('val1', $map->get(['b', 'a']));
}
public function testBasicAddExactOrdered()
{
$map = new MultiMap(KeyType::MULTIPLE, OrderType::STRICT);
$inserted = $map->add(['a', 'b'], 'val1');
self::assertEquals(1, $map->count());
self::assertTrue($inserted);
self::assertEquals('val1', $map->get(['a', 'b']));
self::assertEquals(null, $map->get(['b', 'a']));
}
public function testOverwriteExact()
{
$map = new MultiMap(KeyType::MULTIPLE);
$inserted = $map->add(['a', 'b'], 'val1');
$inserted = $map->add(['a', 'b'], 'val2');
self::assertEquals(1, $map->count());
self::assertTrue($inserted);
self::assertEquals('val2', $map->get(['a', 'b']));
}
public function testOverwritePartialFalseExact()
{
$map = new MultiMap(KeyType::MULTIPLE);
$inserted = $map->add(['a', 'b'], 'val2');
$inserted = $map->add(['a', 'c'], 'val3', false);
self::assertEquals(2, $map->count());
self::assertTrue($inserted);
self::assertEquals('val2', $map->get(['a', 'b']));
self::assertEquals('val3', $map->get(['c', 'a']));
}
public function testOverwriteFalseFalseExact()
{
$map = new MultiMap(KeyType::MULTIPLE);
$inserted = $map->add(['a', 'b'], 'val2');
$inserted = $map->add(['a', 'c'], 'val3', false);
$inserted = $map->add(['a', 'c'], 'val4', false);
self::assertEquals(2, $map->count());
self::assertFalse($inserted);
self::assertEquals('val2', $map->get(['a', 'b']));
self::assertEquals('val3', $map->get(['a', 'c']));
}
public function testSetExact()
{
$map = new MultiMap(KeyType::MULTIPLE);
$inserted = $map->add(['a', 'b'], 'val2');
$inserted = $map->add(['a', 'c'], 'val3', false);
$set = $map->set('d', 'val4');
self::assertFalse($set);
self::assertEquals(2, $map->count());
$set = $map->set(['a', 'b'], 'val4');
self::assertEquals(2, $map->count());
self::assertTrue($set);
self::assertEquals('val4', $map->get(['a', 'b']));
self::assertEquals('val4', $map->get(['b', 'a']));
}
public function testSetExactOrdered()
{
$map = new MultiMap(KeyType::MULTIPLE, OrderType::STRICT);
$inserted = $map->add(['a', 'b'], 'val2');
$inserted = $map->add(['a', 'c'], 'val3', false);
$set = $map->set('c', 'val4');
self::assertFalse($set);
self::assertEquals(2, $map->count());
$set = $map->set(['a', 'b'], 'val4');
self::assertEquals(2, $map->count());
self::assertTrue($set);
self::assertEquals('val4', $map->get(['a', 'b']));
$set = $map->set(['b', 'a'], 'val5');
self::assertEquals(2, $map->count());
self::assertFalse($set);
}
public function testRemapExact()
{
$map = new MultiMap(KeyType::MULTIPLE);
$inserted = $map->add(['a', 'b'], 'val2');
$remap = $map->remap(['a', 'b'], ['c', 'd']);
self::assertFalse($remap);
}
public function testSiblingsExact()
{
$map = new MultiMap(KeyType::MULTIPLE);
$inserted = $map->add(['a', 'b'], 'val2');
self::assertEquals([['a', 'b'], ['b', 'a']], $map->getSiblings(['a', 'b']));
}
public function testSiblingsExactOrdered()
{
$map = new MultiMap(KeyType::MULTIPLE, OrderType::STRICT);
$inserted = $map->add(['a', 'b'], 'val2');
self::assertEquals([], $map->getSiblings(['a', 'b']));
}
public function testRemoveExact()
{
$map = new MultiMap(KeyType::MULTIPLE);
$inserted = $map->add(['a', 'b'], 'val2');
$inserted = $map->add(['a', 'c'], 'val3', false);
self::assertEquals(2, \count($map->keys()));
self::assertEquals(2, \count($map->values()));
$removed = $map->remove('d');
self::assertFalse($removed);
$removed = $map->remove(['a', 'b']);
self::assertTrue($removed);
self::assertEquals(1, \count($map->keys()));
self::assertEquals(1, \count($map->values()));
self::assertFalse($map->removeKey(['a', 'b']));
}
public function testRemoveExactOrdered()
{
$map = new MultiMap(KeyType::MULTIPLE, OrderType::STRICT);
$inserted = $map->add(['a', 'b'], 'val2');
$inserted = $map->add(['a', 'c'], 'val3', false);
self::assertEquals(2, \count($map->keys()));
self::assertEquals(2, \count($map->values()));
$removed = $map->remove(['b', 'a']);
self::assertFalse($removed);
$removed = $map->remove(['a', 'b']);
self::assertTrue($removed);
self::assertEquals(1, \count($map->keys()));
self::assertEquals(1, \count($map->values()));
self::assertFalse($map->removeKey(['a', 'b']));
}
} }