获取可能的数组组合
Posted
技术标签:
【中文标题】获取可能的数组组合【英文标题】:Get possible array combinations 【发布时间】:2013-09-28 11:02:10 【问题描述】:所以,
问题
从 SQL 我得到一个带有字符串的数组(平面数组)——让它成为现实
$rgData = ['foo', 'bar', 'baz', 'bee', 'feo'];现在,我想得到这个数组的对和三元组的可能组合(以及,在常见情况下,4 个元素的组合等)。更具体地说:我的意思是 combinations 在数学意义上(没有重复),即那些计数等于
-so 对于上面的数组,对和三胞胎都是 10。
我的方法
我已经开始将 的可能值映射到可能的数组选定项。我当前的解决方案是指出一个元素是否被选为“1”,否则为“0”。对于上面的示例,它将是:
foo bar baz 蜜蜂 feo 0 0 1 1 1 -> [baz,蜜蜂,feo] 0 1 0 1 1 -> [酒吧,蜜蜂,feo] 0 1 1 0 1 -> [bar, baz, feo] 0 1 1 1 0 -> [酒吧,巴兹,蜜蜂] 1 0 0 1 1 -> [foo, 蜜蜂, feo] 1 0 1 0 1 -> [foo, baz, feo] 1 0 1 1 0 -> [foo, baz, 蜜蜂] 1 1 0 0 1 -> [foo, baz, feo] 1 1 0 1 0 -> [foo,bar,bee] 1 1 1 0 0 -> [foo, bar, baz]我需要做的就是以某种方式生成所需的位集。这是我在 php 中的代码:
function nextAssoc($sAssoc)
if(false !== ($iPos = strrpos($sAssoc, '01')))
$sAssoc[$iPos] = '1';
$sAssoc[$iPos+1] = '0';
return substr($sAssoc, 0, $iPos+2).
str_repeat('0', substr_count(substr($sAssoc, $iPos+2), '0')).
str_repeat('1', substr_count(substr($sAssoc, $iPos+2), '1'));
return false;
function getAssoc(array $rgData, $iCount=2)
if(count($rgData)<$iCount)
return null;
$sAssoc = str_repeat('0', count($rgData)-$iCount).str_repeat('1', $iCount);
$rgResult = [];
do
$rgResult[]=array_intersect_key($rgData, array_filter(str_split($sAssoc)));
while($sAssoc=nextAssoc($sAssoc));
return $rgResult;
-我选择将我的位存储为普通字符串。我产生下一个关联的算法是:
-
尝试查找“01”。如果没有找到,那么它是 11..100..0 的情况(所以它是最大的,找不到更多的)。如果找到,请转到第二步
转到字符串中“01”的最右边位置。将其切换到“10”,然后将所有比找到的“01”位置更右侧的零移动到左侧。
比如
01110
:“01”最右边的位置是0,所以我们先把这个“01”换成“10”。字符串现在仍然是10110
。现在,转到右边部分(它没有10
部分,所以它从 0+2=2-nd 符号开始),并将所有零向左移动,即 110
将是 011
。因此,我们将 10
+011
=10111
作为 01110
的下一个关联。
我发现了类似的问题here - 但是 OP 想要有重复的组合,而我希望它们没有重复。
问题
我的问题是关于两点:
对于我的解决方案,是否有另一种方法可以更有效地生成下一个位集? 可能有更简单的解决方案吗?这似乎是标准问题。【问题讨论】:
Algorithm to return all combinations of k elements from n的可能重复 哇,这看起来很有趣,谢谢@ElKamina 我认为我的回答很好,在干净的 PHP 代码和速度之间取得了很好的平衡。你只是忽略了它吗? 【参考方案1】:很抱歉没有提供 PHP 解决方案,因为我已经很长时间没有使用 PHP 编程了,但让我向您展示一个快速的 Scala 解决方案。也许它会激励你:
val array = Vector("foo", "bar", "baz", "bee", "feo")
for (i <- 0 until array.size;
j <- i + 1 until array.size;
k <- j + 1 until array.size)
yield (array(i), array(j), array(k))
结果:
Vector((foo,bar,baz), (foo,bar,bee), (foo,bar,feo), (foo,baz,bee), (foo,baz,feo), (foo,bee,feo), (bar,baz,bee), (bar,baz,feo), (bar,bee,feo), (baz,bee,feo))
生成k-组合的通用代码:
def combinations(array: Vector[String], k: Int, start: Int = 0): Iterable[List[String]] =
if (k == 1 || start == array.length)
for (i <- start until array.length) yield List(array(i))
else
for (i <- start until array.length; c <- combinations(array, k - 1, i + 1)) yield array(i) :: c
结果:
scala> combinations(Vector("a", "b", "c", "d", "e"), 1)
res8: Iterable[List[String]] = Vector(List(a), List(b), List(c), List(d), List(e))
scala> combinations(Vector("a", "b", "c", "d", "e"), 2)
res9: Iterable[List[String]] = Vector(List(a, b), List(a, c), List(a, d), List(a, e), List(b, c), List(b, d), List(b, e), List(c, d), List(c, e), List(d, e))
scala> combinations(Vector("a", "b", "c", "d", "e"), 3)
res10: Iterable[List[String]] = Vector(List(a, b, c), List(a, b, d), List(a, b, e), List(a, c, d), List(a, c, e), List(a, d, e), List(b, c, d), List(b, c, e), List(b, d, e), List(c, d, e))
scala> combinations(Vector("a", "b", "c", "d", "e"), 4)
res11: Iterable[List[String]] = Vector(List(a, b, c, d), List(a, b, c, e), List(a, b, d, e), List(a, c, d, e), List(b, c, d, e))
scala> combinations(Vector("a", "b", "c", "d", "e"), 5)
res12: Iterable[List[String]] = Vector(List(a, b, c, d, e))
当然,真正的 Scala 代码在接受的元素类型和集合类型方面应该更加通用,但我只是想展示基本思想,而不是最漂亮的 Scala 代码。
【讨论】:
如果我想获得 4 个元素的组合怎么办?还是5?如果不修改您的代码,我将无法做到这一点。常见问题是从N
获取K
好吧,我误解了你只需要对和三对。获得任何 k 组合并不难,但您需要使用递归。
你能推荐你的变种吗? (带递归)
谢谢,@Piotr - 我知道这是一个很好的建议。现在,我在问题上方的链接中找到了答案【参考方案2】:
这是一个递归解决方案:
function subcombi($arr, $arr_size, $count)
$combi_arr = array();
if ($count > 1)
for ($i = $count - 1; $i < $arr_size; $i++)
$highest_index_elem_arr = array($i => $arr[$i]);
foreach (subcombi($arr, $i, $count - 1) as $subcombi_arr)
$combi_arr[] = $subcombi_arr + $highest_index_elem_arr;
else
for ($i = $count - 1; $i < $arr_size; $i++)
$combi_arr[] = array($i => $arr[$i]);
return $combi_arr;
function combinations($arr, $count)
if ( !(0 <= $count && $count <= count($arr)))
return false;
return $count ? subcombi($arr, count($arr), $count) : array();
$input_arr = array('foo', 'bar', 'baz', 'bee', 'feo');
$combi_arr = combinations($input_arr, 3);
var_export($combi_arr); echo ";\n";
OUTPUT:
array (
0 =>
array (
0 => 'foo',
1 => 'bar',
2 => 'baz',
),
1 =>
array (
0 => 'foo',
1 => 'bar',
3 => 'bee',
),
2 =>
array (
0 => 'foo',
2 => 'baz',
3 => 'bee',
),
3 =>
array (
1 => 'bar',
2 => 'baz',
3 => 'bee',
),
4 =>
array (
0 => 'foo',
1 => 'bar',
4 => 'feo',
),
5 =>
array (
0 => 'foo',
2 => 'baz',
4 => 'feo',
),
6 =>
array (
1 => 'bar',
2 => 'baz',
4 => 'feo',
),
7 =>
array (
0 => 'foo',
3 => 'bee',
4 => 'feo',
),
8 =>
array (
1 => 'bar',
3 => 'bee',
4 => 'feo',
),
9 =>
array (
2 => 'baz',
3 => 'bee',
4 => 'feo',
),
);
递归是基于这样一个事实,即要从n
($arr_size
) 中获取k
($count
) 元素的所有组合,对于从零开始的最高索引@ 的所有可能选择,您必须987654327@,从剩余的i
元素中找出所有k-1
元素的“子组合”,索引低于i
。
为了利用 PHP 的“延迟复制”机制,将数组传递给递归调用时,该数组不是 array_slice
d。这样就不会发生真正的复制,因为数组没有被修改。
保留数组索引非常适合调试目的,但这不是必需的。令人惊讶的是,简单地删除 $i =>
部分并将数组 +
替换为 array_merge
会导致相当大的减速。要获得比原始版本稍快的速度,您必须这样做:
function subcombi($arr, $arr_size, $count)
$combi_arr = array();
if ($count > 1)
for ($i = $count - 1; $i < $arr_size; $i++)
$highest_index_elem = $arr[$i];
foreach (subcombi($arr, $i, $count - 1) as $subcombi_arr)
$subcombi_arr[] = $highest_index_elem;
$combi_arr[] = $subcombi_arr;
else
for ($i = $count - 1; $i < $arr_size; $i++)
$combi_arr[] = array($arr[$i]);
return $combi_arr;
关于问题的第一部分,您应该避免多次计算相同的数量,并且应该尽量减少函数调用。例如,像这样:
function nextAssoc($sAssoc)
if (false !== ($iPos = strrpos($sAssoc, '01')))
$sAssoc[$iPos] = '1';
$sAssoc[$iPos+1] = '0';
$tailPos = $iPos+2;
$n0 = substr_count($sAssoc, '0', $tailPos);
$n1 = strlen($sAssoc) - $tailPos - $n0;
return substr($sAssoc, 0, $tailPos).str_repeat('0', $n0)
.str_repeat('1', $n1);
return false;
如果不彻底改变代码,很难对代码进行更深入的更改。不过还不错,因为在我的测试中,它的速度大约是我的递归解决方案的一半(即,时间大约是两倍)
【讨论】:
嗨,沃尔特,现在我看了一下。由于我的原始解决方案是“最小建设性”解决方案(即我正在构建精确的二进制投影并且没有过多) - 它只能在语言/表达级别上进行改进(正如我在您的解决方案中看到的那样)。所以两种解决方案都有相同的大 O 估计,但是,你可以有更好的前导常数。谢谢你。另外 - 我记得,PHP 默认情况下仅通过引用传递对象,因此在函数中接受数组作为引用可能会很好,以防止复制到本地函数堆栈。 @AlmaDoMundo:我担心大 O 再好不过了。关于数组,正如我在回答中解释的那样,它们是被复制的,但是该副本是“惰性副本”(请参阅en.wikipedia.org/wiki/Object_copy#Lazy_copy),因此只要它们没有被写入就不需要通过引用传递它们,实际上最好按值传递。 我知道 - 由于我们都有最小的建设性解决方案,算法本身无法改进。我知道“惰性复制”(但我认为将其命名为“写入时复制”更正确——就像在 PHP 中一样)。我不确定是否要处理到本地堆栈的通道 - 如果它也没有被复制,那么你是对的 - 不需要通过引用传递。 @AlmaDoMundo:是的,堆栈上的副本以及对返回值的副本的行为就像任何赋值副本一样。无论如何,我测量了有和没有&
的时间,我发现没有可测量的差异。
是的 - 我也是(即发现与测试没有区别)【参考方案3】:
我刚刚尝试在不使用 go 语言的递归的情况下以最小的时间复杂度解决这个问题。
我见过一些解决方案,但都是使用递归函数。避免递归解决堆栈大小超出错误。
package main
import "fmt"
func main()
// Arguments
arr := []string"foo", "bar", "baz", "bee", "feo", "boo", "bak"
combinations := make([][]string, 0)
k := 4
n := len(arr)
// Execution starts from here
if k > n
panic("invalid requirement")
pos := make([]int, k) // this variable is used to plot the unique combination of elements
// initialize an array with first ever plotting possitions
i := 0
c := k
for c > 0
c--
pos[i] = c
i++
combinations = append(combinations, getCombination(arr, pos, k))
// Let's begin the work
x := 0
ctr := 1 // counter is use to calculate total iterations
for pos[x] < n-(x+1)
ctr++
pos[x]++
combinations = append(combinations, getCombination(arr, pos, k))
if pos[x] == n-(x+1) && x+1 < k
x++
i := x
s := pos[x] + 1
for i > 0
i--
s++
pos[i] = s
// continue to next index
continue
x = 0
fmt.Println("total # iterations: --> ", ctr)
fmt.Println(combinations, "\ntotal # combinations: ", len(combinations))
func getCombination(arr []string, pos []int, k int) []string
combination := make([]string, k)
for i, j := k-1, 0; i >= 0; i, j = i-1, j+1
combination[j] = arr[pos[i]]
return combination
工作示例在这里https://play.golang.org/p/D6I5aq8685-
【讨论】:
以上是关于获取可能的数组组合的主要内容,如果未能解决你的问题,请参考以下文章