Domanda

Non ho un numero imprecisato di array, ciascuno contenente un numero imprecisato di parole. Voglio concatenare i valori da ogni lista in modo che tutte le possibili varianti delle parole che vengono memorizzati in una matrice finale.

Ad esempio, se il vettore contiene 1:

dog
cat

e matrice 2 contiene:

food
tooth

e matrice 3 contiene:

car
bike

Mi piacerebbe l'output di essere:

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

Ci potrebbero essere più di 3 liste, e ogni lista sarà molto probabilmente avere più di 2 parole.

Mi piacerebbe farlo in PHP.

Io so come si fa se so il numero di liste, anche se non è probabilmente il metodo più efficiente delle risorse. Ma foreach nested loop opere se si conosce il numero di array. E se non lo fai? E quali sono alcuni metodi per risolvere questo problema che continuerà a funzionare se, diciamo, ci sono 100 array di 100 parole ciascuno. O 1000?

Grazie!

È stato utile?

Soluzione

È 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 array di uscita 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 ottenere i "sub-concatinations". 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

Altri suggerimenti

È possibile enumerare gli elementi del set di risultati, vale a dire per ogni numero intero compreso tra 0 .... (numero di elementi) -1 si può dire quale elemento per tornare (cioè 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 del n esimo elemento della (naturale ordinato) set. Dal momento che è necessario solo un numero intero per memorizzare lo stato attuale del consumo di memoria non "esplodere" quando si hanno molte grandi array /. Come ha detto Chris nel suo commento, è il commercio di velocità (quando si utilizza più piccole serie) a basso consumo di memoria. (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 Iterator , un SeekableIterator o forse anche un ArrayAccess (e invertendo così 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 ;-))

Non ho ancora testato questo su enormi elenchi di parole, ma è abbastanza veloce su moderatamente dimensioni liste e non usa la ricorsione, che a mio avviso (per favore correggetemi se sbaglio) è probabilmente 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

Il 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();
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top