Составить все возможные сочетания из элементов разных массивов

Статус
В этой теме нельзя размещать новые ответы.

WBS

Новичок
Есть несколько массивов с числами. Требуется составить все возможные сочетания элементов из этих массивов без повторений. Интересует наиболее быстрый алгоритм.


Пример массива:
$arr[0] = array(1,2,3);
$arr[1] = array(2,4,5,7);
$arr[2] = array(3);
$arr[3] = array(1,5);

Результат:
1 2 3 5
1 4 3 5
1 7 3 5
2 4 3 1
2 4 3 5
2 5 3 1
2 7 3 1
2 7 3 5

Самое очевидное решение:
PHP:
foreach ($arr[0] as $val1)
	foreach ($arr[1] as $val2)
		foreach ($arr[2] as $val3)
			foreach ($arr[3] as $val4)
				if (sizeof(array_unique(array($val1,$val2,$val3,$val4))) == 4)
					... // обрабатываем сочетание
Оно не устраивает по скорости. Кроме того, количество массивов (в примере их 4) величина переменная.
 

WBS

Новичок
Декартово произведение - это не совсем то что нужно, т.к. мне нужно составлять сочетания без повторений.
 

WBS

Новичок
Хочу поднять старую тему, поделиться некоторыми наработками. Мне удалось найти 3 варианта получения декартова произведения. Наверняка можно найти и больше, но для тестов мне этого было достаточно. Ниже привожу исходный код и ссылки на источники информации (в комментариях).

PHP:
function cartesian($input) { 
// http://stackoverflow.com/questions/6311779/finding-cartesian-product-with-php-associative-arrays
    $result = array(); 
 
    while (list($key, $values) = each($input)) { 
        // If a sub-array is empty, it doesn't affect the cartesian product 
        if (empty($values)) { 
            continue; 
        } 
 
        // Seeding the product array with the values from the first sub-array 
        if (empty($result)) { 
            foreach($values as $value) { 
                $result[] = array($key => $value); 
            } 
        } 
        else { 
            // Second and subsequent input sub-arrays work like this: 
            //   1. In each existing array inside $product, add an item with 
            //      key == $key and value == first item in input sub-array 
            //   2. Then, for each remaining item in current input sub-array, 
            //      add a copy of each existing array inside $product with 
            //      key == $key and value == first item of input sub-array 
 
            // Store all items to be added to $product here; adding them 
            // inside the foreach will result in an infinite loop 
            $append = array(); 
 
            foreach($result as &$product) { 
                // Do step 1 above. array_shift is not the most efficient, but 
                // it allows us to iterate over the rest of the items with a 
                // simple foreach, making the code short and easy to read. 
                $product[$key] = array_shift($values); 
 
                // $product is by reference (that's why the key we added above 
                // will appear in the end result), so make a copy of it here 
                $copy = $product; 
 
                // Do step 2 above. 
                foreach($values as $item) { 
                    $copy[$key] = $item; 
                    $append[] = $copy; 
                } 
 
                // Undo the side effecst of array_shift 
                array_unshift($values, $product[$key]); 
            } 
 
            // Out of the foreach, we can add to $results now 
            $result = array_merge($result, $append); 
        } 
    } 
 
    return $result; 
}

function array_cartesian_product($arrays) {
// http://php.net/manual/en/ref.array.php
     $result = array();
     $arrays = array_values($arrays);
     $sizeIn = sizeof($arrays);
     $size = $sizeIn > 0 ? 1 : 0;
     foreach ($arrays as $array)
         $size = $size * sizeof($array);
     for ($i = 0; $i < $size; $i ++)
     {
         $result[$i] = array();
         for ($j = 0; $j < $sizeIn; $j ++)
             array_push($result[$i], current($arrays[$j]));
         for ($j = ($sizeIn -1); $j >= 0; $j --)
         {
             if (next($arrays[$j]))
                 break;
             elseif (isset ($arrays[$j]))
                 reset($arrays[$j]);
         }
     }
     return $result;
}

