In PHP need all unique combinations of multiple arrays - php

First, I have looked through dozens of similar questions/answers on this site an have not found any type of solution I could use or modify to use for this problem. There are a lot of things that come into play with this. I got close but it kept running out of memory or other resource so here I am.
I need to create a function that takes a set of arrays and returns all combinations with a specific set of rules. So to start I pull the arrays. There are 9 to start of all various lengths. 4 of the arrays are duplicate set of 4 other individual arrays. Heres how I pull the arrays to start, just so you have an idea of how the data looks.
$prodA1 = Products::where('type','a')->pluck('id')->toArray();
$prodA2 = Products::where('type','a')->pluck('id')->toArray();
$prodB1 = Products::where('type','b')->pluck('id')->toArray();
$prodB2 = Products::where('type','b')->pluck('id')->toArray();
$prodC1 = Products::where('type','c')->pluck('id')->toArray();
$prodC2 = Products::where('type','c')->pluck('id')->toArray();
$prodD1 = Products::where('type','d')->pluck('id')->toArray();
$prodD2 = Products::where('type','d')->pluck('id')->toArray();
$prodE = Products::where('type','e')->pluck('id')->toArray();
I need all set of arrays to be array('prodA1','prodA2','prodB1','prodB2','prodC1','prodC2','prodD1','prodD2','prodE') with the values being IDs bot the name of the array.
Each returned array must be unique
Each individual array can have as many as 96 ids but they all vary
I found the below function which if I add a take(5) to the above so it limits the output seems to produce at least all the combinations which I can then apply other rules to but it takes forever to run. This is the closest I have gotten though, there has to be a faster solution to this. Any help would be appreciated.
public function combinations($arrays, $i = 0) {
if (!isset($arrays[$i])) {
return array();
}
if ($i == count($arrays) - 1) {
return $arrays[$i];
}
// get combinations from subsequent arrays
$tmp = $this->combinations($arrays, $i + 1);
$result = array();
// concat each array from tmp with each element from $arrays[$i]
foreach ($arrays[$i] as $v) {
foreach ($tmp as $t) {
$result[] = is_array($t) ?
array_merge(array($v), $t) :
array($v, $t);
}
}
return $result;
}
EDIT:
The input wound be a set of 9 arrays with ids in them. For instance:
$prodA1 = array(1,2,3,4,5);
$prodA2 = array(1,2,3,4,5);
$prodB1 = array(6,7,8,9);
$prodB2 = array(6,7,8,9);
$prodC1 = array(10,11,12,13);
$prodC2 = array(10,11,12,13);
$prodD1 = array(14,15,16,17);
$prodD2 = array(14,15,16,17);
$prodE = array(18,19,20,21);
returns would look something like
array(1,2,6,7,10,11,14,15,18)
array(1,4,6,8,10,12,14,16,18)
so all the results would be unique, each one would contain 9 values made up of 2 prodA,2 prodB, 2 prodC, 2 prodD and 1 prodE
Hope that clears it up a little.

Related

How do I move a group of items in a PHP array below the next group of items?

