2010-02-11 10 views
7

Ho un numero sconosciuto di matrici, ognuna contenente un numero sconosciuto di parole. Voglio concatenare i valori da ciascuna lista in modo che tutte le possibili variazioni delle parole siano memorizzate in un array finale.Concatenare i valori di n array in php

Ad esempio, se il vettore contiene 1:

dog 
cat 

e matrice 2 contiene:

food 
tooth 

e matrice 3 contiene:

car 
bike 

Vorrei l'output be:

dog food car 
dog food bike 
dog tooth car 
dog tooth bike 
cat food car 
cat food bike 
cat tooth car 
cat tooth bike 

Possono esserci più di 3 elenchi e molto probabilmente ciascun elenco contiene più di 2 parole.

Mi piacerebbe farlo in PHP.

So come farlo se conosco il numero di elenchi, anche se probabilmente non è il metodo più efficiente in termini di risorse. Ma i cicli nidificati foreach funzionano se si conosce il numero di array. Cosa succede se non lo fai? E quali sono alcuni metodi per risolvere questo problema che funzioneranno ancora se, diciamo, ci sono 100 matrici di 100 parole ciascuna. O 1000?

Grazie!

+0

Vuoi anche * cane *, * gatto *, * cibo per cani *, * dente di cane *, * cibo per gatti *, * dente di gatto * o solo le combinazioni di tutti gli array combinato? – Gordon

+0

Solo la combinazione di tutti gli array per questo particolare problema, anche se sarebbe anche interessante da vedere. – hookedonwinter

+0

Se si desidera poter lavorare con 100 matrici di dimensione 100, si avranno seri problemi di memoria che provano a generare effettivamente un array. La quantità di combinazioni è un numero davvero enorme. Questo è un prodotto cartesiano. È necessario un iteratore. Questo approccio scambia il tempo per lo spazio, è più lento, ma ti consente di rimanere entro i limiti di memoria. Vorrei pubblicare un link, ma sarebbe sul sito web di un membro attivo qui. Se non viene da solo e risponde, posterò il link. Ma altrimenti non voglio rubare la sua gloria. – goat

risposta

9

È possibile inserire tutti gli array di parole in un unico array e utilizzare un ricorsiva funzione come questa:

function concat(array $array) { 
    $current = array_shift($array); 
    if(count($array) > 0) { 
     $results = array(); 
     $temp = concat($array); 
     foreach($current as $word) { 
      foreach($temp as $value) { 
      $results[] = $word . ' ' . $value; 
      } 
     } 
     return $results;   
    } 
    else { 
     return $current; 
    } 
} 

$a = array(array('dog', 'cat'), array('food', 'tooth'), array('car', 'bike')); 

print_r(concat($a)); 

che restituisce:

Array 
(
    [0] => dog food car 
    [1] => dog food bike 
    [2] => dog tooth car 
    [3] => dog tooth bike 
    [4] => cat food car 
    [5] => cat food bike 
    [6] => cat tooth car 
    [7] => cat tooth bike 
) 

Ma credo che questo comporta male per grandi array come l'array di output sarà molto grande.


Per aggirare il problema, è possibile emettere le combinazioni direttamente, utilizzando un approccio simile:

function concat(array $array, $concat = '') { 
    $current = array_shift($array); 

    $current_strings = array(); 

    foreach($current as $word) { 
      $current_strings[] = $concat . ' ' . $word; 
    } 

    if(count($array) > 0) { 
     foreach($current_strings as $string) { 
      concat($array, $string); 
     }  
    } 
    else { 
     foreach($current_strings as $string) { 
      echo $string . PHP_EOL; 
     } 
    } 
} 

concat(array(array('dog', 'cat'), array('food', 'tooth'), array('car', 'bike'))); 

che dà:

dog food car 
dog food bike 
dog tooth car 
dog tooth bike 
cat food car 
cat food bike 
cat tooth car 
cat tooth bike 

Con questo approccio, è anche facile da raggiungere le "subconcentrazioni". Basta inserire echo $string . PHP_EOL; prima concat($array, $string); e l'uscita è:

dog 
dog food 
dog food car 
dog food bike 
dog tooth 
dog tooth car 
dog tooth bike 
cat 
cat food 
cat food car 
cat food bike 
cat tooth 
cat tooth car 
cat tooth bike 
+0

Felix - Funziona alla grande su piccoli array. Ho appena provato su 5 array di lunghezza 100, e ho ottenuto questo: 'Errore fatale: dimensione di memoria consentita di 134217728 byte esauriti (provato ad allocare 11 byte) in/Users/qwerty/- sulla riga 9' - Non so che avrò così tante parole, quindi la tua soluzione probabilmente funzionerà bene per quello che sto facendo. Ma sicuramente ha un certo ritardo sugli array più grandi. Grazie per l'idea! – hookedonwinter

+0

@hookedonwinter: hai provato anche l'approccio iterativo? –

+0

@Felix non ancora. L'ho appena visto. Grazie! – hookedonwinter

2

non ho ancora testato questo su enormi elenchi di parole, ma è abbastanza veloce nelle liste di dimensioni medie e non fa uso di ricorsione, che credo (vi prego di correggermi se sbaglio) probabilmente sta causando i problemi limite di memoria:

