【问题标题】: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;
}

-我选择将我的位存储为普通字符串。我产生下一个关联的算法是:

  1. 尝试查找“01”。如果没有找到,那么它是 11..100..0 的情况(所以它是最大的,找不到更多的)。如果找到,请转到第二步
  2. 转到字符串中“01”的最右边位置。将其切换到“10”,然后将所有比找到的“01”位置更右侧的零移动到左侧。 比如01110:“01”最右边的位置是0,所以我们先把这个“01”换成“10”。字符串现在仍然是10110。现在,转到右边部分(它没有10 部分,所以它从 0+2=2-nd 符号开始),并将所有零向左移动,即 110 将是 011。因此,我们将 10+011=10111 作为 01110 的下一个关联。

我发现了类似的问题here - 但是 OP 想要有重复的组合,而我希望它们没有重复。

问题

我的问题是关于两点:

  • 对于我的解决方案,是否有另一种方法可以更有效地生成下一个位集?
  • 可能有更简单的解决方案吗?这似乎是标准问题。

【问题讨论】:

标签: php arrays algorithm combinations


【解决方案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_sliced。这样就不会发生真正的复制,因为数组没有被修改。

保留数组索引非常适合调试目的,但这不是必需的。令人惊讶的是,简单地删除 $i =&gt; 部分并将数组 + 替换为 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:是的,堆栈上的副本以及对返回值的副本的行为就像任何赋值副本一样。无论如何,我测量了有和没有&amp; 的时间,我发现没有可测量的差异。
  • 是的 - 我也是(即发现与测试没有区别)
【解决方案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-

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-03-20
    • 2013-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多