function array_cartesian() { 
// http://stackoverflow.com/questions/2516599/php-2d-array-output-all-combinations
    $_ = func_get_args(); 
    if(count($_) == 0) 
        return array(array()); 
    $a = array_shift($_); 
    $c = call_user_func_array(__FUNCTION__, $_); 
    $r = array(); 
    foreach($a as $v) 
        foreach($c as $p) 
            $r[] = array_merge(array($v), $p); 
    return $r; 
}

Также добавляем мой, самый очевидный, простой и прямолинейный вариант.

PHP:
function array_cartesian_my($arr) {
	$result = array();
	foreach ($arr[0] as $val1)
		foreach ($arr[1] as $val2)
			foreach ($arr[2] as $val3)
				foreach ($arr[3] as $val4)
					$result[] = array($val1,$val2,$val3,$val4);
	return $result;
}
Измерим скорость выполнения на тестовом примере (100000 вызовов функции):
$arr[0] = array(1,2,3);
$arr[1] = array(2,4,5,7);
$arr[2] = array(3);
$arr[3] = array(1,5);

Результаты:
1. 4.1837410926819 сек.
2. 6.3906211853027 сек.
3. 4.6355211734772 сек.
4. 1.1914730072021 сек.


Вернемся к исходной задаче, т.е. получим декартово произведение с кортежами без повторяющихся элементов.

Для проверки уникальности элементов массива можно использовать 2 функции.

PHP:
function array_is_unique ($arr) {
	return sizeof(array_unique($arr))==sizeof($arr);
}

function array_is_unique2 ($arr) {
	return sizeof(array_flip(array_flip($arr)))==sizeof($arr);
}
Вторая чуть менее универсальна, но работает примерно в два раза быстрее. В нашем случае она подходит.

Вот самое быстрое, что на данный момент удалось получить:

PHP:
function cartesian_wo_repeat ($arr) {
	$result = array();
	foreach ($arr[0] as $val1)
		foreach ($arr[1] as $val2)
			foreach ($arr[2] as $val3)
				foreach ($arr[3] as $val4) {
					$arr_cur = array($val1,$val2,$val3,$val4);
					if (sizeof(array_flip(array_flip($arr_cur)))==4)
						$result[] = $arr_cur;
				}
	return $result;
}
100000 вызовов этой функции у меня выполняется за
3.5850439071655 сек.
 

WBS

Новичок
То есть примеры ты поправить не можешь?
Хочу заметить, что бОльшая часть времени уходит на проверку уникальности, т.е. совет "поправить примеры" был весьма странный, т.к. соблюдение условия "без повторений" - это и есть суть искомого алгоритма, это его ключевая часть, наибольшим образом влияющая на скорость. Не говоря уже о том, что те примеры, которые предлагалось править, в разы медленнее, чем исходный алгоритм, описанный в первом же посте.

Советам по составлению действительно быстрого алгоритма (свой я считаю неоптимальным) буду рад.
 

lagoff

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

Norm Iridium

Новичок
по-моему все решается обычной рекурсией:

PHP:
function cartesian($arr) {
$variant = array();
$result = array();
$sizearr = sizeof($arr);

	function recursiv($arr, $variant, $level, $result, $sizearr){
		$level++;		
		if($level<$sizearr){
			foreach ($arr[$level] as $val){
				$variant[$level] = $val;
				$result = recursiv($arr, $variant, $level, $result, $sizearr);
			}
		}else{
			//if (sizeof(array_flip(array_flip($variant)))==$sizearr)
				$result[] = $variant;
		}		
		return $result;	
	}

return recursiv($arr, $variant, -1, $result, $sizearr);
}
то что в комментарии это убрать повторы. У меня же списки уникальные и слова в них не совпадают, так что мне не надо.
 

Adelf

Administrator
Команда форума
Зачем поднимать годовалую тему про задачу, которую решают студенты на начальных этапах подготовки...
Да еще и предлагать плохие решения.
Тему закрываю.
 
Статус
В этой теме нельзя размещать новые ответы.
Сверху