Değerlerin birden listelerinden tüm değerleri olmayan çakışan kombinasyonları bulma

7 Cevap php

I değerleri içeren diziler aşağıdaki dizi vardır:

$array = array(
    array('1', '2'),
    array('a', 'b', 'c'),
    array('x', 'y'),
);

Orada dizilerin herhangi bir sayı olabilir ve bir dizi değerleri herhangi bir sayı içerebilir. Ben şu anda bir değer her diziden alınan tüm kombinasyonlar üretecektir kodu bir parça var. örneğin:

1ax, 1ay, 1bx, 1by, 1cx, 1cy, 2ax, 2ay, 2bx, 2by, 2cx, 2cy

Ancak ne ben aslında istediğim tek bir değeri, her sütunda, yani oturur sadece kombinasyonlar olduğunu. B ve y ikinci sütunda oturup çünkü tüm üç değer 1, a ve x ilk sütunda oturup çünkü 1AX hiçbir iyi, 1by hiçbir iyidir. Bu nedenle, yukarıdaki örnekte sadece bu kombinasyonlar geçerli olacaktır:

1cy, 2cx

Ben aslında sadece tüm kombinasyonları oluşturmak ve daha sonra çatışmalar ile olanları filtrelemek için planlanan, fakat bu basitleştirilmiş örnekte olduğu gibi o gerçek bir uygulamada çelişkili olanlar da dahil olmak kombinasyonları (milyonlarca potansiyel vardır durumlar olacaksa olacak, ölçek değil .)

Herkes bu çözmek için daha iyi bir şekilde yardımcı olabilir misiniz? PHP çalışıyorum, ama açıkça mantığı gösteren herhangi bir kod örneği yararlı olacaktır.

Şimdiden teşekkürler.


Update:

Ben daha büyük bir veri kümesi karşı çalışmak çözümleri test ettik, bazı kriterler olsun, bu sonuçlar çok uzak:

$array = array(
    array('1', '2', '3', '1', '2', '3', '1', '2', '3', '1', '2', '3', '1', '2', '3'),
    array('a', 'b', 'c', 'd', 'a', 'b', 'c', 'd', 'a', 'b', 'c', 'd', 'a', 'b', 'c', 'd', 'a', 'b', 'c', 'd'),
    array('x', 'y', 'z', 'x', 'y', 'z', 'x', 'y', 'z'),
    array('1', '2', '3', '1', '2', '3', '1', '2', '3'),
    array('a', 'b', 'c', 'd', 'a', 'b', 'c', 'd', 'a', 'b', 'c', 'd'),
    array('x', 'y', 'z'),
);

Josh Davis 2nd Solution:

Combinations:      249480
Time:              0.3180251121521 secs
Memory Usage:      22.012168884277 mb
Peak Memory Usage: 22.03059387207 mb

Josh Davis:

Combinations:      249480
Time:              1.1172790527344 secs
Memory Usage:      22.004837036133 mb
Peak Memory Usage: 22.017387390137 mb

Tom Haigh:

Combinations:      249480
Time:              5.7098741531372 secs
Memory Usage:      39.145843505859 mb
Peak Memory Usage: 39.145843505859 mb

7 Cevap

Bu kendi kendine üretilen kod ve kaba kuvvet basitlik ve performansı üzerinde en algoritmalar yenecek bu durumlardan biridir. Gerçekte ne yapmak istediğiniz zaman önceki cevaplarda özyineleme sürü dizi manipülasyon ve hesaplamaları, gördüm:

foreach ($array[0] as $k0 => $v0)
{
    foreach ($array[1] as $k1 => $v1)
    {
        if ($k1 == $k0)
        {
            continue;
        }
        foreach ($array[2] as $k2 => $v2)
        {
            if ($k2 == $k1 || $k2 == $k0)
            {
                continue;
            }
            $result[] = $v0.$v1.$v2;
        }
    }
}

Eğer $array içinde kaç diziler bilmedikçe Tabii ki, bu yazamıyor. Üretilen kod kullanışlı buradan geliyor:

