У меня есть неизвестное количество массивов, каждое из которых содержит неизвестное количество слов. Я хочу объединить значения из каждого списка, чтобы все возможные варианты слов сохранялись в конечном массиве.
Например, если массив 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 списков, и у каждого списка, скорее всего, будет более двух слов.
Я хотел бы сделать это на PHP.
Я знаю, как это сделать, если я знаю количество списков, хотя это, вероятно, не самый ресурсоэффективный метод. Но вложенные петли foreach
работают, если вы знаете количество массивов. Что, если вы этого не сделаете? И каковы некоторые методы решения этой проблемы, которые будут работать, если, скажем, имеется 100 массивов по 100 слов каждый. Или 1000?
Благодаря!
Вы можете поместить все массивы слов в один массив и использовать такую рекурсивную функцию:
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;
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
Вы можете перечислять элементы набора результатов, т. Е. Для каждого целого числа от 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 (eg 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
(последовательность выглядит нормально ;-))
Я не тестировал это в огромных списках слов, но это довольно быстро в списках умеренного размера и не использует рекурсию, и я думаю (пожалуйста, поправьте меня, если я ошибаюсь), вероятно, вызывает проблемы с ограничением памяти:
$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
Мой прием
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();