I have a PHP array which looks like this example:
$array[0][0] = 'apples';
$array[0][1] = 'pears';
$array[0][2] = 'oranges';
$array[1][0] = 'steve';
$array[1][1] = 'bob';
And I would like to be able to produce from this a table with every possible combination of these, but without repeating any combinations (regardless of their position), so for example this would output
Array 0 Array 1
apples steve
apples bob
pears steve
pears bob
But I would like for this to be able to work with as many different arrays as possible.
this is called "cartesian product", php man page on arrays http://php.net/manual/en/ref.array.php shows some implementations (in comments).
and here's yet another one:
function array_cartesian() {
$_ = 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;
}
$cross = array_cartesian(
array('apples', 'pears', 'oranges'),
array('steve', 'bob')
);
print_r($cross);
You are looking for the cartesian product of the arrays, and there's an example on the php arrays site: http://php.net/manual/en/ref.array.php
Syom copied http://www.php.net/manual/en/ref.array.php#54979 but I adapted it this to become an associative version:
function array_cartesian($arrays) {
$result = array();
$keys = array_keys($arrays);
$reverse_keys = array_reverse($keys);
$size = intval(count($arrays) > 0);
foreach ($arrays as $array) {
$size *= count($array);
}
for ($i = 0; $i < $size; $i ++) {
$result[$i] = array();
foreach ($keys as $j) {
$result[$i][$j] = current($arrays[$j]);
}
foreach ($reverse_keys as $j) {
if (next($arrays[$j])) {
break;
}
elseif (isset ($arrays[$j])) {
reset($arrays[$j]);
}
}
}
return $result;
}
I needed to do the same and I tried the previous solutions posted here but could not make them work. I got a sample from this clever guy http://www.php.net/manual/en/ref.array.php#54979. However, his sample did not managed the concept of no repeating combinations. So I included that part. Here is my modified version, hope it helps:
$data = array(
array('apples', 'pears', 'oranges'),
array('steve', 'bob')
);
$res_matrix = $this->array_cartesian_product( $data );
foreach ( $res_matrix as $res_array )
{
foreach ( $res_array as $res )
{
echo $res . " - ";
}
echo "<br/>";
}
function array_cartesian_product( $arrays )
{
$result = array();
$arrays = array_values( $arrays );
$sizeIn = sizeof( $arrays );
$size = $sizeIn > 0 ? 1 : 0;
foreach ($arrays as $array)
$size = $size * sizeof( $array );
$res_index = 0;
for ( $i = 0; $i < $size; $i++ )
{
$is_duplicate = false;
$curr_values = array();
for ( $j = 0; $j < $sizeIn; $j++ )
{
$curr = current( $arrays[$j] );
if ( !in_array( $curr, $curr_values ) )
{
array_push( $curr_values , $curr );
}
else
{
$is_duplicate = true;
break;
}
}
if ( !$is_duplicate )
{
$result[ $res_index ] = $curr_values;
$res_index++;
}
for ( $j = ( $sizeIn -1 ); $j >= 0; $j-- )
{
$next = next( $arrays[ $j ] );
if ( $next )
{
break;
}
elseif ( isset ( $arrays[ $j ] ) )
{
reset( $arrays[ $j ] );
}
}
}
return $result;
}
The result would be something like this:
apples - steve
apples - bob
pears - steve
pears - bob
oranges - steve
oranges - bob
If you the data array is something like this:
$data = array(
array('Amazing', 'Wonderful'),
array('benefit', 'offer', 'reward'),
array('Amazing', 'Wonderful')
);
Then it will print something like this:
Amazing - benefit - Wonderful
Amazing - offer - Wonderful
Amazing - reward - Wonderful
Wonderful - benefit - Amazing
Wonderful - offer - Amazing
Wonderful - reward - Amazing
foreach($parentArray as $value) {
foreach($subArray as $value2) {
$comboArray[] = array($value, $value2);
}
}
Don't judge me..
This works I think - although after writing it I realised it's pretty similar to what others have put, but it does give you an array in the format requested. Sorry for the poor variable naming.
$output = array();
combinations($array, $output);
print_r($output);
function combinations ($array, & $output, $index = 0, $p = array()) {
foreach ( $array[$index] as $i => $name ) {
$copy = $p;
$copy[] = $name;
$subIndex = $index + 1;
if (isset( $array[$subIndex])) {
combinations ($array, $output, $subIndex, $copy);
} else {
foreach ($copy as $index => $name) {
if ( !isset($output[$index])) {
$output[$index] = array();
}
$output[$index][] = $name;
}
}
}
}
#user187291
I modified this to be
function array_cartesian() {
$_ = func_get_args();
if (count($_) == 0)
return array();
$a = array_shift($_);
if (count($_) == 0)
$c = array(array());
else
$c = call_user_func_array(__FUNCTION__, $_);
$r = array();
foreach($a as $v)
foreach($c as $p)
$r[] = array_merge(array($v), $p);
return $r;
}
so it returns that all-important empty array (the same result as no combinations) when you pass 0 arguments.
Only noticed this because I'm using it like
$combos = call_user_func_array('array_cartesian', $array_of_arrays);
function array_comb($arrays)
{
$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;
}
I had to make combinations from product options. This solution uses recursion and works with 2D array:
function options_combinations($options) {
$result = array();
if (count($options) <= 1) {
$option = array_shift($options);
foreach ($option as $value) {
$result[] = array($value);
}
} else {
$option = array_shift($options);
$next_option = options_combinations($options);
foreach ($next_option as $next_value) {
foreach ($option as $value) {
$result[] = array_merge($next_value, array($value));
}
}
}
return $result;
}
$options = [[1,2],[3,4,5],[6,7,8,9]];
$c = options_combinations($options);
foreach ($c as $combination) {
echo implode(' ', $combination)."\n";
}
Elegant implementation based on native Python function itertools.product
function direct_product(array ...$arrays)
{
$result = [[]];
foreach ($arrays as $array) {
$tmp = [];
foreach ($result as $x) {
foreach ($array as $y) {
$tmp[] = array_merge($x, [$y]);
}
}
$result = $tmp;
}
return $result;
}
Related
I have an image gallery and each is saved by index.. gallery1: /image here.. gallery2: / image here.. etc..
I'm using an index with multiple for loops to return the images and by column because it's either Masonry or rectangular. I am getting the return fine except for the very last index.
private function rectangle($items, $columns, $contents = array()) {
$thumbs = array('talent_thumbnail','360x207');
$arr = array();
$col = round(count($items) / $columns);
$perCol = floor(count($items) / $columns);
$extra = count($items) % $columns;
$ind = 1;
$length = count($items);
$arr = array();
for($i = 0; $i < $columns && $ind < $length; $i++) {
$temp = array();
for($j = 0; $j < $perCol; $j++) {
$obj = new JObject();
$obj->image = $items['gallery' . $ind]['photo'];
$obj->alt_text = $items['gallery'. $ind]['alt_text'];
$temp[] = $obj;
$ind++;
}
if ($extra > 0) {
$obj = new JObject();
$obj->image = $items['gallery'. $ind]['photo'];
$obj->alt_text = $items['gallery'. $ind]['alt_text'];
$temp[] = $obj;
$ind++;
$extra--;
}
$arr[] = $temp;
}
}
I know it can't be that hard but I'm not that good at it right yet.
Any help is so much welcome.
Thank You.
You set the variable $ind to 1, count the length of the array, and then initialize the for loop with the condition $ind < $length.
When $ind reaches the index needed to access the last item, the loop is not run again, since $ind is now equal to $length, not smaller.
You can fix this by changing the condition in your for-loop to "less or equal":
$i < $columns && $ind <= $length
This runs the loop once more when $ind has reached the last index.
Probably the problem is at this line:
$ind = 1;
In PHP non-associative arrays have the indexes starting to 0 instead of 1:
$arr = array('a', 'b', 'c');
print_r($arr);
// output:
Array ( [0] => a [1] => b [2] => c )
So, try to change that line in your code to:
$ind = 0;
Complete code:
private function rectangle($items, $columns, $contents = array()) {
$thumbs = array('talent_thumbnail','360x207');
$arr = array();
$col = round(count($items) / $columns);
$perCol = floor(count($items) / $columns);
$extra = count($items) % $columns;
$ind = 0;
$length = count($items);
$arr = array();
for($i = 0; $i < $columns && $ind < $length; $i++) {
$temp = array();
for($j = 0; $j < $perCol; $j++) {
$obj = new JObject();
$obj->image = $items['gallery' . $ind]['photo'];
$obj->alt_text = $items['gallery'. $ind]['alt_text'];
$temp[] = $obj;
$ind++;
}
if ($extra > 0) {
$obj = new JObject();
$obj->image = $items['gallery'. $ind]['photo'];
$obj->alt_text = $items['gallery'. $ind]['alt_text'];
$temp[] = $obj;
$ind++;
$extra--;
}
$arr[] = $temp;
}
}
I have a array to find sequence of alphabets and then fetch last and first combination. I am trying something like this.
$aarr = ['x','y','z','t','m','n','x','y','z'];
$str = implode('',$aarr);
$all_subset = powerSet($aarr);
foreach ($all_subset as $set) {
$sre_temp = implode('', $set);
$tru = hasOrderedCharactersForward($sre_temp);
if($tru){
echo $sre_temp.'<br>';
}
}
function powerSet($array) {
// add the empty set
$results = array(array());
foreach ($array as $element) {
foreach ($results as $combination) {
$results[] = array_merge(array($element), $combination);
}
}
return $results;
}
function hasOrderedCharactersForward($str, $i = 2) {
$alpha = 'abcdefghijklmnopqrstuvwxyz';
$len = strlen($str);
for($j=0; $j <= $len - $i; $j++){
if(strrpos($alpha, substr($str, $j, $i)) !== false){
return true;
}
}
return false;
}
I think powerSet() is not working like i think. Even it should show 'xyz' as combination but its not;
Have a look at this and make use of it if it fits your needs.
$aarr = ['x','y','z','t','m','n','x','y','z'];
$subsets = [];
$i=0;
#here we merge all chars to sub-sequence
foreach($aarr as $k=>$v){
$subsets[$i][]=$v;
if(isset($aarr[$k+1]) && ord($v)+1!==ord($aarr[$k+1])){
$i++;
}
}
$subsets = array_map(function($a){ return implode('',$a);},$subsets);
print_r($subsets);
Result:
Array ( [0] => xyz [1] => t [2] => mn [3] => xyz )
Getting the first and last value:
#get first
$first=null;
$i=0;
do{
if(strlen($subsets[$i])>1){#find sequence
$first = $subsets[$i];
}
$i++;
}while(!$first && isset($subsets[$i]));
#get last
$last=null;
$i=count($subsets)-1;
do{
if(strlen($subsets[$i])>1){#find sequence
$last = $subsets[$i];
}
$i--;
}while(!$last && isset($subsets[$i]));
print "$first, $last";
Result:
xyz, xyz
Here, there is a example string "XjYAKpR" .. how to create all new string possibility with that string ??
I've tried before
function containAllRots($s, $arr) {
$n = strlen($s);
$a = array();
for ($i = 0; $i < $n ; $i++) {
$rotated = rotate(str_split($s), $i);
$a[] = $rotated;
}
print_r($a);die();
if (array_diff($arr, $a)) {
return True;
}
else
{
return False;
}
}
I make 2 function rotate and generate
function rotate($l, $n) {
$b = $l[$n];
$sisa = array_values(array_diff($l, array($b)));
for ($i = 0; $i < count($sisa) ; $i++) {
$random[] = generate($sisa, $b);
}
print_r($random);die();
$hasil = $l[$n] . implode("",$random);
return $hasil;
}
function generate($sisa, $b) {
$string = implode("",$sisa);
$length = count($sisa);
$size = strlen($string);
$str = '';
for( $i = 0; $i < $length; $i++ ) {
$str .= $string[ rand( 0, $size - 1 ) ];
}
Here there is a pair of functions that lets you calculate a permutation set
(no repetitions are taken in account)
function extends_permutation($char, $perm) {
$result = [];
$times = count($perm);
for ($i=0; $i<$times; $i++) {
$temp = $perm;
array_splice($temp, $i, 0, $char);
array_push($result, $temp);
}
array_push($result, array_merge($perm, [$char]));
return $result;
}
function extends_set_of_permutations($char, $set) {
$step = [];
foreach ($set as $perm) {
$step = array_merge($step, extends_permutation($char, $perm));
}
return $step;
}
you can use them to generate the required set of permutations. Something like this:
$seed = "XjYAKpR";
// the first set of permutations contains only the
// possible permutation of a one char string (1)
$result_set = [[$seed[0]]];
$rest = str_split(substr($seed,1));
foreach($rest as $char) {
$result_set = extends_set_of_permutations($char, $result_set);
}
$result_set = array_map('implode', $result_set);
sort($result_set);
At the end of the execution you will have the 5040 permutations generated by your string in the result_set array (sorted in alphabetical order).
Add a char and you will have more than 40000 results.
The functions are quite naive in implementation and naming, both aspects can be improved.
I have this array:
$array = array(a, b, c, d, e, f, g);
I want to split it in two arrays depending if the index is even or odd, like this:
$odd = array(a, c, e, g);
$even = array(b, d, f);
Thanks in advance!
One solution, using anonymous functions and array_walk:
$odd = array();
$even = array();
$both = array(&$even, &$odd);
array_walk($array, function($v, $k) use ($both) { $both[$k % 2][] = $v; });
This separates the items in just one pass over the array, but it's a bit on the "cleverish" side. It's not really any better than the classic, more verbose
$odd = array();
$even = array();
foreach ($array as $k => $v) {
if ($k % 2 == 0) {
$even[] = $v;
}
else {
$odd[] = $v;
}
}
Use array_filter (PHP >= 5.6):
$odd = array_filter($array, function ($input) {return $input & 1;}, ARRAY_FILTER_USE_KEY);
$even = array_filter($array, function ($input) {return !($input & 1);}, ARRAY_FILTER_USE_KEY);
I am not sure if this is the most elegant way, but it should work a charm:
$odd=array();
$even=array();
$count=1;
foreach($array as $val)
{
if($count%2==1)
{
$odd[]=$val;
}
else
{
$even[]=$val;
}
$count++;
}
As an almost-one-liner, I think this will be my favourite:
$even = $odd = array();
foreach( $array as $k => $v ) $k % 2 ? $odd[] = $v : $even[] = $v;
Or for a tiny little more? speed:
$even = $odd = array();
foreach( $array as $k => $v ) ( $k & 1 ) === 0 ? $even[] = $v : $odd[] = $v;
A bit more verbose variant:
$both = array( array(), array() );
// or, if $array has at least two elements:
$both = array();
foreach( $array as $k => $v ) $both[ $k % 2 ][] = $v;
list( $even, $odd ) = $both;
With array_chunk:
$even = $odd = array();
foreach( array_chunk( $array, 2 ) as $chunk ){
list( $even[], $odd[] ) = isset( $chunk[1]) ? $chunk : $chunk + array( null, null );
// or, to force even and odd arrays to have the same count:
list( $even[], $odd[] ) = $chunk + array( null, null );
}
If $array is guaranteed to have even number of elements:
$even = $odd = array();
foreach( array_chunk( $array, 2 ) as $chunk )
list( $even[], $odd[] ) = $chunk;
PHP 5.5.0+ with array_column:
$chunks = array_chunk( $array, 2 );
$even = array_column( $chunks, 0 );
$odd = array_column( $chunks, 1 );
Something similar for older PHP versions.
The keys will be 0,2,4,… and 1,3,5,…. If you don't like this, apply an array_values too:
$even = array_intersect_key( $array, array_flip( range( 0, count( $array ), 2 )));
$odd = array_intersect_key( $array, array_flip( range( 1, count( $array ), 2 )));
or
$even = array_intersect_key( $array, array_fill_keys( range( 0, count( $array ), 2 ), null ));
$odd = array_intersect_key( $array, array_fill_keys( range( 1, count( $array ), 2 ), null ));
One more functional solution with array_chunk and array_map. The last line removes empty item from the 2nd array, when size of a source array is odd
list($odd, $even) = array_map(null, ...array_chunk($ar,2));
if(count($ar) % 2) array_pop($even);
Just loop though them and check if the key is even or odd:
$odd = array();
$even = array();
foreach( $array as $key => $value ) {
if( 0 === $key%2) { //Even
$even[] = $value;
}
else {
$odd[] = $value;
}
}
One
$odd = $even = array();
for ($i = 0, $l = count($array ); $i < $l;) { // Notice how we increment $i each time we use it below, by two in total
$even[] = $array[$i++];
if($i < $l)
{
$odd[] = $array[$i++];
}
}
Two
$odd = $even = array();
foreach (array_chunk($array , 2) as $chunk) {
$even[] = $chunk[0];
if(!empty( $chunk[1]))
{
$odd[] = $chunk[1];
}
}
Based on #Jon's second variant, I made this following for use with PHP Smarty v3 template engine. This is for displaying news/blog with both one or two columns template model.
After the MySql query I'll do the following code :
if(sizeof($results) > 0) {
$data = array();
foreach($results as $k => $res) {
if($k % 2 == 0) {
$res["class"] = "even";
$data["all"][] = $data["even"][] = $res;
}
else {
$res["class"] = "odd";
$data["all"][] = $data["odd"][] = $res;
}
}
}
I obtain an array of 3 sub-arrays (including odd/even class) with Smarty syntax of use :
all items {foreach $data.all as $article}...{/foreach}
odd items only {foreach $data.odd as $article}...{/foreach}
even items only {foreach $data.even as $article}...{/foreach}
Hope it helps some people...
$odd = [];
$even = [];
while (count($arr)) {
$odd[] = array_shift($arr);
$even[] = array_shift($arr);
}
<?php
$array1 = array(0,1,2,3,4,5,6,7,8,9);
$oddarray = array();
$evenarray = array();
$count = 1;
echo "Original: ";
foreach ($array1 as $value)
{
echo "$value";
}
echo "<br> Even: ";
foreach ($array1 as $print)
{
if ($count%2==1)
{
$evenarray = $print;
echo "$print";
}
$count++;
}
echo "<br> Odd: ";
foreach ($array1 as $print2) {
if ($count%2!=1)
{
$oddarray[] = $print2;
echo "$print2";
}
$count++;
}
?>
Output:
Original: 0123456789
Even: 02468
Odd: 13579
I have a problem with an advanced loop, this are my arrays
$array1 = array(1,2,3);
$array2 = array(4,5);
$array3 = array(6,7,8,9);
$array4 = array(10,11);
I want to loop with the following result:
1,4,6,10
1,4,6,11
1,4,7,10
1,5,7,11
With the last: 3,5,8,11
How can I do that?
Solution for a variable number of arrays. Can probably be written a bit shorter, but I leave that to you. ;)
<?php
$array1 = array(1,2,3);
$array2 = array(4,5);
$array3 = array(6,7,8,9);
$array4 = array(10,11);
function turboArray()
{
$arrays = func_get_args();
$indexes = array_fill(0, count($arrays), 0);
function turboSubArray(&$arrays, &$indexes, $l){
for ($a = 0; $a < count($arrays[$l]); $a++){
$indexes[$l] = $a;
if ($l == count($arrays) - 1) {
for ($i = 0; $i < count($indexes); $i++)
{
echo $arrays[$i][$indexes[$i]];
if ($i == count($indexes) - 1){
echo "\n<br/>";
$indexes[$i] = 0;
if ($i == 0) return;
}
else {
echo ', ';
}
}
} else if ($l < count($indexes)) {
turboSubArray($arrays, $indexes, $l + 1);
}
}
}
$l = 0;
turboSubArray($arrays, $indexes, $l);
}
turboArray($array1, $array2, $array3, $array4);
This should do it... ?
foreach ($array1 as $first) {
foreach ($array2 as $second) {
foreach ($array3 as $third) {
foreach ($array4 as $fourth) {
echo "$first $second $third $fourth";
}
}
}
}
I got bored and decided to overengineer a bit. Enjoy.
class CrossJoin implements Iterator {
private $arrays = array();
private $index = 0;
public function __construct(/* $array1, ... */) {
foreach (func_get_args() as $arr) {
// ArrayIterator's iteration stuff is cleaner than arrays',
// and less prone to breakage when objects are passed around
if (count($arr)) $this->arrays[] = new ArrayIterator($arr);
}
// if any of the arrays were empty, a cross join should fail.
// fudge a single empty array to prevent special-casing later
if (count($this->arrays) !== func_num_args()) {
$this->arrays = array(new ArrayIterator(array()));
}
}
public function next() {
for ($i = count($this->arrays) - 1; $i >= 0; --$i) {
// carry til we don't have to anymore
$place = $this->arrays[$i];
$place->next();
if ($place->valid()) break;
}
// if $i<0, then we carried right off the edge of the list.
// don't let $arrays[0] be reset, cause that's our indicator
// that we're done.
if ($i<0) return;
++$this->index;
for (++$i; $i < count($this->arrays); ++$i) {
$this->arrays[$i]->rewind();
}
}
public function current() {
$result = array();
foreach ($this->arrays as $arr) { $result[] = $arr->current(); }
return $result;
}
public function key() { return $this->index; }
public function valid() {
return !empty($this->arrays) && $this->arrays[0]->valid();
}
public function rewind() {
foreach ($this->arrays as $arr) $arr->rewind();
$this->index = 0;
}
}
$combos = new CrossJoin(
array(1, 2, 3),
array(4, 5),
array(6, 7, 8, 9),
array(10, 11)
);
foreach ($combos as $combo) {
echo implode(', ', $combo), "\n";
}
Hope that helps:
for ($i = 0; $i < count($a1); ++i) {
for ($j = 0; $j < count($a2); ++j) {
for ($k = 0; $k < count($a3); ++k) {
for ($l = 0; $l < count($a4); ++l) {
echo $a1[$i] . ' ' . $a2[$j] . ' ' . $a3[$k] . ' ' . $a4[$l] . '\n';
}
}
}
}
Cheers!
You are looking for the PHP array_merge function.