I have an array of values (non unique) and I need to work out how many times I can pull out x (e.g 3) unique items from that array.
e.g.
[5, 5, 4, 1, 2, 3, 4, 2, 1, 3, 5, 5]
What is the largest quantity of unique items of length 3 (e.g. [5, 4, 1]) I can retrieve?
For context, this is for an offer system in a shopping cart. The array of values is the product ids, and I need to know how many times I can apply a specific offer that requires 3 different items from the array of ids in order to be valid.
Thanks for any help - just ask if anything is unclear and I'll try to explain. If I've missed an existing question that answers this please let me know and I'll close this question.
Here's one way you can follow:
$basket = array(5, 5, 4, 1, 2, 3, 4, 2, 1, 3, 5, 5);
$length = 3; //unique set size
function getSetCount($basket, $length) {
$counts = array_count_values($basket); //count of each ID
$totalCount = count($basket); //count of all elements
//maximum amount of sets
$max = floor($totalCount / $length);
//since every ID can be only once in a set, it all above $max occurences won't be able to fit any set.
$reducedCounts = array_map(function ($el) use ($max) {
return min($el, $max);
}, $counts);
//final result is "all elements that should fit in sets / size of set
return floor(array_sum($reducedCounts) / $length);
}
If you would like to print them:
function printSets($basket, $length) {
$counts = array_count_values($basket); //count of each ID
while(!empty($counts)) {
arsort($counts);//reverse sort with keeping the keys
$set = array_slice(array_keys($counts),0,$length); //get the set
echo implode(',', $set) . '<br/>';
foreach($set as $id) { //reduce counts
$counts[$id]--;
}
$counts = array_filter($counts); //clean zeros
}
}
The code above may not handle proparly some edge cases. But this is the idea.
Basically array_count_values() counts values' occurences and returns an array of value => count pairs. Then its easy to manipulate this data.
If i understand you correctly:
function getChunksCount($products)
{
// get unique ids
$uniqueProducts = array_unique($products);
// count unique ids
$count = count($uniqueProducts);
// let's get the number of chucks available
$chunkSize = 3;
// round to the floor
return floor($count / $chunkSize);
}
No complex logic or processing at all. Next time try to write down what exactly needs to be done in what order, and the solution might become pretty obvious :)
You can do it using array_unique and array_slice.
$arr = [5, 5, 4, 1, 2, 3, 4, 2, 1, 3, 5, 5];
$new_arr = array_slice(array_unique($arr), 0, 3);
print_r($new_arr); //Array ( [0] => 5 [1] => 4 [2] => 1 )
You can use array_count_values.
<?php `$array = array(5, 5, 4, 1, 2, 3, 4, 2, 1, 3, 5, 5); $vals = array_count_values($array); echo 'No. of NON Duplicate Items: '.count($vals).'<br><br>'; print_r($vals);`?>
Output is -Array ( [5] => 4 [4] => 2 1 => 2 [2] => 2 [3] => 2 )
Related
Preamble:
This question is not about to learn PHP, nor is it code that I plan to use in a productive environment. I just want so see and learn better ways to do this work, as I did in my approach. So please only correct my code or show me better, faster or shorter solutions for doing it. The problem itself has already been solved. Thank you!
The Problem:
Some days ago a user asked a question here on SO. His problem has got my attention, because I wanted to find a way to solve his needs.
He wanted to get all possible key combinations of an PHP array, where the sum of the values is 100, or as close as possible to 100. He had given us an example array, which I will use for my examples too:
$array = array(25, 30, 50, 15, 20, 30);
For example, one result should be [2, 4, 5], because 50 + 20 + 30 is 100.
$sum = $array[2] + $array[4] + $array[5]; // = 100
I think the basic idea should be clear. Now let's take a look at my work ...
My Approach:
So this problem has got my attention as a developer. At first, I thought it would be pretty simple. Just do some addition and check the result. But then I've noticed that there some points to keep in mind ...
There are plenty of combinations to test. For the example array, there would be up to 720 (6! = 1*2*3*4*5*6 = 720) possible permutations. To get all possible combinations, I wanted to get all possible permutations of the array first.
But that was only the half truth. Because there could be double values in the array (as in your example the 30), we could not get all possible permutations of the array values, we had to get all possible permutations of the array keys instead.
So I've used the pc_permut function of the php cookbook and modified it for my needs. It will return all possible permutations in an array of keys.
/**
* gets all possible permutations of $array
* #param array $array
* #param array $permutations
* #return array
*/
function permutations($array, $permutations = array()) {
if( !empty($array) ) {
$result = array();
for( $i = count($array) - 1; $i >= 0; --$i ) {
$newItems = $array;
$newPerms = $permutations;
list($values) = array_splice($newItems, $i, 1);
array_unshift($newPerms, $values);
$result = array_merge($result, permutations($newItems, $newPerms));
}
}
else {
$result = array($permutations);
}
return $result;
}
The result of this function is a multidimensional array, containing all permutations in an ordered key array.
Array (
[0] => Array (
[0] => 0
[1] => 1
[2] => 2
[3] => 3
[4] => 4
[5] => 5
)
[1] => Array (
[0] => 1
[1] => 0
[2] => 2
[3] => 3
[4] => 4
[5] => 5
)
[...
)
So, for now I got all permutations to work with. The calculation of the possible combinations is not so hard at all. I'll just loop through the permutation, increment the sum until they've reached 100 or above and return the key combination.
But I find out that I missed one thing. As I got all possible permutations, there are even some results doubled in the list. To explain, this two results are basically the same:
[2, 4, 5]; // 50 + 20 + 30 = 100
[4, 5, 2]; // 20 + 30 + 50 = 100
I've ended up sorting the keys after calculation and use them as index in the resulting array. So it would be sure, that every combination only exists once in the result. This is my combinations function:
/**
* gets all possible key combinations of $array with a sum below or equal $maxSum
* #param array $array
* #param integer $maxSum
* #return array
*/
function combinations($array, $maxSum) {
// get all permutations of the array keys
$permutations = permutations(array_keys($array));
$combinations = array();
// loop all permutations
foreach( $permutations as $keys ) {
// create a container for each permutation to store calculation
$current = array(
"sum" => 0,
"keys" => array()
);
// now loop through the permutation keys
foreach( $keys as $key ) {
// if the addition is still between or equal $maxSum
if( $current["sum"] + $array[$key] <= $maxSum ) {
// increment the sum and add key to result
$current["sum"] += $array[$key];
$current["keys"][] = $key;
}
}
// to be sure each combination only exists once in the result
// order the keys and use them as array index
sort($current["keys"]);
$combinations[join("", $current["keys"])] = $current;
}
// remove the created key-index from array when finished
return array_values($combinations);
}
The execution is simple straight forward:
$array = array(25, 30, 50, 15, 20, 30);
print_r(combinations($array, 100));
The result is an array, containing all combinations. For our example array there are eleven possible combinations. The result looks like this:
Array (
[0] => Array (
[sum] => 90
[keys] => Array (
[0] => 0
[1] => 1
[2] => 3
[3] => 4
)
)
[1] => Array (
[sum] => 90
[keys] => Array (
[0] => 0
[1] => 2
[2] => 3
)
)
[...
Since I've written this script as an answer of the original question, I'll ask myself, if there would be another, even better way to do the work. Maybe there is a way without permutations, or a way to exclude same combinations from calculation or the resulting array. I know that I could execute the calculation directly in the permutations function too, but this would be basically the same work flow.
I would really like to get some advises, tips or improvements from you. I think here is some potential to improve the script, but I have actually no idea how to. But I'm sure it could be done more simple and straight forward ...
Thanks for your time! :)
Sorting the array leads to some possibilities: here is the one i'm thinking of:
I denote a(selectedIndexes) the element composed of all elements of selectedIndexes e.g.
a({25, 30, 30}) = (25, 30, 30)
P(n) is the set of all combinations of the indexes 1 to n and for clarity sake my arrays starts at index 1 (thus P(2)={1, 2, (1, 2)})
I'm using 2 break conditions explained in the pseudocode below. 1st one is the first element of aSorted = the allowed sum.
2nd one is the sum is too small compared to aSorted first element
selectedIndexes = {}
sum = 100
aSorted = {15, 20, 25, 30, 30, 50} //starting values for the example
//to clarify the following function
aSum = {15, 35, 60, 90}
function n(aSorted, sum, selectedIndexes){
compute aSum //precisely search in aSum for the index at which
//the elements are bigger than sum, and cut
answer = (P(count(aSum))) X a(selectedIndexes) // with X being the cartesian product
for (i=count(aSum)+1; i<=count(aSorted); i++){
newASorted = splice(aSorted, count(aSum))
// 1st break condition
if(newASorted is empty) return answer
// 2nd break condition the new sum < the first element of aSorted
if (aSorted(i)<sum && sum-aSorted(i)>=aSorted(1)){
answer += n(newASorted, sum-aSorted(i), push(selectedIndexes,
i))
}
}
return answer
}
The complexity of this algorithm feels quadratic(after a quick check it's more like of order n^log2(n)) in regard to the number of elements in the array
To make it less abstract, let's develop the example(warning i trust the example more than the pseudocode although i didn't see inaccuracies myself in the pseudocode):
n({15, 20, 25, 30, 30, 50}, 100, {}) = P(4) + n({15, 20, 25, 30, 30}, 50, {6}) + n({15, 20, 25, 30}, 70, {5})
Starting by developing the first n function of the right side of the equation
n({15, 20, 25, 30, 30}, 50, {5}) = (P(2) X {6}) + n({15, 20, 25, 30}, 20, {5, 6}) + n({15, 20, 25}, 20, {4, 6}) + n({15, 20}, 25, {3, 6})
n({15, 20, 25, 30}, 20, {5, 6}) = (P(1) X {(5, 6)}) // + n({15}, 0, {2, 5, 6}) (but 0<15) break condition 2
n({15, 20, 25}, 20, {4, 6}) = P(1) X {(4, 6)} // and break condition 2
n({15, 20}, 25, {3, 6}) = P(1) X {(3, 6)} // + n({15}, 5, {2, 3, 6}) (but 5<15) break condition 2
Now developing the second n function of the right side of the equation
n({15, 20, 25, 30}, 70, {5}) = (P(3) X {5}) + n({15, 20, 25}, 40, {4, 5})
n({15, 20, 25}, 40, {4, 5}) = (P(2) X {(4, 5)}) + n({15, 20}, 15, {3, 4, 5})
n({15, 20}, 15, {3, 4, 5}) = P(1) x {(3, 4, 5)} // + n({}, 0, {1, 3, 4, 5}) new break condition aSum is empty
I am working with PHP array to store the comma separated values of product Ids and then in turn use them to show as recent seen products.
Implementation is as below:
$product_data = array();
$array_ids_a_display = array();
$check_status = array();
$array_check= array();
$_COOKIE['Pr'] = [1,2,3,4,5,6,7,8]
Then i am Taking the last point of the stored string
$array_check = explode(',',substr($_COOKIE['IdProduto'],0,-1));
Now, i check of products are enabled then store them into one other array
foreach($array_check as $id_a_checar){
$check_status = $this->model->getProduct($id_a_checar);
if($check_status['status']){ // If status is 1
$array_ids_a_display[$contprods++] = $id_a_checar;
}
}
if($s['limit']>count($array_ids_a_display)){
//If the number of valid products < number of products of module
$s['limit'] = count($array_ids_a_display);
//will show,then reconfigures the number of products that will show
}
}
where $s['limit'] comes from backend , let us say 6 to limit the number of products.
Now, i will reverse the array to get latest visited product at first place like as
$last_ended = array_reverse($array_ids_a_display);
array_splice($last_ended,0,(int)$s['limit']);
foreach ($last_ended as $result) {
$product_data[$result] = $this->product->getProduct($result);
}
Now here comes the problem, as i am only getting 3 products in $product_data array but is shall get 6 products.
I hope that there is issue with array_splice because if i will comment array_splice then i am getting all stores products in cookies as result.
Mysql query is working very fine.
Please advice how to get latest 6 values from array
Here you are:
$last_ended = array(1, 2, 3, 4, 5, 6, 7, 8);
$last_ended = array_reverse($last_ended);
//here is what you missed:
$last_ended = array_splice($last_ended, 0, 6);
print_r($last_ended);
//returns Array ( [0] => 8 [1] => 7 [2] => 6 [3] => 5 [4] => 4 [5] => 3 )
You need to assign your $last_ended var into array_splice result.
<?php
// Get the last 6 entries of an array...
$arr = array(
1,
2,
3,
4,
5,
6,
7,
8
);
$entries = array_splice($arr, -6);
// returns [3, 4, 5, 6, 7, 8]
}
$maxResultLength = 6;
$someArray = [1,2,3,4,5,6,7,8,9,10,'a','b','c','d','e','f','g','h'];
$startIndex = (count($someArray) >= $maxResultLength) ? count($someArray) - $maxResultLength : 0;
$lastSixElements = array_slice($someArray, $startIndex, $maxResultLength);
or
$maxResultLength = 6;
$someArray = [1,2,3,4,5,6,7,8,9,10,'a','b','c','d','e','f','g','h'];
$lastSixElements = array_splice($someArray, -6);
I'm building an online store for a clothes selling company. When displaying the products on page, the user has to be able to filter the clothes by SIZE. I also show them how many clothes belong to the specific sizes.
For this porpuse I use three arrays: $size_names, $size_ids, $size_counts
The first array can hold numeric and string values together, so it could look like this:
array(1, 2, 3, 'M', 'L', 'S')
What I basically want is to sort the values within this array in the following logic:
sort the numerical elements first, ascending
sort alphabetical elements in the following order: XXS, XS, S, M, L, XL, XXL
I read about usort(), but the problem is that I need to reorder all three arrays (size_names, size_ids, size_counts) by the reordering rule of only size_names.
So I have to sort the first array, than based on the sort, I have to sort the other two.
EDIT
One possible scenario with my three arrays could be like:
$size_names = array(3, 1, M, S)
$size_ids = array(1, 2, 3, 4)
$size_counts = array(10, 8, 3, 2)
So based on the array values I can say to the visitor that there are TWO clothes that have size S, which size has the id 4. (The ID value is not shown to the visitor, only helps me building the sql for filtering.)
I currently use array_multisort:
array_multisort($size_names, $size_ids, $size_counts);
which produces the following result:
$size_names = array(1, 3, M, S)
$size_ids = array(2, 1, 3, 4)
$size_counts = array(8, 10, 3, 2)
This is half way there, because the numeric values are sorted in the desired order, but the alphabetical values are sorted alphabetically, which is not what I want.
The desired order should be:
$size_names = array(1, 3, S, M)
$size_ids = array(2, 1, 4, 3)
$size_counts = array(8, 10, 2, 3)
Replace the alphabetical elements with numbers, do the sorting and then bring the alphabetical elements back. The following is an example:
$size_names = array(3, 1, "M", "S");
$size_ids = array(1, 2, 3, 4);
$size_counts = array(10, 8, 3, 2);
$arr1 = array("XXS", "XS", "S", "M", "L", "XL", "XXL");
$arr2 = range(1001, 1007);
$size_names = str_replace($arr1, $arr2, $size_names);
array_multisort($size_names, $size_ids, $size_counts);
$size_names = str_replace($arr2, $arr1, $size_names);
DEMO
I have an array within an array, for example:
[
[0, 20, 5],
[5, 0, 15],
[5, 10, 0]
]
I need to get the max number in each column.
The max of [0 , 5 , 5] is 5, so that goes into the result array.
The max of [20, 0, 10] is 20, so that goes into the result array.
The max of [5, 15, 0] is 15, so that goes into the result array.
The final result array must contain [5, 20, 15].
First, the array has to be transposed (flip the rows and columns):
function array_transpose($arr) {
$map_args = array_merge(array(NULL), $arr);
return call_user_func_array('array_map', $map_args);
}
(taken from Is there better way to transpose a PHP 2D array? - read that question for an explanation of how it works)
Then, you can map the max function on the new array:
$maxes = array_map('max', array_transpose($arr));
Example: http://codepad.org/3gPExrhO
Output [I think this is what you meant instead of (5, 15, 20) because you said index 1 should be max of (20, 0, 10)]:
Array
(
[0] => 5
[1] => 20
[2] => 15
)
Without the splat operator, array_map() with max() will return the max value for each row. ([20, 15, 10])
With the splat operator to transpose the data structure, the max value for each column is returned.
Code: (Demo)
$array = [
[0, 20, 5],
[5, 0, 15],
[5, 10, 0]
];
var_export(
array_map('max', ...$array)
);
Output:
array (
0 => 5,
1 => 20,
2 => 15,
)
I've searched for this but can't seem to find the exact answer. I want to use array_multisort to simultanously sort 3 arrays based on numeric values in 3 of the arrays. Basically I want to make a "standings" table similar to what you'd see for NFL/NHL standings etc. I have 3 arrays, tempIDs (string), tempWins (numeric), tempWinPercentage (numeric). I need all 3 to be sorted at the same time based first on wins, and then if there is a tie, win percentage.
I can't seem to get array_multisort to work with more than just 2 arrays, so maybe I'm misunderstanding the terminology when they say that it can work with "several" arrays. Thank you!
You should have a data array like this:
$data = array(
0 => array(
'tempIDs' => 'something',
'tempWins' => 10,
'tempWinPercentage' => 50,
),
1 => array(
'tempIDs' => 'something else',
'tempWins' => 10,
'tempWinPercentage' => 60,
),
3 => array(
'tempIDs' => 'something more',
'tempWins' => 20,
'tempWinPercentage' => 50,
),
);
Then sort this array using usort($data, 'my_sort_cb')
Your callback method should first compare tempWins, and if they are equal, compare tempWinPercentages:
function my_sort_cb($a, $b) {
if ($a['tempWins'] > $b['tempWins']) return 1;
if ($a['tempWins'] < $b['tempWins']) return -1;
if ($a['tempWinPercentage'] > $b['tempWinPercentage']) return 1;
if ($a['tempWinPercentage'] < $b['tempWinPercentage']) return -1;
return 0;
}
(this can be made shorter)
I can't seem to get array_multisort to
work with more than just 2 arrays, so
maybe I'm misunderstanding the
terminology when they say that it can
work with "several" arrays. Thank you!
I think they mean it can be used for sorting more than two arrays, but the other arrays will be sorted basing on the first one.
In example, executing this code
$a1 = array(12, 23, 34, 45, 45, 34);
$a2 = array(234, 56, 243, 456, 34, 346);
$a3 = array(654, 56, 8, 12, 56, 90);
array_multisort($a1, $a2, $a3);
you will get the arrays sorted as if the would have been defined as
$a1 = array(12, 23, 34, 34, 45, 45);
$a3 = array(654, 56, 8, 90, 56, 12);