I have an if statement that needs to check if a series of items exist AND if any other items exist.
So for example
foreach($id_cart as $id) {
if(($id == 4 || $id == 5) && **IF ANY OTHER ITEM EXISTS**){
echo "Yes";
}
Is there a simple way of doing that?
An item exists or it does not. $id_cart will have, let's say, ids: 4,5,6,8,12,14
There is nothing else stored. So if 4 or 5 are there, plus any other number...
If your question is asking
"I have a list of ids, from which I want to know if one is there, and apart of that one, if other items exist", one course of action could be the following:
if the item you look for exists, remove it from the array and check if it still has any members
if count(array) is > 0 do this; else do that;
else wasn't there or was 'alone'.
This course of action is very optimizable: for instance, your original array would lose members at each iteration. One easy change would be just do count(array) - 1 if you don't care which members the array contains.
Perhaps this does what you want:
foreach ($id_cart as $id) {
if (($id == 4 || $id == 5) && count(array_diff($id_cart, array(4, 5)))) {
// do something
}
}
or, better:
if ((in_array(4, $id_cart) || in_array(5, $id_cart)) && count(array_diff($id_cart, array(4, 5)))) {
// four or five exists, while other elements are also in the array
// do something
}
<?php
$a = array(4, 5);
if (array_intersect($id_cart, $a) && array_diff($id_cart, $a))
{
echo "Yes\n";
} else {
echo "No\n";
}
Tested:
4,5: No
4,6: Yes
5,6: Yes
6,8: No
4,5,6,8,12,14: Yes
See array_intersect() and array_diff().
Intersect with array(4,5) tests for presence of either 4 or 5, because the result would be empty if neither value occurred in $id_cart.
Diff with array(4,5) tests for presence of another value besides 4 and 5, because the result would be empty if no value but 4 or 5 occurred in $id_cart.
Using count() to test for a non-empty array is unnecessary. An empty array evaluates as false in a condition.
Re Adriano's comment about simplicity or efficiency: PHP is tricky this way. Some functions are more efficient than others, so it's hard to say 2 function calls is better than 3. I tried running both my solution and Adriano's, and measuring elapsed time using microtime():
Bill's solution: 6.25 seconds for 100000 iterations
Adriano's solution: 5.59 seconds for 100000 iterations
So they're not equal, but Adriano's is only 10.5% faster. Close enough that I'd choose a solution for readability instead of for performance. If optimal performance were one's highest priority, one wouldn't be using PHP in the first place. :-)
FWIW, Adriano's other solution using foreach took 7.13 seconds for 100000 iterations.
this will find items which are not 4 or 5
$others = array_diff($id_cart, array(4, 5));
this will find items whose keys are not 4 or 5
$others = array_diff_key($id_cart, array_flip(array(4, 5)));
An efficient way is to store the ID's in the array's keys and check for their existence with isset().
PHP's arrays are dictionaries.
Take advantage of this feature, instead of wasting time with in_array().
Related
I have this array:
Array
(
[10:00:00] => 15
[10:30:00] => 15
[11:00:00] => 8
)
I need to see if any of them equal 15, if so do something.
There will be more items being added in the future. The items are all time slots and the number on the right is the amount of bookings. No more than 15 bookings per time slot.
The times will go into a form as select options. I either need to show all and disable the fully booked ones, or only show the available slots.
The other way it could be done is by looking for all the ones that don't equal 15 and then adding them to the select.
I will of course include some server side validation to stop more than 15 being allocated to a time slot.
This is one way of doing it ...
if (in_array(15, $myArray)) {
// Do something
}
... as #AbraCadaver mentioned, it is from the official PHP manual at https://php.net/manual/en/function.in-array.php
To get an array of all of those NOT equal to 15:
$result = array_filter(function($v) { return $v != 15; }, $array);
To check if a value is in an array you can use in_array, however for your use case you are looking to eliminate ALL of the 15 values from your select. So just build an array of what you want.
Andreas's comment didn't occur to me at first but is how I would normally do it:
$result = array_diff($array, [15]);
You could use array_count_values() along with isset().
if(isset(array_count_values($myArray)[15])) {
//do something
}
Basically, array_count_values() will return an array, which, with your example would be:
[15 => 2, 8 => 1]
Which is just how many times each value appeared in the original array. Then we use isset() to check if the key 15 exists in the output from array_count_values().
There are different build in methods in php to solve this.
some of them are.
in_array
array_search
array_filter
array_reduce
array_diff
array_intersect
here is how you do on them
in_array
if (in_array(15, $array)) // do your stuff here
array_search
if (array_search(15, $array) !== false) // do your stuff here
array_filter
if (! empty(array_filter($array, function ($a) { return $a == 15; })) // do your stuff here
array_reduce
if (array_reduce($array, function ($c, $d) { return $c = $c == 15 || $d == 15 ? 15 : 0; }) -- 15) // do your stuff here
array_diff
if (count(array_diff($array, [ 15 ])) != count($array)) // do your stuff here
array_intersect
if (! empty(array_intersect($array, [15]))) // do your stuff here
There are many more methods, depending on the logic you use.
Since you want to show a select box, you will need to iterate over the array anyway. The only senseful condition before doing so could be whether or not already all slots are booked. Then you might not want to show the <select> at all but some notice instead. Looking if at least one is 15 does not seem to make sense.
count($slots) === (array_count_values($slots)[15]??0) is true when all slots are booked.
The complete code generating enabled/disabled <options> or no <select> box at all could look like:
$slots =
[
'10:00:00' => 15,
'10:30:00' => 15,
'11:00:00' => 8,
];
if(count($slots) === (array_count_values($slots)[15]??0))
{
?>
<div>No slots are available anymore.</div>
<?php
}
else
{
?>
<select name="slot">
<option value="">select a time slot</option>
<?php
foreach ($slots as $k=>$v)
{
$avail = 15-intval($v);
?>
<option value="<?php echo $k;?>"<?php if($avail < 1) echo ' disabled'?>><?php echo "$k ($avail available)";?></option>
<?php
}
?>
</select>
<?php
}
I need to calculate the sum of the values in array where key index is lower than... something.
I have done that, by this:
$temp_sum = 0;
for($temp_start = 0; $temp_start < 10; $temp_start++)
{
$temp_sum += $array[$temp_start];
}
But my question is... Another way to do this in more fashion way?
With usage of array functions? Maybe one specific array function for this task?
This for loop (or using even foreach loop) doesn't look nice and proper - but maybe it's only way to use standard looping.
for loops allow multiple statements inside its expressions, separated by commas. So, you could instantiate $temp_sum inside the for construct, to make it a little more concise:
for($temp_sum = 0, $temp_start = 0; $temp_start < 10; $temp_start++)
{
$temp_sum += $array[$temp_start];
}
I don't know if it necessarily helps readability though. Also, you might want to make sure there's at least 10 elements in $array to begin with, if you are not already verifying this.
Another alternative could be:
$sum = array_sum( array_slice( $array, 0, 10 ) );
but this requires the array keys to start at 0 and be adjacent and sequentially sorted. In other words, this array:
$array = [
13 => 12,
31 => 23,
1 => 24,
0 => 21
/* ... */
];
will create an undesirable result.
Not sure if it's more efficient either, but it's a nice one-liner and it has the possibly added benefit that it won't complain if there's less than 10 elements inside the array. You'll have to decide if that is desirable or not.
All in all, I think your initial use of the for loop is a pretty good example of a typical use-case. foreach loops, for example, are typically used in cases where you want to iterate all elements, not just a limited set.
I executed the following code and its result made me confused!
I pass two arrays and a function named "myfunction" as arguments to the array_diff_ukey function. I see that myfunction is called 13 times (while it should be called at most 9 times). Even more amazing is that it compares the keys of the same array too! In both columns of the output, I see the key "e", while only the second array has it (the same is true for some other keys).
function myfunction($a,$b) {
echo $a . " ".$b."<br>";
if ($a===$b) {
return 0;
}
return ($a>$b)?1:-1;
}
$a1=array("a"=>"green","b"=>"blue","c"=>"red");
$a2=array("d"=>"blue","e"=>"black","f"=>"blue");
$result=array_diff_ukey($a1,$a2,"myfunction");
print_r($result);
Output:
a b
b c
d e
e f
a d
a e
a f
b d
b e
b f
c d
c e
c f
Array
(
[a] => green
[b] => blue
[c] => red
)
See it run on eval.in.
Why does the array_diff_ukey perform that many unnecessary calls to the compare function?
Nice question. Indeed the implemented algorithm is not the most efficient.
The C-source for PHP array functions can be found github. The implementation for array_diff_ukey uses a C-function php_array_diff which is also used by the implementations of array_udiff, array_diff_uassoc, and array_udiff_uassoc.
As you can see there, that function has this C-code:
for (i = 0; i < arr_argc; i++) {
//...
zend_sort((void *) lists[i], hash->nNumOfElements,
sizeof(Bucket), diff_key_compare_func, (swap_func_t)zend_hash_bucket_swap);
//...
}
...which means each input array is sorted using the compare function, explaining the first series of output you get, where keys of the same array are compared, and the first column can list other keys than the those of the first array.
Then it has a loop on the elements of the first array, a nested loop on the other arrays, and -- nested in that -- a loop on the elements of each of those:
while (Z_TYPE(ptrs[0]->val) != IS_UNDEF) {
//...
for (i = 1; i < arr_argc; i++) {
//...
while (Z_TYPE(ptr->val) != IS_UNDEF &&
(0 != (c = diff_key_compare_func(ptrs[0], ptr)))) {
ptr++;
}
//...
}
//...
}
Evidently, the sorting that is done on each of the arrays does not really contribute to anything in this algorithm, since still all keys of the first array are compared to potentially all the keys of the other array(s) with a plain 0 != comparison. The algorithm is thus O(klogk + nm), where n is the size of the first array, and m is the sum of the sizes of the other arrays, and k is the size of the largest array. Often the nm term will be the most significant.
One can only guess why this inefficient algorithm was chosen, but it looks like the main reason is code reusability: as stated above, this C code is used by other PHP functions as well, where it may make more sense. Still, it does not really sound like a good excuse.
A simple implementation of this (inefficient) array_diff_ukey algorithm in PHP (excluding all type checking, border conditions, etc) could look like this mimic_array_diff_ukey function :
function mimic_array_diff_ukey(...$args) {
$key_compare_func = array_pop($args);
foreach ($args as $arr) uksort($arr, $key_compare_func);
$first = array_shift($args);
return array_filter($first, function ($key) use($key_compare_func, $args) {
foreach ($args as $arr) {
foreach ($arr as $otherkey => $othervalue) {
if ($key_compare_func($key, $otherkey) == 0) return false;
}
}
return true;
}, ARRAY_FILTER_USE_KEY);
}
A more efficient algorithm would use sorting, but then would also take benefit from that and step through the keys of the first arrays while at the same time stepping through the keys of the other arrays in ascending order, in tandem -- never having to step back. This would make the algorithm O(nlogn + mlogm + n+m) = O(nlogn + mlogm).
Here is a possible implementation of that improved algorithm in PHP:
function better_array_diff_ukey(...$args) {
$key_compare_func = array_pop($args);
$first = array_shift($args);
$rest = [];
foreach ($args as $arr) $rest = $rest + $arr;
$rest = array_keys($rest);
uksort($first, $key_compare_func);
usort($rest, $key_compare_func);
$i = 0;
return array_filter($first, function ($key) use($key_compare_func, $rest, &$i) {
while ($i < count($rest) && ($cmp = $key_compare_func($rest[$i], $key)) < 0) $i++;
return $i >= count($rest) || $cmp > 0;
}, ARRAY_FILTER_USE_KEY);
}
Of course, this algorithm would need to be implemented in C if taken on board for improving array_diff_ukey, and to get a fair runtime comparison.
See the comparisons that are made -- on a slightly different input than in your question -- by the three functions (array_diff_ukey, mimic_array_diff_ukey and better_array_diff_ukey) on eval.in.
array_diff_ukey runs in two stages:
Sort the array keys
Compare key by key
This would probably explain why the callback is expected to return a sort value rather than a boolean "is equal".
I expect this is probably done for performance reasons, but if that's the case I would have thought that it can use this to say "well this key is bigger than all keys in the other array, so I shouldn't bother testing if these other, bigger keys are also bigger because they must be", but this doesn't seem to be the case: it compares them dutifully anyway.
I can only assume it's because the function cannot prove itself to be deterministic (and indeed in this case produces side-effects) so it can't be optimised like that. Perhaps array_diff_key (without user-defined function) does this optimisation just fine.
But anyway, that's what happens under the hood, and why you see more than just 9 comparisons. It could probably be made better in the core...
How to get random value from array set an value in array that will be one to selected more than other?
array('00','01','02');
I want to select an random value from this array with most selected value be '00', i.e the '00' value will be selected 80% and other two values will be 20%.
Here array can have many values, its only an example with three values
$main_array=array('00','01','02');
$priority=array(0,0,0,0,0,0,0,0,1,2);
$rand_index=array_rand($priority);//select a random index from priority array.. $priority[$rand_index] will give you index 0 or 1 or 2 with your priority set..
echo $main_array[$priority[$rand_index]];
I think the code is self explanatory...
Array will have many elements case will come when lets say the requirement will come like 3% probability of "00" 28% prob. of "01" rest to other elements...In that case use array_fill function to fill elements in masses...and array_merge them
Like for the same case I've taken answer will be
$main_array=array('00','01','02');
$priority1=array_fill(0,69,2);
$priority2=array_fill(0,28,1);
$priority3=array_fill(0,3,0);
$priority=array_merge($priority1,$priority2,$priority3);
$rand_index=array_rand($priority);
echo $main_array[$priority[$rand_index]];
Here's an idea.
$arr = array('00', '01', '02');
$randInt = rand(1, 10);
if ($randInt <= 8) {
$value = $arr[0];
}
else if ($randInt == 9) {
$value = $arr[1];
}
else { // ($randInt == 10)
$value = $arr[2];
}
There is now an 80% chance that $value contains '00', a 10% chance that it contains '01', and a 10% chance it contains '02'. This may not be the most efficient solution, but it will work.
one of many options
$a = array_fill(0, 8, '0');
$a['9']='1';
$a['10']='2';
echo $a[array_rand($a,1)];
Use a parallel array with the probability distribution, e.g.:
array('80', '10', '10');
Use standard random number function to generate a number between 0 and 99, then look it up in this array. The index you find it at is then used as the index to the values array.
I have an array that reflects rebate percentages depending on the number of items ordered:
$rebates = array(
1 => 0,
3 => 10,
5 => 25,
10 => 35)
meaning that for one or two items, you get no rebate; for 3+ items you get 10%, for 5+ items 20%, for 10+ 35% and so on.
Is there an elegant, one-line way to get the correct rebate percentage for an arbitrary number of items, say 7?
Obviously, this can be solved using a simple loop: That's not what I'm looking for. I'm interested whether there is a core array or other function that I don't know of that can do this more elegantly.
I'm going to award the accepted answer a bounty of 200, but apparently, I have to wait 24 hours until I can do that. The question is solved.
Here's another one, again not short at all.
$percent = $rebates[max(array_intersect(array_keys($rebates),range(0,$items)))];
The idea is basically to get the highest key (max) which is somewhere between 0 and $items.
I think that the above one-line solutions aren't really elegant or readable. So why not use something that can really be understood by someone on first glance?
$items = NUM_OF_ITEMS;
$rabate = 0;
foreach ($rabates as $rItems => $rRabate) {
if ($rItems > $items) break;
$rabate = $rRabate;
}
This obviously needs a sorted array, but at least in your example this is given ;)
Okay, I know, you don't want the solution with the simple loop. But what about this:
while (!isset($rabates[$items])) {
--$items;
}
$rabate = $rabates[$items];
Still quite straightforward, but a little shorter. Can we do even shorter?
for (; !isset($rabates[$items]); --$items);
$rabate = $rabates[$items];
We are already getting near one line. So let's do a little bit of cheating:
for (; !isset($rabates[$items]) || 0 > $rabate = $rabates[$items]; --$items);
This is shorter then all of the approaches in other answers. It has only one disadvantage: It changes the value of $items which you may still need later on. So we could do:
for ($i = $items; !isset($rabates[$i]) || 0 > $rabate = $rabates[$i]; --$i);
That's again one character less and we keep $items.
Though I think that the last two versions are already too hacky. Better stick with this one, as it is both short and understandable:
for ($i = $items; !isset($rabates[$i]); --$i);
$rabate = $rabates[$i];
This might work without changing the rebate array.
But the array must be constructed in another way for this to work
$rebates = array(
3 => 0, //Every number below this will get this rebate
5 => 10,
10 => 25,
1000 => 35); //Arbitrary large numer to catch all
$count = $_REQUEST["count"];
$rv = $rebates[array_shift(array_filter(array_keys($rebates), function ($v) {global $count; return $v > $count;}))];
echo $rv;
Working testcase, just change count in url
http://empirium.dnet.nu/arraytest.php?count=5
http://empirium.dnet.nu/arraytest.php?count=10
Best I can manage so far:
$testValue = 7;
array_walk( $rebates, function($value, $key, &$test) { if ($key > $test[0]) unset($test[1][$key]); } array($testValue,&$rebates) );
Uses a nasty little quirk of passing by reference, and strips off any entry in the $rebates array where the key is numerically greater than $testValue... unfortunately, it still leaves lower-keyed entries, so an array_pop() would be needed to get the correct value. Note that it actively reduces the entries in the original $rebates array.
Perhaps somebody can build on this to discard the lower entries in the array.
Don't have 5.3.3 available to hand at the moment, so not tested using an anonymous function, but works (as much as it works) when using a standard callback function.
EDIT
Building on my previous one-liner, adding a second line (so probably shouldn't count):
$testValue = 7;
array_walk( $rebates, function($value, $key, &$test) { if ($key > $test[0]) unset($test[1][$key]); } array($testValue,&$rebates) );
array_walk( array_reverse($rebates,true), function($value, $key, &$test) { if ($key < $test[0]) unset($test[1][$key]); } array(array_pop(array_keys($rebates)),&$rebates) );
Now results in the $rebates array containing only a single element, being the highest break point key from the original $rebates array that is a lower key than $testValue.