$array = array(
    array('1', '2'),
    array('a', 'b', 'c'),
    array('x', 'y')
);
$result = array();

$php = '';
foreach ($array as $i => $arr)
{
    $php .= 'foreach ($array[' . $i . '] as $k' . $i . ' => $v' . $i . '){';

    if ($i > 0)
    {
        $sep  = 'if (';
        $j    = $i - 1;
        do
        {
            $php .= $sep . '$k' . $i . ' == $k' . $j;
            $sep  = ' || ';
        }
        while (--$j >= 0);

        $php .= ') { continue; } ';
    }
}

$php .= '$result[] = $v' . implode('.$v', array_keys($array)) . ';' . str_repeat('}', count($array));

eval($php);
print_r($result);

Bu rutin $array sizin örnekte olduğu gibi sıfır tabanlı bir sayısal indisli bir dizi olduğunu varsayar. Bu diziler keyfi bir sayı için ayarlanmış yukarıda alıntılanan kodu üretecektir.


Update

İşte alternatif bir algoritma var. Hala kendi ürettiği, ama daha az bruteforce bulunuyor. Biz yine de her döngü şu anda dış döngüler tarafından kullanılan anahtarlar bu döngünün diziden çıkarıldı dizinin bir kopyası üzerinde çalışır dışında, döngüler iç içe. Değerleri (a, b, c) olmalı ama dış döngüler endeksleri 0 ve 2 kullanıyorsanız Örneğin, biz "bir" (indeks 0) ve "c" (index 2) çıkarın ve biz kalan tek şey "b". Bu döngüler olası kombinasyonları sadece çalışmak ve biz artık bir if koşul gerekmez anlamına gelir.

Biz kullanılan akım endeksleri dizisinde mevcut olduğunu garanti o kadar Ayrıca ve bu bölüm de, önceki algoritma uygulanabilir, en büyük en küçük sırayla değerlerin dizileri işlem. Dezavantajı aynı sırayla kombinasyonları oluşturmak değildir değildir. Bu sadece değil, aynı sırayla, aynı kombinasyonu oluşturur. Kod şöyle görünür:

$a0 = $array[0];
foreach ($a0 as $k0 => $v0)
{
    $a2 = $array[2];
    unset($a2[$k0]);
    foreach ($a2 as $k2 => $v2)
    {
        $a1 = $array[1];
        unset($a1[$k0], $a1[$k2]);
        foreach ($a1 as $k1 => $v1)
        {
            $result[] = "$v0$v1$v2";
        }
    }
}

Yukarıdaki rutin dış döngü tarafından kullanılan değerleri kaldırır, daha sonra her bir döngü başlangıcında değerlerinin bir kopyası kurar. Sen, başında değerleri only once bir kopyasını kurarak bu süreci geliştirmek kullanıldığı gibi (her döngünün başında) anahtarları kaldırmak ve onlar da (kurtulmuş olarak onları geri koyabilirsiniz Her döngünün sonu). Rutin sonra bu gibi görünüyor:

list($a0,$a1,$a2) = $array;
foreach ($a0 as $k0 => $v0)
{
    unset($a1[$k0], $a2[$k0]);
    foreach ($a2 as $k2 => $v2)
    {
        unset($a1[$k2]);
        foreach ($a1 as $k1 => $v1)
        {
            $result[] = "$v0$v1$v2";
        }
        $a1[$k2] = $array[1][$k2];
    }
    $a1[$k0] = $array[1][$k0];
    $a2[$k0] = $array[2][$k0];
}

Yukarıda kaynağını oluşturur gerçek kod:

$keys = array_map('count', $array);
arsort($keys);

$inner_keys = array();
foreach ($keys as $k => $cnt)
{
    $keys[$k] = $inner_keys;
    $inner_keys[] = $k;
}

$result = array();

$php = 'list($a' . implode(',$a', array_keys($array)) . ')=$array;';
foreach (array_reverse($keys, true) as $i => $inner_keys)
{
    $php .= 'foreach ($a' . $i . ' as $k' . $i . ' => $v' . $i . '){';

    if ($inner_keys)
    {
        $php .= 'unset($a' . implode('[$k' . $i . '],$a', $inner_keys) . '[$k' . $i . ']);';
    }
}

