diff --git a/docs/math/set.md b/docs/math/set.md new file mode 100644 index 0000000..fa016ed --- /dev/null +++ b/docs/math/set.md @@ -0,0 +1,127 @@ +# Set + +Class that wraps PHP arrays containing primitive types to mathematical sets. + +### Creation + +To create Set use flat arrays containing primitives only: + +``` +use \Phpml\Math\Set; + +$set = new Set([1, 2, 2, 3, 1.1, -1, -10]); +$set->toArray(); +// return [-10, -1, 1, 1.1, 2, 3] + +$set = new Set(['B', '', 'A']); +$set->toArray(); +// return ['', 'A', 'B'] +``` + +Injected array is sorted by SORT_ASC, duplicates are removed and index is rewritten. + +### Union + +Create the union of two Sets: + +``` +use \Phpml\Math\Set; + +$union = Set::union(new Set([1, 3]), new Set([1, 2])); +$union->toArray(); +//return [1, 2, 3] +``` + +### Intersection + +Create the intersection of two Sets: + +``` +use \Phpml\Math\Set; + +$intersection = Set::intersection(new Set(['A', 'C']), new Set(['B', 'C'])); +$intersection->toArray(); +//return ['C'] +``` + +### Complement + +Create the set-theoretic difference of two Sets: + +``` +use \Phpml\Math\Set; + +$difference = Set::difference(new Set(['A', 'B', 'C']), new Set(['A'])); +$union->toArray(); +//return ['B', 'C'] +``` + +### Adding elements + +``` +use \Phpml\Math\Set; + +$set = new Set([1, 2]); +$set->addAll([3]); +$set->add(4); +$set->toArray(); +//return [1, 2, 3, 4] +``` + +### Removing elements + +``` +use \Phpml\Math\Set; + +$set = new Set([1, 2]); +$set->removeAll([2]); +$set->remove(1); +$set->toArray(); +//return [] +``` + +### Check membership + +``` +use \Phpml\Math\Set; + +$set = new Set([1, 2]); +$set->containsAll([2, 3]); +//return false +$set->contains(1); +//return true +``` + +### Cardinality + +``` +use \Phpml\Math\Set; + +$set = new Set([1, 2]); +$set->cardinality(); +//return 2 +``` + +### Is empty + +``` +use \Phpml\Math\Set; + +$set = new Set(); +$set->isEmpty(); +//return true +``` + +### Working with loops + +``` +use \Phpml\Math\Set; + +$set = new Set(['A', 'B', 'C']); + +foreach($set as $element) { + echo "$element, "; +} + +// echoes A, B, C +``` diff --git a/src/Phpml/Math/Set.php b/src/Phpml/Math/Set.php new file mode 100644 index 0000000..20fc780 --- /dev/null +++ b/src/Phpml/Math/Set.php @@ -0,0 +1,211 @@ +elements = self::sanitize($elements); + } + + /** + * Creates the union of A and B. + * + * @param Set $a + * @param Set $b + * + * @return Set + */ + public static function union(Set $a, Set $b) : Set + { + return new self(array_merge($a->toArray(), $b->toArray())); + } + + /** + * Creates the intersection of A and B. + * + * @param Set $a + * @param Set $b + * + * @return Set + */ + public static function intersection(Set $a, Set $b) : Set + { + return new self(array_intersect($a->toArray(), $b->toArray())); + } + + /** + * Creates the difference of A and B. + * + * @param Set $a + * @param Set $b + * + * @return Set + */ + public static function difference(Set $a, Set $b) : Set + { + return new self(array_diff($a->toArray(), $b->toArray())); + } + + /** + * Creates the Cartesian product of A and B. + * + * @param Set $a + * @param Set $b + * + * @return Set[] + */ + public static function cartesian(Set $a, Set $b) : array + { + $cartesian = []; + + foreach ($a as $multiplier) { + foreach ($b as $multiplicand) { + $cartesian[] = new self(array_merge([$multiplicand], [$multiplier])); + } + } + + return $cartesian; + } + + /** + * Creates the power set of A. + * + * @param Set $a + * + * @return Set[] + */ + public static function power(Set $a) : array + { + $power = [new self()]; + + foreach ($a as $multiplicand) { + foreach ($power as $multiplier) { + $power[] = new self(array_merge([$multiplicand], $multiplier->toArray())); + } + } + + return $power; + } + + /** + * Removes duplicates and rewrites index. + * + * @param string[]|int[]|float[] $elements + * + * @return string[]|int[]|float[] + */ + private static function sanitize(array $elements) : array + { + sort($elements, SORT_ASC); + + return array_values(array_unique($elements, SORT_ASC)); + } + + /** + * @param string|int|float $element + * + * @return Set + */ + public function add($element) : Set + { + return $this->addAll([$element]); + } + + /** + * @param string[]|int[]|float[] $elements + * + * @return Set + */ + public function addAll(array $elements) : Set + { + $this->elements = self::sanitize(array_merge($this->elements, $elements)); + + return $this; + } + + /** + * @param string|int|float $element + * + * @return Set + */ + public function remove($element) : Set + { + return $this->removeAll([$element]); + } + + /** + * @param string[]|int[]|float[] $elements + * + * @return Set + */ + public function removeAll(array $elements) : Set + { + $this->elements = self::sanitize(array_diff($this->elements, $elements)); + + return $this; + } + + /** + * @param string|int|float $element + * + * @return bool + */ + public function contains($element) : bool + { + return $this->containsAll([$element]); + } + + /** + * @param string[]|int[]|float[] $elements + * + * @return bool + */ + public function containsAll(array $elements) : bool + { + return !array_diff($elements, $this->elements); + } + + /** + * @return string[]|int[]|float[] + */ + public function toArray() : array + { + return $this->elements; + } + + /** + * @return \ArrayIterator + */ + public function getIterator() : \ArrayIterator + { + return new \ArrayIterator($this->elements); + } + + /** + * @return bool + */ + public function isEmpty() : bool + { + return $this->cardinality() == 0; + } + + /** + * @return int + */ + public function cardinality() : int + { + return count($this->elements); + } +} diff --git a/tests/Phpml/Math/SetTest.php b/tests/Phpml/Math/SetTest.php new file mode 100644 index 0000000..cdf4460 --- /dev/null +++ b/tests/Phpml/Math/SetTest.php @@ -0,0 +1,93 @@ +assertInstanceOf('\Phpml\Math\Set', $union); + $this->assertEquals(new Set([1, 2, 3]), $union); + $this->assertEquals(3, $union->cardinality()); + } + + public function testIntersection() + { + $intersection = Set::intersection(new Set(['C', 'A']), new Set(['B', 'C'])); + + $this->assertInstanceOf('\Phpml\Math\Set', $intersection); + $this->assertEquals(new Set(['C']), $intersection); + $this->assertEquals(1, $intersection->cardinality()); + } + + public function testDifference() + { + $difference = Set::difference(new Set(['C', 'A', 'B']), new Set(['A'])); + + $this->assertInstanceOf('\Phpml\Math\Set', $difference); + $this->assertEquals(new Set(['B', 'C']), $difference); + $this->assertEquals(2, $difference->cardinality()); + } + + public function testPower() + { + $power = Set::power(new Set(['A', 'B'])); + + $this->assertInternalType('array', $power); + $this->assertEquals([new Set(), new Set(['A']), new Set(['B']), new Set(['A', 'B'])], $power); + $this->assertEquals(4, count($power)); + } + + public function testCartesian() + { + $cartesian = Set::cartesian(new Set(['A']), new Set([1, 2])); + + $this->assertInternalType('array', $cartesian); + $this->assertEquals([new Set(['A', 1]), new Set(['A', 2])], $cartesian); + $this->assertEquals(2, count($cartesian)); + } + + public function testContains() + { + $set = new Set(['B', 'A', 2, 1]); + + $this->assertTrue($set->contains('B')); + $this->assertTrue($set->containsAll(['A', 'B'])); + + $this->assertFalse($set->contains('C')); + $this->assertFalse($set->containsAll(['A', 'B', 'C'])); + } + + public function testRemove() + { + $set = new Set(['B', 'A', 2, 1]); + + $this->assertEquals((new Set([1, 2, 2, 2, 'B']))->toArray(), $set->remove('A')->toArray()); + } + + public function testAdd() + { + $set = new Set(['B', 'A', 2, 1]); + $set->addAll(['foo', 'bar']); + $this->assertEquals(6, $set->cardinality()); + } + + public function testEmpty() + { + $set = new Set([1, 2]); + $set->removeAll([2, 1]); + $this->assertEquals(new Set(), $set); + $this->assertTrue($set->isEmpty()); + } + + public function testToArray() + { + $set = new Set([1, 2, 2, 3, 'A', false, '', 1.1, -1, -10, 'B']); + + $this->assertEquals([-10, '', -1, 'A', 'B', 1, 1.1, 2, 3], $set->toArray()); + } +}