Skip to content
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1868,6 +1868,7 @@ $bool = $A->isProperSuperset($B); // A ⊇ B & A ≠ B
// Set operations with other sets - return a new Set
$A∪B = $A->union($B);
$A∩B = $A->intersect($B);
$A∩B = $A->intersectPartial(2, $B); // M-partial intersection
$A\B = $A->difference($B); // relative complement
$AΔB = $A->symmetricDifference($B);
$A×B = $A->cartesianProduct($B);
Expand Down
61 changes: 61 additions & 0 deletions src/SetTheory/Set.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace MathPHP\SetTheory;

use MathPHP\Util\JustifyMultipleIterator;
use MathPHP\Util\NoValueMonad;

/**
* Set (Set Theory)
* A set is a collection of distinct objects, considered as an object in
Expand Down Expand Up @@ -374,6 +377,7 @@ public function isProperSuperset(Set $B): bool
* SET OPERATIONS ON OTHER SETS
* - Union
* - Intersection
* - Partial intersection
* - Difference
* - Symmetric difference
**************************************************************************/
Expand Down Expand Up @@ -428,6 +432,63 @@ public function intersect(Set ...$Bs): Set
return new Set($intersection);
}

/**
* Produces a new set of the M-partial intersection of this set and another given sets.
*
* Definition:
*
* An M-partial intersection (for M > 0) of N sets is a set elements in which
* are contained in at least M initial sets.
*
* Properties:
*
* - 1-partial intersection is equivalent to the union of these sets.
* - 2-partial intersection is equivalent to the difference of the union and the symmetric difference of these sets.
* - N-partial intersection is equivalent to the common (complete) intersection of these sets.
* - For any M > N M-partial intersection always equals to the empty set.
*
* @see https://github.com/Smoren/partial-intersection-php for the explanation and the examples.
*
* @param int $m Min intersection count
* @param Set ...$Bs One or more sets
*
* @return Set
*/
public function intersectPartial(int $m, Set ...$Bs): Set
{
$B_members = [];
foreach ($Bs as $B) {
$B_members[] = $B->asArray();
}

$iterator = new JustifyMultipleIterator($this->asArray(), ...$B_members);

$usageMap = [];
$intersection = [];

foreach ($iterator as $values) {
foreach ($values as $value) {
if ($value instanceof NoValueMonad) {
continue;
}

$key = $this->getKey($value);

if (!isset($usageMap[$key])) {
$usageMap[$key] = 0;
}

$usageMap[$key]++;

if ($usageMap[$key] === $m) {
$intersection[] = $value;
}
}
}

return new Set($intersection);
}

/**
* Difference (relative complement) (A ∖ B) or (A - B)
* Produces a new set with elements that are not in the other sets.
Expand Down
2 changes: 1 addition & 1 deletion src/Util/Iter.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static function zip(iterable ...$iterables): \MultipleIterator
*
* @return \Iterator|\IteratorIterator|\ArrayIterator
*/
private static function makeIterator(iterable $iterable): \Iterator
public static function makeIterator(iterable $iterable): \Iterator
{
switch (true) {
case $iterable instanceof \Iterator:
Expand Down
95 changes: 95 additions & 0 deletions src/Util/JustifyMultipleIterator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

namespace MathPHP\Util;

/**
* @implements \Iterator<array<mixed>>
*
* Based on IterTools PHP's IteratorFactory.
* @see https://github.com/markrogoyski/itertools-php
* @see https://github.com/markrogoyski/itertools-php/blob/main/src/Util/JustifyMultipleIterator.php
*/
class JustifyMultipleIterator implements \Iterator
{
/**
* @var array<\Iterator<mixed>>
*/
protected $iterators = [];
/**
* @var int
*/
protected $index = 0;

/**
* @param iterable<mixed> ...$iterables
*/
public function __construct(iterable ...$iterables)
{
foreach ($iterables as $iterable) {
$this->iterators[] = Iter::makeIterator($iterable);
}
}

/**
* {@inheritDoc}
*
* @return array<mixed>
*/
public function current(): array
{
return array_map(
static function (\Iterator $iterator) {
return $iterator->valid() ? $iterator->current() : NoValueMonad::getInstance();
},
$this->iterators
);
}

/**
* {@inheritDoc}
*/
public function next(): void
{
foreach ($this->iterators as $iterator) {
if ($iterator->valid()) {
$iterator->next();
}
}
$this->index++;
}

/**
* {@inheritDoc}
*
* @return int
*/
public function key(): int
{
return $this->index;
}

/**
* {@inheritDoc}
*/
public function valid(): bool
{
foreach ($this->iterators as $iterator) {
if ($iterator->valid()) {
return true;
}
}

return false;
}

/**
* {@inheritDoc}
*/
public function rewind(): void
{
foreach ($this->iterators as $iterator) {
$iterator->rewind();
}
$this->index = 0;
}
}
29 changes: 29 additions & 0 deletions src/Util/NoValueMonad.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace MathPHP\Util;

/**
* Based on IterTools PHP's IteratorFactory.
* @see https://github.com/markrogoyski/itertools-php
* @see https://github.com/markrogoyski/itertools-php/blob/main/src/Util/NoValueMonad.php
*/
class NoValueMonad
{
/**
* @var self|null
*/
private static $instance = null;

public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}

return self::$instance;
}

private function __construct()
{
}
}
Loading