In an Array with normal numerical keys ( $file_array_for_editing ), I want to move a set of consecutive X number of values below the next set of X values.
Example: If a group of X values is 10, then in an Array of 50, I have 5 sets of 10 values. I want to move the 10 values in set 3 - below the 10 values in set 4.
So that : Set1(10 values) , Set2(10 values) , Set3(10 values) , Set4(10 values) , Set5(10 values)
Becomes: Set1(10 values) , Set2(10 values) , Set4(10 values) , Set3(10 values) Set5(10 values)
Since I don't know a better way, this is how I have done it now - by what I believe is switching the values:
$file_array_for_editing = [];
for($i=1; $i <= 50; $i++) {
$file_array_for_editing[] = $i;
}
$number_in_one_set = 10 ;
$set_number_to_move = 3 ;
$f = $file_array_for_editing ;
$s = ($set_number_to_move - 1) * $number_in_one_set ; // (3 - 1) * 10 = 20
list(
$f[$s+0],$f[$s+1],$f[$s+2],$f[$s+3],$f[$s+4],$f[$s+5],$f[$s+6],$f[$s+7],$f[$s+8],$f[$s+9],$f[$s+10],$f[$s+11],$f[$s+12],$f[$s+13],$f[$s+14],$f[$s+15],$f[$s+16],$f[$s+17],$f[$s+18],$f[$s+19]
) =
[$f[$s+10],$f[$s+11],$f[$s+12],$f[$s+13],$f[$s+14],$f[$s+15],$f[$s+16],$f[$s+17],$f[$s+18],$f[$s+19],$f[$s+0],$f[$s+1],$f[$s+2],$f[$s+3],$f[$s+4],$f[$s+5],$f[$s+6],$f[$s+7],$f[$s+8],$f[$s+9]];
$file_array_for_editing = $f ;
At the end, the numerical keys stay properly in order, from 0 to 49.
I need help finding a way to do this where I can vary the $number_in_one_set
If possible, I would appreciate help finding a solution where it is clearly apparent what is being done, so that after a year, when I look at the code again, it will be easy to understand. (as in, even now, I just vaguely understand why the list() above works)
Possibly following might help you on your way. You can change the 'sets' you'd like to switch using array $switch.
<?php
$arr = [];
for($i=1; $i <= 50; $i++) {
$arr[] = $i;
}
$set = 10; // each set has 10 items
$switch = [3, 4]; // switch element 3 vs. 4
$newArr = array_chunk($arr, $set); // chop array in chunks of size $set
// swap
$tmp = $newArr[$switch[0]]; // $temp=$newArr[3]
$newArr[$switch[0]] = $newArr[$switch[1]]; // $newArr[3]=newArr[4]
$newArr[$switch[1]] = $tmp; // $newArr[4]=$newArr[3]
$result = [];
array_walk_recursive($newArr, function($el) use (&$result) { $result[] = $el; }); // flatten array
EDIT
array_walk_recursive() applies the callback function (function($el)) to each element of nested array $newArr.
Each element of array $newArr, referenced by $el will be pushed into array $result, a flat indexed array.
Also, because the anonymous function (closure) writes to flat array $result which is defined in the parent scope, we need to employ the use language construct.
source manual: Closures may also inherit variables from the parent scope. Any such variables must be passed to the use language construct.
working demo
#jibsteroos - I really like the method you provided (which #CBroe also suggested) - reason being that it allows any two sets anywhere within the original array to be swapped.
After some thought, I find I prefer using the method below, even though it is a little long winded - reason being my use case is only to exchange a set with the one below it.
<?php
$arr = []; // Setting up a sample array
for($i=1; $i <= 50; $i++) {
$arr[] = $i;
}
$set_size = 10 ; // 10 items per set
$set = 3 ; // set to move down
$set_to_move = ($set - 1) * $set_size; // start position of this set in the array - 20
print_r($arr) ;
echo '<hr>';
$slice1 = array_slice($arr,0,$set_to_move);
print_r($slice1) ; // from the start of the array - to the start of the set to move
echo '<hr>';
$slice2 = array_slice($arr,$set_to_move,$set_size);
print_r($slice2) ; // the set to move
echo '<hr>';
$slice3 = array_slice($arr,$set_to_move + $set_size,$set_size);
print_r($slice3) ; // the set after the set to move
echo '<hr>';
$slice4 = array_slice($arr,$set_to_move + (2 * $set_size));
print_r($slice4) ; // the rest of the array
echo '<hr>';
$arr_edited = array_merge($slice1,$slice3,$slice2,$slice4,);
print_r($arr_edited) ; // All 4 arrays combined in the new order
echo '<hr>';

Searching an array of objects for specific key value pairs