$php .= '$result[] = "$v' . implode('$v', array_keys($array)) . '";';

foreach ($keys as $i => $inner_keys)
{
    foreach ($inner_keys as $j)
    {
        $php .= '$a' . $j . '[$k' . $i . ']=$array[' . $j . '][$k' . $i . "];\n";
    }
    $php .= '}';
}
eval($php);

Ilginç bir sorun! Bu düşündüğümden daha karmaşık olduğu ortaya çıktı, ama o iş gibi görünüyor.

Temel strateji ilk sipariş için diziler küçük büyük (I çıkışı doğru sırayla cevaplar olabilir onlar vardı ne sipariş takip, bu yüzden).

Ben giriş listeleri bu sıralanan diziye endekslerin bir dizi şeklinde cevaplar tutun.

Şimdi listeler sıralanır ki, ben dizi (0,1,2, ..., n) olarak ilk doğru cevabı saklayabilirsiniz;

Sonra bu cevap dizide (bu yuvaya için çok büyük değil tüm olanlar) diğer değerleri ile takas yoluyla (0 yukarıda) orada ilk yuvaya bütün değerleri çalıştığınız için bir işlev özyineleme. Ben büyüklüğüne göre sıralanır var ettik bu yana, ben o doğru yuvası için büyük olma endişesi olmadan, ben takas ediyorum sağa herhangi bir değer taşıyabilir.

Her geçerli yuvası çıktısı tüm sıralamayı geri almak için bazı çılgın indirection vardır.

Bu kafa karıştırıcı görünüyor özür dilerim. Ben o kadar temizlik çok zaman harcamak yoktu.

<?php
# $lists is an array of arrays
function noconfcombos($lists) {
    $lengths = array();
    foreach($lists as $list) {
        $lengths[] = count($list);
    }

    # find one solution (and make sure there is one)
    $answer = array();
    $sorted_lengths = $lengths;
    asort($sorted_lengths);
    $answer_order_lists = array();
    $answer_order_lengths = array();
    $output_order = array();
    $min = 1;
    $max_list_length = 0;
    foreach($sorted_lengths as $lists_key => $list_max) {
        if($list_max < $min) {
            # no possible combos
            return array();
        }
        $answer[] = $min - 1; # min-1 is lowest possible value (handing out colums starting with smallest rows)
        $output_order[$lists_key] = $min - 1; # min-1 is which slot in $answers corresponds to this list
        $answer_order_lists[] = $lists[$lists_key];
        $answer_order_lengths[] = $lengths[$lists_key];
        ++$min;
    }
    ksort($output_order);
    $number_of_lists = count($lists);
    $max_list_length = end($sorted_lengths);
    if($max_list_length > $number_of_lists) {
       for($i = $number_of_lists; $i < $max_list_length; ++$i) {
          $answer[] = $i;
       }
       $stop_at = $number_of_lists;
    } else {
       $stop_at = $number_of_lists - 1;
    }

    # now $answer is valid (it has the keys into the arrays in $list for the
    # answer), and we can find the others by swapping around the values in
    # $answer.

    $ret = array();
    $ret[] = noconfcombos_convert($answer, $answer_order_lists, $output_order);
    noconfcombos_recurse($ret, $max_list_length, $stop_at, $answer_order_lengths, $answer_order_lists, $output_order, $answer, 0);

    return $ret;
}

# try swapping in different indexes into position $index, from the positions
# higher, then recurse
function noconfcombos_recurse(&$ret, $max_list_length, $stop_at, &$lengths, &$lists, &$output_order, $answer, $index) {
    if($index < $stop_at) {
        noconfcombos_recurse($ret, $max_list_length, $stop_at, $lengths, $lists, $output_order, $answer, $index + 1);
    }
    for($other = $index + 1; $other < $max_list_length; ++$other) {
        if($answer[$other] < $lengths[$index]) { # && $answer[$index] < $lengths[$other]) {
            $tmp = $answer[$index];
            $answer[$index] = $answer[$other];
            $answer[$other] = $tmp;
            $ret[] = noconfcombos_convert($answer, $lists, $output_order);
            if($index < $stop_at) {
                noconfcombos_recurse($ret, $max_list_length, $stop_at, $lengths, $lists, $output_order, $answer, $index + 1);
            }
        }
    }
}


