6

所以,

问题

从 SQL 我得到一个带有字符串的数组(平面数组) - 让它成为

$rgData = ['foo', 'bar', 'baz', 'bee', 'feo'];

现在,我想获得这个数组的对和三元组的可能组合(以及,在常见情况下,4 个元素的组合)。更具体地说:我的意思是数学意义上的组合(没有重复),即那些计数等于

在此处输入图像描述

- 所以对于上面的数组,对和三胞胎都是 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 符号开始),并将所有零向左移动,即 110will be 011。结果,我们将10+ 011=10111作为 的下一个关联01110

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

问题

我的问题是关于两点:

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

3 回答 3

1

这是一个递归解决方案:

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',
  ),
);

递归基于这样一个事实,即要从 ( ) 中获取 ( ) 元素的所有组合,您k必须$count对于从零开始的最高索引的所有可能选择,从索引较低的剩余元素中找到元素的所有“子组合”比。n$arr_sizeik-1ii

数组在传递给递归调用时不是array_sliced,以便利用 PHP 的“延迟复制”机制。这样就不会发生真正的复制,因为数组没有被修改。

保存数组索引非常适合调试目的,但这不是必需的。令人惊讶的是,简单地移除部件并用一个$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;
}

如果不彻底改变代码,就很难对代码进行更深入的更改。不过还不错,因为在我的测试中,它的速度大约是我的递归解决方案的一半(即,时间大约是两倍)

于 2013-10-24T21:39:21.150 回答
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 代码。

于 2013-09-23T16:23:11.953 回答
1

我刚刚尝试以最小的时间复杂度解决这个问题,并且不使用 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-

于 2019-07-17T22:25:38.390 回答