Объединить значения n массивов в php - PullRequest
7 голосов
/ 11 февраля 2010

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

Например, если массив 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?

Спасибо!

Ответы [ 4 ]

9 голосов
/ 11 февраля 2010

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

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
5 голосов
/ 12 февраля 2010

Вы можете перечислить элементы набора результатов, то есть для каждого целого числа от 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";
}

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

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

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

2 голосов
/ 12 февраля 2010

Мой дубль

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 голосов
/ 11 февраля 2010

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

$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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...