function noconfcombos_convert(&$indexes, &$lists, &$order) {
    $ret = '';
    foreach($order as $i) {
        $ret .= $lists[$i][$indexes[$i]];
    }
    return $ret;
}

function noconfcombos_test() {
    $a = array('1', '2', '3', '4');
    $b = array('a', 'b', 'c', 'd', 'e');
    $c = array('x', 'y', 'z');
    $all = array($a, $b, $c);
    print_r(noconfcombos($all));
}

noconfcombos_test();

Ben bu işleri düşünüyorum. Bir ağaç gibi yapı üzerinde gitmek için özyineleme kullanıyor. Her şube için bu sütunlar zaten alındığı izler. Bir kaba kuvvet yaklaşımdır çünkü muhtemelen yavaş.

<?php 

$array = array(
    array('1', '2'),
    array('a', 'b', 'c'),
    array('x', 'y'),
);


function f($array, & $result, $colsToIgnore = array(), $currentPath = array()) {
    $row = array_shift($array);
    $length = count($row);
    $isMoreRows = !! count($array);

    for ($col = 0; $col < $length; $col++) {
        //check whether column has already been used
        if (in_array($col, $colsToIgnore)) {
            continue;   
        }

        $item = $row[$col];

        $tmpPath = $currentPath;
        $tmpPath[] = $item;

        if ($isMoreRows) {
            $tmpIgnoreCols = $colsToIgnore;
            $tmpIgnoreCols[] = $col;
            f($array, $result, $tmpIgnoreCols, $tmpPath);
        } else {
            $result[] = implode('', $tmpPath);
        }

    }
}


$result = array();
f($array, $result);
print_r($result);

Bu dizilerin herhangi bir keyfi miktarı ile iş yapma özyineleme kullanarak refactored edilebilir. Ben vakit bulursam, ben bir kendimi denemek vereceğim.

ps. I don't know php, the example is written in Delphi.

Keyfi # dizilerle Edit: özyinelemeli çözüm

type
  TSingleArray = array of string;
  TMasterArray = array of TSingleArray;
var
  solutions: array of integer; // Q&D container to hold currently used indexes of SingleArrays


procedure WriteSolution(const masterArray: TMasterArray);
var
  I: Integer;
  indexes: string;
  solution: string;
begin
  for I := 0 to High(solutions) do
  begin
    indexes := indexes + IntToStr(solutions[I]) + ' ';
    solution := solution + masterArray[I][solutions[I]];
  end;
  Writeln('Solution: ' + solution + ' Using indexes: ' + indexes);
end;

procedure FindSolution(const masterArray: TMasterArray; const singleArrayIndex: Integer; var bits: Integer);
var
  I: Integer;
begin
  for I := 0 to High(masterArray[singleArrayIndex]) do
  begin
    //***** Use bit manipulation to check if current index is already in use
    if bits and (1 shl I)  = (1 shl I ) then continue;
    solutions[singleArrayIndex] := I;
    Inc(bits, 1 shl I);
    //***** If it is not the last array in our masterArray, continue by calling RecursArrays recursivly.
    if singleArrayIndex <> High(masterArray) then FindSolution(masterArray, Succ(singleArrayIndex), bits)
    else WriteSolution(masterArray);
    Dec(bits, 1 shl I);
  end;
end;

//***************
// Initialization
//***************
var
  I, J: Integer;
  bits: Integer;
  singleArrayString: string;
  masterArray: TMasterArray;
