I am using array_diff() to take values out of array1 which are found in array2. The issue is it removes all occurrences from array1, as the PHP documentations makes note of. I want it to only take out one at a time.
$array1 = array();
$array1[] = 'a';
$array1[] = 'b';
$array1[] = 'a';
$array2 = array();
$array2[] = 'a';
It should return an array with one 'a' and one 'b', instead of an array with just 'b';
Just for the fun of it, something that just came to mind. Will work as long as your arrays contain strings:
$a = array('a','b','a','c');
$b = array('a');
$counts = array_count_values($b);
$a = array_filter($a, function($o) use (&$counts) {
return empty($counts[$o]) || !$counts[$o]--;
});
It has the advantage that it only walks over each of your arrays just once.
See it in action.
How it works:
First the frequencies of each element in the second array are counted. This gives us an arrays where keys are the elements that should be removed from $a and values are the number of times that each element should be removed.
Then array_filter is used to examine the elements of $a one by one and remove those that should be removed. The filter function uses empty to return true if there is no key equal to the item being examined or if the remaining removal count for that item has reached zero; empty's behavior fits the bill perfectly.
If neither of the above holds then we want to return false and decrement the removal count by one. Using false || !$counts[$o]-- is a trick in order to be terse: it decrements the count and always evaluates to false because we know that the count was greater than zero to begin with (if it were not, || would short-circuit after evaluating empty).
Write some function that removes elements from first array one by one, something like:
function array_diff_once($array1, $array2) {
foreach($array2 as $a) {
$pos = array_search($a, $array1);
if($pos !== false) {
unset($array1[$pos]);
}
}
return $array1;
}
$a = array('a', 'b', 'a', 'c', 'a', 'b');
$b = array('a', 'b', 'c');
print_r( array_diff_once($a, $b) );
Related
Suppose you have two arrays $a=array('apple','banana','canaple'); and $b=array('apple');, how do you (elegantly) extract the numeric indices of elements in array a that aren't in array b? (in this case, indices: 1 and 2).
In this case, array a will always have more elements than b.
Note, this is not asking for array_diff_key, but rather the numeric indices in the array with more elements that don't exist in the array with fewer elements.
array_diff gets you half way there. Using array_keys on the diff gets you the rest of what you want.
$a = ['apple','banana','canaple'];
$b = ['apple'];
$diff = array_diff($a, $b);
$keys = array_keys($diff);
var_dump($keys); // [1, 2]
This is because array_diff returns both the element and it's key from the first array. If you wanted to write a PHP implementation of array_diff it might look something like this...
function array_diff(Array ... $arrays) {
$return = [];
$cmp = array_shift($arrays);
foreach ($cmp as $key => $value) {
foreach($arrays as $array) {
if (!in_array($value, $array)) {
$return[$key] = $value;
}
}
}
return $return;
}
This gives you an idea how you might achieve the result, but internally php implements this as a sort, because it's much faster than the aforementioned implementation.
Here this function in PHP that allows to merge any N amount of different length arrays in a fashion that output array will be in next order: Array1[0],Array2[0],..,ArrayN[0],Array1[1],Array2[1],..,ArrayN[1]... :
function array_zip_merge() {
$output = array();
// The loop incrementer takes each array out of the loop as it gets emptied by array_shift().
for ($args = func_get_args(); count($args); $args = array_filter($args)) {
// &$arg allows array_shift() to change the original.
foreach ($args as &$arg) {
$output[] = array_shift($arg);
}
}
return $output;
}
// test
$a = range(1, 10);
$b = range('a', 'f');
$c = range('A', 'B');
echo implode('', array_zip_merge($a, $b, $c)); // prints 1aA2bB3c4d5e6f78910
While I understand what each of built in functions in this example do on their own, I just cant wrap my mind how it all works together in this function, despite included explaining commentaries...
Can someone break it down for me, please? The function works great as is, its just driving me crazy that I don't understand how it works...
P.S: this function is taken from Interleaving multiple arrays into a single array question.
The arrays $a, $b and $c have 10, 6 and 2 elements respectively.
$a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
$b = ['a', 'b', 'c', 'd', 'e', 'f'];
$c = ['A', 'B'];
When you feed the arrays as arguments for the array_zip_merge() function, look at the for loop. The func_get_args() will set the $args with all the arguments supplied. On start of first for loop run,
$args = [$a, $b, $c];
count($args) = 3;
At the foreach loop the array_shift will return the first element of each array resulting the $output to be like
$output = [1, 'a', 'A'];
And the arrays now look like,
$a = [2, 3, 4, 5, 6, 7, 8, 9, 10];
$b = ['b', 'c', 'd', 'e', 'f'];
$c = ['B'];
At the end of the first for loop the array_filter function will test if any array is empty and remove it from $args. Same thing will happen at the second run, and by the end of the second for loop, the variables would look like
$a = [3, 4, 5, 6, 7, 8, 9, 10];
$b = ['c', 'd', 'e', 'f'];
$c = [];
$output = $output = [1, 'a', 'A', 2, 'b', 'B'];
//because $c is empty array_filter() removes it from $args
$args = [$a, $b];
So, on the third iteration of the for loop count($args) will return 2. When the last element of $b has been removed by array_shift the count($args) will return 1. The iteration will continue until all the arrays are empty
Inside array_zip_merge, the for statement always takes the first values of each array and add them to output variable respectively.
Because array_shift removes the element it returns, on every loop the first elements are different. When it gets empty because of it, the loop has nothing to do and breaks.
If you still dont understand, ask the specific part of the code you have trouble with please.
The custom function is effectively a transposing and flattening technique that will accommodate a non-matrix set of data. By non-matrix, I mean by pushing the three arrays into a single array, there will be columnar gaps in the data set. Using implode(array_merge(...array_map(null, $a, $b, $c))) will work by happenstance (because you are imploding the data with an empty string as glue, but in other cases, the generation of null elements will potentially skew the results.
I find the borrowed script to be very hard to interpret. The for loop would be much simpler as a foreach(). Instead of calling func_get_args(), the incoming columnar data could have been declared a variable using the spread operator in the function definition.
Here is one way to perform the 3-step task (without implode):
Transpose the data from the 3 arrays (isolate columns of data)
Filter null values out of the columnar data
Flatten the filtered two dimensional array (array_merge(...$tranposedAndfiltered))
Code: (Demo)
var_export(
array_merge(
...array_map(
fn(...$col) => array_filter(
$col,
fn($v) => !is_null($v)
),
$a,
$b,
$c
)
)
);
If it is simpler to perform the transposition without the spread operator and null filtering, just use nested foreach loops before flattening and imploding. (Demo)
$output = [];
foreach ([$a, $b, $c] as $i => $array) {
foreach ($array as $k => $v) {
$output[$k][$i] = $v;
}
}
var_export(implode(array_merge(...$output)));
See Stack Overflow's array transpose canonical: Transposing multidimensional arrays in PHP
I have an array with possible duplicate values, and I want not only to remove them (i use array_unique for that), but extract them in anothr array.
i.e.
$a = array(1,2,2,3,4,4,5,6,6,6);
$b = array_unique($a); // has 1,2,3,4,5,6
I want a third array ($c) with the duplicates only
that is, with [2,4,6] or [2,4,6,6] (either would do)
whats the easiest way of doing it?
I tried $c = array_diff($a,$b), but gives an empty array, since it is removing all of the occurrences of $b from $a (because, of course, they occur at least once in $b)
I also thought of array_intersect, but it result in an array exactly like $a
Is there a direct function in php to achieve this? How to do it?
I also found such solution on Internet:
$c = array_unique( array_diff_assoc( $a, array_unique( $a ) ) );
But it doesn't seem easy to understand
You can use array_count_values to count the # of occurrences of each element and use array_filter to only keep those that occur more than once.
$a = array(1,2,2,3,4,4,5,6,6,6);
$b = array_count_values($a);
$c = array_filter($b,function($val){ return $val > 1; });
$c = array_keys($c);
print_r($c);
If your input array is sorted you can find dupes by looping through the array and checking if the previous element is equal to the current one
$a = array(1,2,2,3,4,4,5,6,6,6);
$dupes = array();
foreach($a as $i => $v) {
if($i > 0 && $a[--$i] === $v)
$dupes[] = $v;
}
print_r($dupes);
like
array(a, b, c, d, e);
I want to add new elements to it, but keep the maximum element count to 5. So if after the add the count exceeds 5, I want to remove elements from the start until the size of the array is 5 again.
For my suggestion I made use of array_sliceDocs:
Input:
$arr = range('a', 'c');
$new = 'f';
$highest = 4;
Code:
$arr = array_slice($arr, -$highest);
$arr[] = $new;
array_slice takes care to limit the array to the last 4 elements, then the new element is added.
If the array has less than 4 elements, this won't remove any elements, so only add the new one.
Demo
array_slice will help you
$array = array('a','b','c','d','e');
$array[] = 'f';
if(count($array) > 5)
$array = array_slice($array,count($array)-5);
var_dump($array);
reusable function
function add_array_max(&$array,$item,$max)
{
$array[] = $item;
if(count($array) > $max)
$array = array_slice($array,count($array)-$max);
}
add_array_max($array,'g',5);
add_array_max($array,'h',5);
add_array_max($array,'i',5);
add_array_max($array,'j',5);
var_dump($array);
Add elements in array and check following condition
if(count($arr) >= 5) {
array_shift($arr); //remove element from beginning
}
function add($array,$item) {
array_push($item);
while(count($array)>5) array_shift($array);
}
Try to use array_pad function.
(see http://www.php.net/manual/en/function.array-pad.php)
Or array_slice (see http://www.php.net/manual/en/function.array-slice.php)
Use a circular buffer, overwriting old elements and remembering the index of the "first" one.
add_element (k)
a [first] = k;
first = (first + 1) % 5
access_element (i)
return a [(first + i) % 5]
You can use this code:
$array = array('a', 'b', 'c', 'd', 'e');
$newElems = array ('f', 'g', 'h');
foreach($newElems as $elem)
{
array_shift($array);
array_push($elem);
}
It works as you can see here: http://codepad.org/DH2UUuTY
From http://php.net/manual/en/language.types.array.php:
The unset() function allows removing keys from an array. Be aware that
the array will not be reindexed. If a true "remove and shift" behavior
is desired, the array can be reindexed using the array_values()
function.
An example follows.
Try this:
if(count($array) >= 5)) { array_pop($array); }
array_push($array, 'whatever');
I have two sequential (non-associative) arrays whose values I want to combine into a new array, ignoring the index but preserving the order. Is there a better solution (i.e. an existing operator or function) other than do the following:
$a = array('one', 'two');
$b = array('three', 'four', 'five');
foreach($b as $value) {
$a[] = $value;
}
Remark: The '+' operator doesn't work here ('three' with index 0 overwrites 'one' with index zero). The function array_merge has the same problem.
array_merge is what you want, and I don't think you are correct with that overwriting problem. From the manual:
If, however, the arrays contain numeric keys, the later value will not overwrite the original value, but will be appended.
$a + $b on two arrays is the union of $a and $b:
The + operator appends elements of remaining keys from the right handed array to the left handed, whereas duplicated keys are NOT overwritten.
So use array_merge to merge both arrays:
$merged = array_merge($a, $b);