After pulling in some data from a mysql database saving it to a variable, I'm wondering if it's possible to "query" the variable instead of doing another request to the database? I realise I need to search an array of objects based on key and value. So here is an example of what I have.
<?php
[{"customer":1,"item":1,"bought_at":"2016-12-15 11:41:11"},
{"customer":2,"item":1,"bought_at":"2016-12-15 11:43:21"},
{"customer":3,"item":1,"bought_at":"2016-12-16 13:31:11"},
{"customer":1,"item":2,"bought_at":"2016-12-16 12:12:21"},
{"customer":1,"item":3,"bought_at":"2016-12-17 15:13:58"}]
?>
So lets say I need to search it based on the item number and the date (but not time) when the item was bought. The next step would be to return the result as another array of objects. So if I were to search for item 1 bought at 2016-12-15 it would return.
[{"customer":1,"item":1,"bought_at":"2016-12-15 11:41:11"},
{"customer":2,"item":1,"bought_at":"2016-12-15 11:41:21"},]
Is this possible? If so how would I go about doing it?
Regards
EDIT: The reason I originally asked this question was because I had a query inside a nested foreach loop which bothered me. It's a piece of code that builds up a a json table at the back-end to pass information to the front end to draw a google line graph. Also I changed the data slightly in my original question to try to make it easier to read. It's also built in Laravel. The complete code is pretty large so I'm just posting the nested foreach loops. The query is in the second loop and is given the variable $activations.
foreach ($timeRange as $time){
$temp = array();
$timeTwentyFour = date("G", strtotime($time));
$temp[] = array('v' => "Date(01,01,2000,$timeTwentyFour)");
foreach($data as $row){
$count = 0;
$activations = DB::table('customer_display')->where('display_id',$row->id)->where(DB::raw('DATE(created_at)'),$day)->get();
foreach($activations as $activation){
$timestamp = $activation->created_at;
$activationTime = explode(" ", $timestamp)[1];
if (strtotime($activationTime) >= strtotime($time) && strtotime($activationTime) < strtotime($time) + 3600){
$count++;
};
}
$temp[] = array('v' => (float) $count);
//The custom tooltip
$temp[] = array('v' => $time . ' ' . $row->location . '. ' . $count . ($count == 1 ? ' Activation' : ' Activations'));
}
$rows[] = array('c' => $temp);
}
If those are objects in an array and you only wanted the entries where item is 1 you could use array_filter;
$filtered = array_filter($items, function($item){
// only return objects where this is true
return $item->item == 1;
});
If you wanted only items purchased on the 15th use
return date('d', strtotime($item->bought_at)) == 15;
and if you want to see items 1 bought on the 15th you'd use
$filtered = array_filter($items, function($item){
return $item->item === 1
&& date('d', strtotime($item->bought_at)) == 15;
});
Also check out this answer on comparing dates for more information on how to better do that.
Another database request will be the better approach in most cases. A database is optimized for querying data. It can use indexes, etc. Well known databases like MySQL have a query optimalisation. Doing it by hand will be less efficient.
First downloading too much data and then use something like array_filter to linearly search through all the data is far less efficient than just querying the data with the search criteria in the query.
One way to do it is:
//Prepare statement once
$statement = $pdo->prepare("SELECT * FROM table WHERE item = ? AND bought_at = ?");
$statement->execute(array(1, "2016-12-15"));
foreach ($statement->fetchAll() as $array)
//Do something with $array
//reuse prepared statement with another selection criteria
$statement->execute(array(3, "2016-12-16"));
foreach ($statement->fetchAll() as $array)
//Do something with $array

PHP array get next key/value in foreach() [duplicate]

This question already has answers here:
Get next element in foreach loop
(11 answers)
Closed 9 years ago.
I am looking for a way to get the next and next+1 key/value pair in a foreach(). For example:
$a = array('leg1'=>'LA', 'leg2'=>'NY', 'leg3'=>'NY', 'leg4'=>'FL');
foreach($a AS $k => $v){
if($nextval == $v && $nextnextval == $v){
//staying put for next two legs
}
}
You can't access that way the next and next-next values.
But you can do something similar:
$a = array('leg1'=>'LA', 'leg2'=>'NY', 'leg3'=>'NY', 'leg4'=>'FL');
$keys = array_keys($a);
foreach(array_keys($keys) AS $k ){
$this_value = $a[$keys[$k]];
$nextval = $a[$keys[$k+1]];
$nextnextval = $a[$keys[$k+2]];
if($nextval == $this_value && $nextnextval == $this_value){
//staying put for next two legs
}
}
I've found the solution with complexity O(n) and does not require seeking through array back and forward:
$a = array('leg1'=>'LA', 'leg2'=>'NY', 'leg3'=>'NY', 'leg4'=>'FL');
// initiate the iterator for "next_val":
$nextIterator = new ArrayIterator($a);
$nextIterator->rewind();
$nextIterator->next(); // put the initial pointer to 2nd position
// initiaite another iterator for "next_next_val":
$nextNextIterator = new ArrayIterator($a);
$nextNextIterator->rewind();
$nextNextIterator->next();
$nextNextIterator->next(); // put the initial pointer to 3rd position
foreach($a AS $k => $v){
$next_val = $nextIterator->current();
$next_next_val = $nextNextIterator->current();
echo "Current: $v; next: $next_val; next_next: $next_next_val" . PHP_EOL;
$nextIterator->next();
$nextNextIterator->next();
}
Just remember to test for valid() if you plan to relay on the $next_val and $next_next_val.
Here's one way to do it:
while($current = current($a)) {
$next = next($a);
$nextnext = next($a);
// Comparison logic here
prev($a); // Because we moved the pointer ahead twice, lets back it up once
}
Example: http://3v4l.org/IGCXW
Note that the loop written this way will never examine the last element in your original array. That could be fixed, although with your current logic it doesn't seem to matter since there are no "more" elements to compare the last one to.
Have a look at CachingIterator, as described in this answer:
Peek ahead when iterating an array in PHP
Or use array_keys() is in another answer posted for the same question, e.g.
$keys = array_keys($array);
for ($i = 0; $i < count($keys); $i++) {
$cur = $array[$keys[$i]];
$next = $array[$keys[$i+1]];
}
You can't simply "stay put" in a loop. I suspect you're looking to do something a lot easier than write custom iterators. If you simply want to ignore entries with duplicate keys, then track the last key and compare it to the current one.
$a = array('leg1'=>'LA', 'leg2'=>'NY', 'leg3'=>'NY', 'leg4'=>'FL');
// Prints LA NY FL
$last_v = null;
foreach ( $a as $k => $v ){
if ( $last_v == $v ) {
/**
* Duplicate value, so skip it
*/
continue;
}
echo $v.' ';
$last_v = $v;
}
Funny, I'm programming PHP for a decade (with years pause), but I needed one-by-one walk functions just a week ago.
Here you are: next, prev, reset etc. See "see also" section. Also, check array_keys()

