在PHP中计算具有阈值的数组的交集
Calculate the intersection of arrays with a threshold in PHP
假设我有以下数组:
$a = [1,2,3,4,5];
$b = [1,3,4,5,6];
$c = [1,7,8,9,10];
$d = [1,2,3,4];
它们的交集是 $result = [1]
,这很容易。但是,如果我想要那些最小阈值比方说 3 的交集呢?阈值意味着我可以从交叉点跳过一个或多个数组,只要我得到的交叉点至少有 3 个元素,在这种情况下可能会导致:
$result = [1,3,4];
1、3、4出现在$a、$b、$d中,但不出现在$c中,因为阈值而被跳过。是否有现有的 PHP class、算法或函数可以用来完成此操作?
没有内置功能。你需要写一些简短的东西,比如:
$values = [];
foreach ([$a, $b, $c, $d] as $arr)
foreach ($arr as $value)
$values[$value] = ($values[$value] ?? 0) + 1;
// For threshold of 3
$values = array_keys(array_filter($values, function($a) { return $a >= 3; }));
注意:这需要 PHP7 for ??操作员。否则使用类似:
$values[$value] = empty($values[$value]) ? 1 : $values[$value] + 1;
为此,我们必须使用数组的组合。我使用了这个 great article 中的组合算法。调整这个算法我们可以写成下面的class:
class Intersections
{
protected $arrays;
private $arraysSize;
public function __construct($arrays)
{
$this->arrays = $arrays;
$this->arraysSize = count($arrays);
}
public function getByThreshold($threshold)
{
$intersections = $this->getAll();
foreach ($intersections as $intersection) {
if (count($intersection) >= $threshold) {
return $intersection;
}
}
return null;
}
protected $intersections;
public function getAll()
{
if (is_null($this->intersections)) {
$this->generateIntersections();
}
return $this->intersections;
}
private function generateIntersections()
{
$this->generateCombinationsMasks();
$this->generateCombinations();
$combinationSize = $this->arraysSize;
$intersectionSize = 0;
foreach ($this->combinations as $combination) {
$intersection = call_user_func_array('array_intersect', $combination);
if ($combinationSize > count($combination)) {
$combinationSize = count($combination);
$intersectionSize = 0;
}
if (count($intersection) > $intersectionSize) {
$this->intersections[$combinationSize] = $intersection;
$intersectionSize = count($intersection);
}
}
}
private $combinationsMasks;
private function generateCombinationsMasks()
{
$combinationsMasks = [];
$totalNumberOfCombinations = pow(2, $this->arraysSize);
for ($i = $totalNumberOfCombinations - 1; $i > 0; $i--) {
$combinationsMasks[] = str_pad(
decbin($i), $this->arraysSize, '0', STR_PAD_LEFT
);
}
usort($combinationsMasks, function ($a, $b) {
return strcmp(strtr($b, ['']), strtr($a, ['']));
});
$this->combinationsMasks = array_slice(
$combinationsMasks, 0, -$this->arraysSize
);
}
private $combinations;
private function generateCombinations()
{
$this->combinations = array_map(function ($combinationMask) {
return $this->generateCombination($combinationMask);
}, $this->combinationsMasks);
}
private function generateCombination($combinationMask)
{
$combination = [];
foreach (str_split($combinationMask) as $key => $indicator) {
if ($indicator) {
$combination[] = $this->arrays[$key];
}
}
return $combination;
}
}
我试图给方法起一个不言自明的名字。一些代码块可以进一步优化(例如,我在同一个数组上多次调用 count
函数;这样做是为了减少变量摆弄)以供生产使用。
基本上逻辑很简单。我们生成数组的所有组合,并根据使用的数组数量对它们进行递减排序。然后我们找到每个组合长度的最长交集。实际上,这是最难的部分。为了获得一个特定的交叉点,我们 return 第一个与阈值匹配的交叉点。
$intersections = new Intersections([$a, $b, $c, $d]);
var_dump($intersections->getAll());
var_dump($intersections->getByThreshold(3));
这里是working demo。
还有其他方法可以找到所有组合,例如one from "PHP Cookbook"。你可以选择你最喜欢的。
假设我有以下数组:
$a = [1,2,3,4,5];
$b = [1,3,4,5,6];
$c = [1,7,8,9,10];
$d = [1,2,3,4];
它们的交集是 $result = [1]
,这很容易。但是,如果我想要那些最小阈值比方说 3 的交集呢?阈值意味着我可以从交叉点跳过一个或多个数组,只要我得到的交叉点至少有 3 个元素,在这种情况下可能会导致:
$result = [1,3,4];
1、3、4出现在$a、$b、$d中,但不出现在$c中,因为阈值而被跳过。是否有现有的 PHP class、算法或函数可以用来完成此操作?
没有内置功能。你需要写一些简短的东西,比如:
$values = [];
foreach ([$a, $b, $c, $d] as $arr)
foreach ($arr as $value)
$values[$value] = ($values[$value] ?? 0) + 1;
// For threshold of 3
$values = array_keys(array_filter($values, function($a) { return $a >= 3; }));
注意:这需要 PHP7 for ??操作员。否则使用类似:
$values[$value] = empty($values[$value]) ? 1 : $values[$value] + 1;
为此,我们必须使用数组的组合。我使用了这个 great article 中的组合算法。调整这个算法我们可以写成下面的class:
class Intersections
{
protected $arrays;
private $arraysSize;
public function __construct($arrays)
{
$this->arrays = $arrays;
$this->arraysSize = count($arrays);
}
public function getByThreshold($threshold)
{
$intersections = $this->getAll();
foreach ($intersections as $intersection) {
if (count($intersection) >= $threshold) {
return $intersection;
}
}
return null;
}
protected $intersections;
public function getAll()
{
if (is_null($this->intersections)) {
$this->generateIntersections();
}
return $this->intersections;
}
private function generateIntersections()
{
$this->generateCombinationsMasks();
$this->generateCombinations();
$combinationSize = $this->arraysSize;
$intersectionSize = 0;
foreach ($this->combinations as $combination) {
$intersection = call_user_func_array('array_intersect', $combination);
if ($combinationSize > count($combination)) {
$combinationSize = count($combination);
$intersectionSize = 0;
}
if (count($intersection) > $intersectionSize) {
$this->intersections[$combinationSize] = $intersection;
$intersectionSize = count($intersection);
}
}
}
private $combinationsMasks;
private function generateCombinationsMasks()
{
$combinationsMasks = [];
$totalNumberOfCombinations = pow(2, $this->arraysSize);
for ($i = $totalNumberOfCombinations - 1; $i > 0; $i--) {
$combinationsMasks[] = str_pad(
decbin($i), $this->arraysSize, '0', STR_PAD_LEFT
);
}
usort($combinationsMasks, function ($a, $b) {
return strcmp(strtr($b, ['']), strtr($a, ['']));
});
$this->combinationsMasks = array_slice(
$combinationsMasks, 0, -$this->arraysSize
);
}
private $combinations;
private function generateCombinations()
{
$this->combinations = array_map(function ($combinationMask) {
return $this->generateCombination($combinationMask);
}, $this->combinationsMasks);
}
private function generateCombination($combinationMask)
{
$combination = [];
foreach (str_split($combinationMask) as $key => $indicator) {
if ($indicator) {
$combination[] = $this->arrays[$key];
}
}
return $combination;
}
}
我试图给方法起一个不言自明的名字。一些代码块可以进一步优化(例如,我在同一个数组上多次调用 count
函数;这样做是为了减少变量摆弄)以供生产使用。
基本上逻辑很简单。我们生成数组的所有组合,并根据使用的数组数量对它们进行递减排序。然后我们找到每个组合长度的最长交集。实际上,这是最难的部分。为了获得一个特定的交叉点,我们 return 第一个与阈值匹配的交叉点。
$intersections = new Intersections([$a, $b, $c, $d]);
var_dump($intersections->getAll());
var_dump($intersections->getByThreshold(3));
这里是working demo。
还有其他方法可以找到所有组合,例如one from "PHP Cookbook"。你可以选择你最喜欢的。