У меня есть неизвестное количество массивов, каждый содержащий неизвестное количество слов. Я хочу связать значения из каждого списка так, чтобы все возможные изменения слов были сохранены к заключительному массиву.
Например, если массив 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?
Спасибо!
Вы можете поместить все массивы слов в один массив и использовать рекурсивную функцию следующим образом:
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
Вы можете перечислить элементы набора результатов, т.е. для каждого целого числа от 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
(последовательность вроде в порядке ;-))
Я не тестировал это на огромных списках слов, но это довольно быстро для списков среднего размера и не использует рекурсию, что, как мне кажется (поправьте меня, если я ошибаюсь), вероятно, вызывает проблемы с ограничением памяти:
$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();