Свяжите значения массивов n в php

У меня есть неизвестное количество массивов, каждый содержащий неизвестное количество слов. Я хочу связать значения из каждого списка так, чтобы все возможные изменения слов были сохранены к заключительному массиву.

Например, если массив 1 содержит:

dog
cat

и массив 2 содержит:

food
tooth

и массив 3 содержит:

car
bike

Я хотел бы, чтобы вывод был:

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

Могло быть больше чем 3 списка, и каждый список будет, скорее всего, иметь больше чем 2 слова.

Я хотел бы сделать это в PHP.

Я знаю, как сделать это, если я знаю количество списков, хотя это - вероятно, не самый эффективный с точки зрения ресурсов метод. Но вложенный foreach циклы работают, если Вы знаете количество массивов. Что, если Вы не делаете? И что является некоторыми методами для решения этой проблемы, которая будет все еще работать, если, скажем, будет 100 массивов 100 слов каждый. Или 1000?

Спасибо!

7
задан VolkerK 12 February 2010 в 07:55
поделиться

4 ответа

Вы можете поместить все массивы слов в один массив и использовать рекурсивную функцию следующим образом:

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

Что возвращает:

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
)

Но я предполагаю, что это плохо для больших массивов, так как выходной массив будет очень большим.


Чтобы обойти это, вы можете напрямую вывести комбинации, используя аналогичный подход:

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

Что дает:

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

При таком подходе также легко получить «суб-конкатенации». Просто вставьте echo $ string. PHP_EOL; перед concat ($ array, $ string); и вывод:

 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
9
ответ дан 6 December 2019 в 10:49
поделиться

Вы можете перечислить элементы набора результатов, т.е. для каждого целого числа от 0 .... (количество элементов) -1 вы можете определить, какой элемент вернуть (т.е. есть естественный порядок). Для данного примера:

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]

Все, что вам нужно, это (целочисленный) индекс n и функция, которая «переводит» индекс в n -й элемент (естественный порядок) набор. Поскольку вам нужно только целое число для хранения текущего состояния, потребление памяти не "взрывается", когда у вас много / больших массивов. Как сказал Крис в своем комментарии, вы жертвуете скоростью (при использовании меньших наборов) в пользу низкого потребления памяти. (Хотя я думаю - способ реализации php - это тоже разумное быстрое решение.)

$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";
}

Вы можете использовать это, например, для реализации. Iterator , SeekableIterator или, может быть, даже ArrayAccess (и тем самым инвертирует управление по сравнению с рекурсивными решениями, почти как yield на python или ruby)

<?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; }
}

выводит

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

(последовательность вроде в порядке ;-))

5
ответ дан 6 December 2019 в 10:49
поделиться

Я не тестировал это на огромных списках слов, но это довольно быстро для списков среднего размера и не использует рекурсию, что, как мне кажется (поправьте меня, если я ошибаюсь), вероятно, вызывает проблемы с ограничением памяти:

$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
2
ответ дан 6 December 2019 в 10:49
поделиться

Мой дубль

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();
2
ответ дан 6 December 2019 в 10:49
поделиться