begin
  I := 10;
  SetLength(masterArray, I);
  for I := 0 to High(masterArray) do
  begin
    SetLength(masterArray[I], High(masterArray) + Succ(I));
    singleArrayString := EmptyStr;
    for J := 0 to High(masterArray[I]) do
    begin
      masterArray[I][J] := IntToStr(J);
      singleArrayString := singleArrayString + masterArray[I][J];
    end;
    WriteLn(singleArrayString)
  end;
  ReadLn;

  //****** Start solving the problem using recursion
  bits := 0;
  SetLength(solutions, Succ(High(masterArray)));
  FindSolution(masterArray, 0, bits);    
end.

Farklı bir açıdan bakmak: Sonuç satırını oluşturmak amacıyla, her sütun için bir değer almak gerekir. Her değer, farklı bir kaynak satırdan aldı edilmelidir. Sorun "M dışarı N seçmek", ya da daha matematiksel, bir Combination olarak biliyorum.

Bu bir sonuç satır kaynak satırın endeksi bir dizi tekabül demektir.

Böyle bir dizin-dizi (pseudo-code) inşa etmek için başlayan bütün olası avanta inşa edebilirsiniz

function combinations( $source ) {
  if( count( $source ) == 0 ) return $source;
  $result=array();
  // build one row
  foreach( $source as $index=>$value ) {
    $newsource = array_splice( $source, $index, 1 );

    $reduced_combinations=combinations( $newsource  );
    foreach( $reduced_combinations as $reduced_combi ) {
      $newrow=array_unshift( $reduced_combi, $value );
      $result[]=$newrow;
    }

  }
  return $result;
}

function retrieve_indices( $indices, $arrays ) {
   $result=array();
   foreach( $indices as $column=>$index ) {
     $result[]=$arrays[$index][$column];
   }
   return $result;
}

$source_arrays = array(
  array( "1", "2", "3" ),
  array( "a", "b", "c" ),
  array( "x", "y", "z" )
);

$index_combinations = combinations( range(0,2) );
$result=array();
foreach( $index_combinations as $combination ) {
  $result[]=retrieve_indices( $combination, $source_arrays );
}

Başka bir seçenek:

    $arr = array(
        array('1', '2'),
        array('a', 'b', 'c'),
        array('x', 'y'),
    );
    //-----
    //assuming $arr consists of non empty sub-arrays
    function array_combinations($arr){ 
        $max = 1;
        for ($i = 0; $i < count($arr); $i ++){
            $max *= count($arr[$i]); 
        }
        $matrix = array();
        for ($i = 0; $i < $max; $i ++){
            $matrix = array(); 
        }
        $c_rep = 1;
        for ($i = count($arr) - 1; $i >= 0; $i --){
            $c_rep *= ($i < count($arr) - 1)//last sub-array 
                ? count($arr[$i + 1])
                : 1;
            $k = 0; 
            while ($k < $max){
                for ($t = 0; $t < count($arr[$i]); $t ++){
                    for ($j = 0; $j < $c_rep; $j ++){
                        $matrix[$i][$k ++] = $arr[$i][$t];
                    }
                }   
            }
        }
        return $matrix;
    }
    //-----
    $matrix = array_combinations($arr);

İşletme problem finding a determinant of a matrix buna benzer. Iyi yolu imho, bazı sembolü ile daha küçük diziler doldurmak için olması '0 'demek, bu yüzden hepsi senin örnekte, değerlerin eşit sayıda olurdu

$array = array(
    array('1', '2', '0'),
    array('a', 'b', 'c'),
    array('x', 'y', '0'),
);

sonra ilk dizi değerlerin her yoluyla ve her artış için 1 dizinin indeksi ve sonraki dizi ve sonraki sütunu (ilk döngü o '1 'olacak ve indeksi kontrol döngü artırılır 0 olacak - 1, daha sonra almak $ dizi 1 -. böylece 'b' ve) Eğer '0 'ulaşmak eğer doğru sınır ulaşması halinde, break, eksiltilmesi ile aynı şeyi Sonra 0'a ilk dizin sıfırlamak ve olacak bütün kombinasyonlarını içerir. Muhtemelen belli değil, ben bağlantılı görüntüyü kontrol