Add new class Set for simple Set-theoretical operations

### Features

* Works only with primitive types int, float, string
* Implements set theortic operations union, intersection, complement
* Modifies set by adding, removing elements
* Implements \IteratorAggregate for use in loops

### Implementation details

Based on array functions:
* array_diff,
* array_merge,
* array_intersection,
* array_unique,
* array_values,
* sort.

### Drawbacks

* **Do not work with objects.**
* Power set and Cartesian product returning array of Set
This commit is contained in:
Patrick Florek 2016-09-10 13:24:43 +02:00
parent 90038befa9
commit fa87eca375
3 changed files with 431 additions and 0 deletions

127
docs/math/set.md Normal file
View File

@ -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
```

211
src/Phpml/Math/Set.php Normal file
View File

@ -0,0 +1,211 @@
<?php
declare(strict_types=1);
namespace Phpml\Math;
class Set implements \IteratorAggregate
{
/**
* @var string[]|int[]|float[]
*/
private $elements;
/**
* @param string[]|int[]|float[] $elements
*/
public function __construct(array $elements = [])
{
$this->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);
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace tests\Phpml\Math;
use Phpml\Math\Set;
class SetTest extends \PHPUnit_Framework_TestCase
{
public function testUnion()
{
$union = Set::union(new Set([3, 1]), new Set([3, 2, 2]));
$this->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());
}
}