mirror of
https://github.com/Llewellynvdm/php-ml.git
synced 2024-11-21 20:45:10 +00:00
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:
parent
90038befa9
commit
fa87eca375
127
docs/math/set.md
Normal file
127
docs/math/set.md
Normal 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
211
src/Phpml/Math/Set.php
Normal 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);
|
||||
}
|
||||
}
|
93
tests/Phpml/Math/SetTest.php
Normal file
93
tests/Phpml/Math/SetTest.php
Normal 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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user