all combination between 1-7 without any duplicates

I found this here PHP take all combinations
I modified it further to include n number of sets to the given array. However i am not able to figure out how to not get any duplicate numbers in a set. for example
If the output is
1 , 1, 3, 4
then it should remove the extra '1' and give it as
1,3,4
similarly if there are 2 outputs.
1,3,4,5 and 4,5,3,1
then it should remove one of the duplicate set as well.
I tried using array_unique and thought it could solve half of the issue however it gave a memory allocation error.
<?php
function permutations($arr,$n)
{
$res = array();
foreach ($arr as $w)
{
if ($n==1) $res[] = $w;
else
{
$perms = permutations($arr,$n-1);
foreach ($perms as $p)
{
$res[] = $w." ".$p;
}
}
}
return $res;
}
// Your array
$numbers = array(1,2,3,4,5,6,7);
// Get permutation by groups of n elements
for($i=1; $i<8; $i++)
$pe = permutations($numbers,$i);
$pe = array_unique($pe);
// Print it out
print_r($pe);
?>

PHP Array shuffle, keeping unique

this is my first php script and problem, I've searched hours with no conclusion other than looping a function" too many laterations". but it doesn't solve my problem I've never studied programming or what ever so I'm hoping that there is an educated person to fill me in on this:
I have an array that contains 120 elements; consists of duplicates eg:
myArray = [0]= item_1, [1] = item _1, [2] = item_2, [3] = item_3 ect..
Briefly I'm trying to make a flash php pokermachine but I need these items in the array to be shuffled BUT I do not want the duplicates to be next to each other after the shuffle but I need the duplicates to be still in the array
I can't do a loop function to check this because it will change the shuffle too many times which will effect the odds of the game: below is what I currently have:
/ * Removed the link here that is no longer available */
you may notice at times it will double up with 2 items in the same reel
Basically I created the virtual reel dynamically with php.ini file
these values are repeatedly pushed into an array($virtualreel) so the value may appear 10 times in the reel and another value will appear 5 times variating the odds. Then after I take a random slice() from the $virtualreel to display 3 vars from this reel and repeat the loop 4 more times for the other reels, also I only can shuffle once as I want the slice() to be from the same reels array order
I only shuffle every new spin not running loop functions to shuffle if I double up on a slice(array,3 items).
hope I've explained what I'm after well enough to give you guys an idea.
You can use this function:
<?php
function shuffleArray($myArray) {
$value_count = array_count_values($myArray);
foreach($value_count as $key=>$value) {
if ($value > count($myArray)/2) {
return false;
}
}
$last_value = $myArray[count($myArray) - 1];
unset($myArray[count($myArray) - 1]);
$shuffle = array();
$last = false;
while (count($myArray) > 0) {
$keys = array_keys($myArray);
$i = round(rand(0, count($keys) - 1));
while ($last === $myArray[$keys[$i]]) {
$i = round(rand(0, count($keys) - 1));
}
$shuffle[] = $myArray[$keys[$i]];
$last = $myArray[$keys[$i]];
unset($myArray[$keys[$i]]);
}
if ($last_value === $last) {
$i = 0;
foreach($shuffle as $key=>$value) {
if ($value !== $last_value) {
$i = $key;
break;
}
}
array_splice($shuffle, $i + 1, 0, $last_value);
} else {
$shuffle[] = $last_value;
}
return $shuffle;
}
print_r(shuffleArray(array(1,5,5,3,7,7)));
Why not just:
Edit :
$shuffled = array();
while(count($to_shuffle) > 0):
$i = rand(0, count($to_shuffle)-1);
$shuffled[] = $to_shuffle[$i];
array_splice($to_shuffle, $i, 1,null);
endwhile;
I think this is what you were expecting, if you don't mind not preserving the association between keys and values.

Categories