I have been reading through the manual to find a function that does what I want but I ended up doing it myself. I want to compare two arrays and calculate the difference between the keys. Or more practically analyse the difference in order of the values.
I have done this as follows, but I have a feeling this can be done better.
if anyone has an idea how to improve this please let me know becasue im eager to improve.
<?php
$goodarray = array(300,250,200,150,100);
$usersupliedarray = array(250,300,200,150,100); // first two spots are wrong
$score = count($goodarray);
foreach($usersupliedarray as $key => $value){
$arraykey = array_search($value, $goodarray);
$difference = abs($key-$arraykey);
$score = $score + $difference;
echo "$value $goodarray[$key] ($difference = $score) <hr />";
}
array_map with a void callback can come in handy here, for example,
$a = array(300,250,200,150,100);
$b = array(250,300,200,150,100);
$faults = 0;
foreach(array_map(null, $a, $b) as $x)
$faults += $x[0] != $x[1]; // x[0] is $a element, x[1] is $b
print $faults; // 2
UPD: if you want to compute distances between equal elements, and not just count differences, your original code looks just fine to me. One improvement which can be made is to get rid of inefficient array_search and to use an "inverted index" of the first array instead:
foreach($a as $pos => $val)
$inv[$val] = $pos;
or just
$inv = array_flip($a);
and then
foreach($b as $pos => $val)
$score += abs($pos - $inv[$val]);
Related
I'm trying to combine numbers in an array by adding them so that the max value can only by 30.
For example, this is my array:
array(10,30,10,10,15);
After combining the numbers in the array to items with a max value 30, the result should be:
array(30,30,15);
How to achieve this?
I'm trying to combine numbers in an array by adding them so that the
max value can only by 30
So, when you combine numbers, you can achieve the lowest possible set of values in your array and also make sure that max value remains 30 by:
First, sort them.
Second, keeping adding elements to sum till you are about to get a sum > 30.
Third, once an element can no longer be added to a sum, add the current sum in your array and make the current element as the new sum.
Code:
<?php
$arr = array(10,30,10,10,15);
sort($arr);
$res = [];
$curr_sum = 0;
foreach($arr as $each_value){
if($curr_sum + $each_value <= 30) $curr_sum += $each_value;
else{
$res[] = $curr_sum;
$curr_sum = $each_value;
}
}
$res[] = $curr_sum;
print_r($res);
Demo: https://3v4l.org/BYhuE
Update: If order of the numbers matters, seeing your current output, you could just use rsort() to show them in descending order.
rsort($res);
$total = array_sum(array(10,30,10,10,15)); //assign sum totals from orignal array
$maxValue = 30; //assign max value allowed in array
$numberOfWholeOccurancesOfMaxValue = floor($total/$maxValue);
$remainder = $total%$maxValue;
//build array
$i=0;
while ( $i < $numberOfWholeOccurancesOfMaxValue ){
$array[] = $maxValue;
$i++;
}
$array[] = $remainder;
print_r($array);
You can loop only once to get this,
$temp = array(10,30,10,10,15);
natsort($temp); // sorting to reduce hustle and complication
$result = [];
$i = 0;
$maxValue = 30;
foreach($temp as $v){
// checking sum is greater or value is greater or $v is greater than equal to
if(!empty($result[$i]) && (($result[$i]+$v) > $maxValue)){
$i++;
}
$result[$i] = (!empty($result[$i]) ? ($result[$i]+$v) : $v);
}
print_r($result);
Working demo.
I believe finding most space-optimized/compact result requires a nested loop. My advice resembles the firstFitDecreasing() function in this answer of mine except in this case the nested loops are accessing the same array. I've added a couple of simple conditions to prevent needless iterations.
rsort($array);
foreach ($array as $k1 => &$v1) {
if ($v1 >= $limit) {
continue;
}
foreach ($array as $k2 => $v2) {
if ($k1 !== $k2 && $v1 + $v2 <= $limit) {
$v1 += $v2;
unset($array[$k2]);
if ($v1 === $limit) {
continue 2;
}
}
}
}
rsort($array);
var_export($array);
By putting larger numbers before smaller numbers before processing AND by attempting to add multiple subsequent values to earlier values, having fewer total elements in the result is possible.
See my comparative demonstration.
I believe #Clint's answer is misinterpreting the task and is damaging the data by summing all values then distributing the max amounts in the result array.
With more challenging input data like $array = [10,30,5,10,5,13,14,15,10,5]; and $limit = 30;, my solution provides a more dense result versus #nice_dev's and #rahul's answers.
Title is lengthy and confusing, forgive me.
$array = (1,5,10,25,50);
$x = 8
How would I compare $x to each value within the array, and then select the value with the closest match.
In this case, it would be 10.
I imagined creating a handful of if statements but thought there could be a better way to do this.
Thanks in advance
Another way, using an intermediate array with the differences:
$diff = array();
foreach($array as $n)
$diff[$n] = abs($x - $n); // key = number, value = difference
// get the key that contains the smallest difference
$closest = array_search(min($diff), $diff);
$min = 0;
foreach ($array AS $i => $v) {
if (abs($array[$min] - $x) > abs($v - $x))
$min = $i;
// you can optimize this with :
if ($v == $x)
break;
}
$closest = $array[$min];
Something like that should work.
For instance, i have a 2 list of values that has no relevance with each elements. I'm planning to put this values manually.
$a1 = 'red';
$a2 = '007';
$a3 = 'gun';
$a4 = 'apple';
$b1 = 'poison';
$b2 = 'movie';
$b3 = 'man';
$b4 = 'store';
echo $a.' with '.$b
I want an output like:
red with poison
007 with movie
gun with man
apple with store
So I want $an displayed with $bn. I have thought of using a for loop, but I don't know how to do it (I'm really new to PHP...)
Any suggestions? any help would be great. thanks in advance!
Use arrays :
$a[0] = 'red';
$a[1] = '007';
$a[2] = 'gun';
$a[3] = 'apple';
$b[0] = 'poison';
$b[1] = 'movie';
$b[2] = 'man';
$b[3] = 'store';
foreach($a as $key=>$value)
{
echo $value.' with '.$b[$key];
}
I think the simplest is to use a associative array and foreach like this:
foreach (array("red" => "poison",
"007" => "movie",
"gun" => "man",
"apple" => "store") as $a => $b) {
echo "$a with $b\n";
}
change your setup to an array
$a[1] = 'red';
$a[2] = '007';
$a[3] = 'gun';
$a[4] = 'apple';
$b[1] = 'poison';
$b[2] = 'movie';
$b[3] = 'man';
$b[4] = 'store';
then use a for loop
for($n=0;$n<sizeof($a);$n++){
echo $a[$n] . ' with ' . $b[$n];
}
with this method you can call any part of the variable with its known number at any time elsewhere in the script, such as:
echo $b[2];
This is an explanation on the for loop.
for() This calls the loop and is limited by { and }
The arguments in the loop start with your starter.
In my case I used $n. I set $n to 0 to give the loops somewhere to start from.
The next part of the loop is how many times you want it iterated in comparison to your start variable. This is usually dependent on the size of an array but can also be a number. The argument I created here to determine the iterations. sizeof() is another function within php to get the number of elements within an array or variable (or i think it can do file sizes too). I am telling it to make sure that as long as $n is less than the size of the variable to keep looping. You normally want a loop to end after a while else pages take forever to load.
Finally you add the thing at the end to increase the counter so to speak. This can be anything once again but using $n++ means it increases the value of $n by 1 each time until the end of the loop
You can use arrays:
//that's how you add elements to an array
$a[] = 'red';
$a[] = '007';
$a[] = 'gun';
$a[] = 'apple';
$b[] = 'poison';
$b[] = 'movie';
$b[] = 'man';
$b[] = 'store';
//and this is how you read from an array
for($i=0; $i<count($a); $i++){
echo $a[$i].' with '.$b[$i] . "\n";
}
for (var $i = 1; $i <= 4; $i++) {
echo ${"a".$i} . " with " . ${"b".$i};
}
see http://php.net/manual/en/language.variables.variable.php
Imagine a simple, but large array with keys 0 to 100000.
When doing a foreach loop of this array, is it possible to 'seek' ahead without doing something like:
foreach($array as $key=>$value){
if($key<10000){
continue;
}
}
We do this kind of operation once in a while thru our codebase. It seams like a bit of a waste of ticks to go thru each of the keys until key is greater then 10000.
Is this possible in php 5.4?
Thanks.
it was possible even in PHP 2.0FI or ALTAIR BASIC
for($i=10000;$i < count($array);$i++){
}
No doubt some nitpickers will come to tell that doing count($array) 90000 times is a waste of ticks too.
However, to get a real performance gain one have to avoid lengthy loops at all.
$rest = array_slice($array, 10000);
Depending on what you want to achieve (here: what you want to do after seeking)
for ($length = count($array), $key = 10000; $key < $length, $key++) {
$value = $array[$key];
}
Assuming the keys are consecutive integers:
$count = count($array);
for ($key = 10000; $key < $count; ++$key) {
$value = $array[$key];
}
I'm not sure if count is O(1) though, so if it's not, you might be better off doing:
$key = 0;
while (isset($array[$key])) {
$value = $array[$key];
++$key;
}
Note that array_key_exists would be required if the key could be considered not set yet exist in the array.
Borrowing from this solution, this would do the trick and set the array pointer at the element you want. This would be the closest you get to seeking the array and not just specifying the interval of keys to loop through.
$start = 10000; // or what ever number you're starting at
while(key($array) < $start) next($array);
You can't use this if you plan to use a foreach-loop (as it resets the pointer), but should be good if you iterate the rest of the array like this
$count = count($array);
do {
$key = key($array);
$value = current($array);
} while($key < $count);
I need to combine two foreach statement into one for example
foreach ($categories_stack as $category)
foreach ($page_name as $value)
I need to add these into the same foreach statement
Is this possible if so how?
(I am not sure I have understood your question completely. I am assuming that you want to iterate through the two lists in parallel)
You can do it using for loop as follows :
$n = min(count($category), count($value));
for($c = 0; $c < $n; $c = $c + 1){
$categories_stack = $category[$c];
$pagename = $value[$c];
...
}
To achieve the same with foreach you need a function similar to Python's zip() function.
In Python, it would be :
for categories_stack, pagename in zip(categories, values):
print categories_stack, pagename
Since PHP doesn't have a standard zip() function, you'll have to write such a function on your own or go with the for loop solution.
You can do nested foreachs if that's what you want. But without knowing more of your data, it's impossible to say if this helps:
foreach ($categories_stack as $category) {
foreach ($page_name as $value) {
}
}
Probably you want to print out all pages in a category? That probably won't work, so can you give a bit more info on how the arrays look like and relate to each other?
This loop will continue to the length of the longest array and return null for where there are no matching elements in either of the arrays. Try it out!
$a = array(1 => "a",25 => "b", 10 => "c",99=>"d");
$b = array(15=>1,5=>2,6=>3);
$ao = new ArrayObject($a);
$bo = new ArrayObject($b);
$ai = $ao->getIterator();
$bi = $bo->getIterator();
for (
$ai->rewind(),$bi->rewind(),$av = $ai->current(),$bv = $bi->current();
list($av,$bv) =
array(
($ai->valid() ? $ai->current() : null),
($bi->valid() ? $bi->current() : null)
),
($ai->valid() || $bi->valid());
($ai->valid() ? $ai->next() : null),($bi->valid() ? $bi->next() : null))
{
echo "\$av = $av\n";
echo "\$bv = $bv\n";
}
I cannot really tell from the question exactly how you want to traverse the two arrays. For a nested foreach you simply write
foreach ($myArray as $k => $v) {
foreach ($mySecondArray as $kb => $vb {
}
}
However you can do all sorts of things with some creative use of callback functions. In this case an anonymous function returning two items from each array on each iteration. It's then easy to use the iteration value as an array or split it into variables using list() as done below.
This also has the added benefit of working regardless of key structure. I's purely based on the ordering of array elements. Just use the appropriate sorting function if the elements are out of order.
It does not worry about the length of the arrays as there is no error reported, so make sure you keep an eye out for empty values.
$a = array("a","b","c");
$b = array(1,2,3);
foreach (
array_map(
create_function(
'$a,$b', 'return array($a,$b);'
)
,$a,$b
)
as $value
)
{
list($a,$b) = $value;
echo "\$a = $a\n";
echo "\$b = $b\n";
}
Output
$a = a
$b = 1
$a = b
$b = 2
$a = c
$b = 3
Here's another one for you that stops on either of the lists ending. Same as using min(count(a),count(b). Useful if you have arrays of same length. If someone can make it continue to the max(count(a),count(b)) let me know.
$ao = new ArrayObject($a);
$bo = new ArrayObject($b);
$ai = $ao->getIterator();
$bi = $bo->getIterator();
for (
$ai->rewind(),$bi->rewind();
$av = $ai->current(),$bv=$bi->current();
$ai->next(),$bi->next())
{
echo "\$av = $av\n";
echo "\$bv = $bv\n";
}
This is where the venerable for loop comes in handy:
for(
$i = 0,
$n = sizeof($categories_stack),
$m = sizeof($page_name);
$i < $n && $i < $m;
$i++
) {
$category = $categories_stack[$i];
$value = $page_name[$i];
// do stuff here ....
}
Surely you can just merge the arrays before looping?
$data = array_merge($categories_stack, $page_name);
foreach($data AS $item){
...
}
Do the array elements have a direct correspondence with one another, i.e. is there an element in $page_name for each element in $categories_stack? If so, just iterate over the keys and values (assuming they have the same keys):
foreach ($categories_stack as $key => $value)
{
$category = $value;
$page = $page_name[$key];
// ...
}
Could you just nest them with variables outside the scope of the foreach, or prehaps store the content as an array similar to a KVP setup? My answer is vague but I'm not really sure why you're trying to accomplish this.