$lines = array(''); 

foreach ($arrays as $array) { 

    $old_lines = $lines; 
    $lines = array(); 

    foreach ($array as $word) { 

    foreach ($old_lines as $line) { 

     $lines[] = trim($line .' '. $word); 

    } // foreach 

    } // foreach 

} // foreach 
+0

Immagino che il limite di memoria sia stato causato dal grande array di risultati che sarebbe stato lo stesso nel tuo approccio. Ma stampare la linea non dovrebbe essere un problema. Voglio dire 100^5 elementi nell'array è molto;) –

+0

Funziona benissimo su array più piccoli, non così caldi su quelli grandi. Mi piace comunque! – hookedonwinter

2

mio prendere

class Combinator 
{ 
    protected $words; 
    protected $combinator; 

    public function __construct($words, $combinator = null) 
    { 
     $this->words = $words; 
     $this->combinator = $combinator; 
    } 

    public function run($combo = '') 
    { 
     foreach($this->words as $word) { 
      if($this->combinator !== null) { 
       $this->combinator->run("$combo $word"); 
      } else { 
       echo "$combo $word", PHP_EOL; 
      } 
     } 
    } 
} 

$c = new Combinator(array('dog', 'cat'), 
        new Combinator(array('food', 'tooth'), 
            new Combinator(array('car', 'bike')))); 

$c->run(); 
5

è possibile enumerare gli elementi del r esult set, cioè per ogni numero intero compreso tra 0 .... (numero di elementi) -1 puoi dire quale elemento ritornare (ad es.c'è un ordine naturale). Per il nostro esempio:

0 => array1[0], array2[0], array3[0] 
1 => array1[0], array2[0], array3[1] 
2 => array1[0], array2[1], array3[0] 
7 => array1[1], array2[1], array3[1] 

Tutto ciò che serve è un (intero) Indice n e una funzione che "traduce" l'indice all'elemento esimo del (naturale ordinato) insieme n. Dal momento che è sufficiente un numero intero per memorizzare lo stato corrente, il consumo di memoria non "esplode" quando si dispone di array molti/grandi. Come diceva chris nel suo commento, la velocità di scambio (quando si utilizzano set più piccoli) per un consumo di memoria ridotto. (Anche se penso che -la modo php è attueranno questa è anche una soluzione veloce ragionevole.)

$array1 = array('dog', 'cat'); 
$array2 = array('food', 'tooth'); 
$array3 = array('car', 'bike'); 

function foo($key /* , ... */) { 
    $params = func_get_args(); 
    $rv = array(); 

    $key = array_shift($params); 
    $i=count($params); 

    while(0 < $i--) { 
    array_unshift($rv, $params[$i][ $key % count($params[$i]) ]); 
    $key = (int)($key/count($params[$i])); 
    } 
    return $rv; 
} 

for($i=0; $i<8; $i++) { 
    $a = foo($i, $array1, $array2, $array3); 
    echo join(', ', $a), "\n"; 
} 

È possibile utilizzare questo per implementare esempio un Iterator, un SeekableIterator o forse anche un ArrayAccess (e invertendo il controllo rispetto alle soluzioni ricorsive, quasi come un yield in pitone o rubino)

<?php 
$array1 = array('dog', 'cat', 'mouse', 'bird'); 
$array2 = array('food', 'tooth', 'brush', 'paste'); 
$array3 = array('car', 'bike', 'plane', 'shuttlecraft'); 
$f = new Foo($array1, $array2, $array3); 
foreach($f as $e) { 
    echo join(', ', $e), "\n"; 
} 

class Foo implements Iterator { 
    protected $data = null; 
    protected $limit = null; 
    protected $current = null; 

    public function __construct(/* ... */) { 
    $params = func_get_args(); 
    // add parameter arrays in reverse order so we can use foreach() in current() 
    // could use array_reverse(), but you might want to check is_array() for each element. 
    $this->data = array(); 
    foreach($params as $p) { 
     // <-- add: test is_array() for each $p --> 
     array_unshift($this->data, $p); 
    } 
    $this->current = 0; 
    // there are |arr1|*|arr2|...*|arrN| elements in the result set 
    $this->limit = array_product(array_map('count', $params)); 
    } 

    public function current() { 
    /* this works like a baseX->baseY converter (e.g. dechex()) 
     the only difference is that each "position" has its own number of elements/"digits" 
    */ 
    // <-- add: test this->valid() --> 
    $rv = array(); 
    $key = $this->current; 
    foreach($this->data as $e) { 
     array_unshift($rv, $e[$key % count($e)]); 
     $key = (int)($key/count($e)); 
    } 
    return $rv; 
    } 

    public function key() { return $this->current; } 
    public function next() { ++$this->current; } 
    public function rewind() { $this->current = 0; } 
    public function valid() { return $this->current < $this->limit; } 
} 

stampe

dog, food, car 
dog, food, bike 
dog, food, plane 
dog, food, shuttlecraft 
dog, tooth, car 
dog, tooth, bike 
[...] 
bird, paste, bike 
bird, paste, plane 
bird, paste, shuttlecraft 

(la sequenza sembra essere ok ;-))

Problemi correlati