Why cant i push element onto sub array in foreach loop? - php

I'm trying to understand something about arrays in for each loops that might be obvious to many.
When i loop through my multi-dimensional array, i attempt to find sub arrays with no third element. If they have no third element, i want to add a third element to that sub array with a specific value.
$testArray = array (
array("Green", "Yellow", "Blue"),
array("Brown", "Silver"),
array("Orange", "Pink", "Black"),
);
When i use the foreach loop:
foreach ( $testArray as $key => $array ) {
if (count($array) == '2') {
$array[] = "None";
};
}
No errors are thrown but nothing happens. When i use the for each loop:
foreach ( $testArray as $key => $array ) {
if (count($array) == '2') {
$testArray[$key][] = "None";
};
}
It works as expected.
Sorry for the long preamble, my questions is:
Why aren't these two foreach loops doing the same thing? Thanks!

Because you need to access $testArray entries "by reference"
foreach ( $testArray as &$array ) {
if (count($array) == 2) {
$array[] = "None";
};
}
unset($array);

The problem here lies in the fact that foreach iterates over iterables and sets the iteration variable by value. This means that the $array which you are dealing with in the foreach is not the same value of the $testArray.
To rememdy this (and avoid introducing an $index variable to mutate an item in the array), you will need to tell foreach to pass the value by reference. References are PHP's answer to C-style pointers. If a variable references another, both variables point to the same value, so a modification to the contents of one is in effect a modification to the other. In your foreach, you can use &$array to have the loop pass you the items of $testArray by reference instead of by value:
foreach ( $testArray as $key => &$array ) {
if (count($array) == '2') {
$array[] = "None";
}
}
(Codepad Demo)
This aligns with PHP's references, where one variable can be made to reference another like so:
$a = array(1, 2, 3);
$b = &$a;
$b[] = 4;
print_r($a); // 1, 2, 3, 4
(Codepad Demo)
You experience a similar phenomenon with functions:
function byValue($a) {
$a[] = 4;
}
function byRef(&$a) {
$a[] = 5;
}
$a = array(1, 2, 3);
byValue($a);
print_r($a); // 1, 2, 3
byRef($a);
print_r($a); // 1, 2, 3, 5
(Codepad Demo)
The references section of the PHP docs has some examples about this syntax of foreach. Also note this (somewhat) related, but interesting read on foreach and references.
Also, on an unrelated note if you weren't aware: you don't need a semicolon after closing a block with } in PHP unless you're doing something like assigning a closure to a variable:
$someFunc = function($a) { return $a; }; //need one here
if(1 + 2 == 4) {
echo "I just broke math";
} // but you don't need one here

foreach loops do not pass the elements by reference. To get the first array to do what you want, it would have to be:
foreach ( $testArray as $key => &$array ) {
if (count($array) == '2') {
$array[] = "None";
};
}

Related

PHP - Elegantly extract the numeric indices in array a that are not in array b (not array_diff_key)

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.

How to remove values from an array if occurring more than one time?

I need to remove values from an array that occur more than one time in the array.
For example:
$value = array(10,10,5,8);
I need this result:
$value = array(5,8);
Is there any in-built function in php?
I tried this, but this will not return my expected result:
$unique = array_unique($value);
$dupes = array_diff_key($value, $unique);
You can use array functions and ditch the foreach loops if you wish:
Here is a one-liner:
Code:
$value = [10, 10, 5, 8];
var_export(array_keys(array_intersect(array_count_values($value),[1])));
As multi-line:
var_export(
array_keys(
array_intersect(
array_count_values($value),
[1]
)
)
);
Output:
array (
0 => 5,
1 => 8,
)
This gets the value counts as an array, then uses array_intersect() to only retain values that occur once, then turns the keys into the values of a zero-index array.
The above snippet works identically to #modsfabio's and #axiac's answers. The ONLY advantage in my snippet is brevity. It is possible that their solutions may outperform mine, but judging speed on relatively small data sets may be a waste of dev time. For anyone processing relatively large data sets, do your own benchmarking to find the technique that works best.
For lowest computational/time complexity, use a single loop and as you iterate conditionally populate a lookup array and unset() as needed.
Code: (Demo) (Crosslink to my CodeReview answer)
$values = [10, 10, 5, 8];
$found = [];
foreach ($values as $index => $value) {
if (!isset($found[$value])) {
$found[$value] = $index;
} else {
unset($values[$index], $values[$found[$value]]);
}
}
var_export($values);
// [2 => 5, 3 => 8]
A couple of notes:
If processing float values, using a technique that stores the values as keys (as all of my snippets do), then the results may be incorrect because php will change floats to integers when used as keys.
PHP is consistently much faster at searching for keys than it is at searching for values.
You can do it like this using array_count_values() and a foreach loop:
<?php
$input = array(10,10,5,8);
$output = array();
foreach(array_count_values($input) as $value => $count)
{
if($count == 1)
{
$output[] = $value;
}
}
var_dump($output);
Output:
array(2) {
[0]=>
int(5)
[1]=>
int(8)
}
Example: https://eval.in/819461
A possible approach:
$value = array(10,10,5,8);
$output = array_keys(
array_filter(
array_count_values($value),
function ($count) {
return $count == 1;
}
)
)
array_count_values() produces an array that associates to each unique value from $value the number of times it appears in the array.
array_filter() keeps in this result only the entries (the keys) that appear only once in the original array.
array_keys() produces the desired result.
I would use array_count_values to get an array with how often every element occurs in the array. Then remove all the elements from the original array that occur more than once.
You need to use array_count_values(), array_search() and unset() functions.
<?php
$value = array(10,10,5,8);
echo '<pre>';print_r($value);echo '</pre>';
$cnt = array_count_values($value);
$dup = array();
foreach ($cnt as $k => $repeated) {
if ($repeated > 1) {
if(($key = array_search($k, $value)) !== false) {
unset($value[$key]);
}
}
}
echo '<pre>';print_r($cnt);echo '</pre>';
echo '<pre>';print_r($value);echo '</pre>';
?>
Demo
you can use
foreach loop
and
array_diff() function:
$value=array(10,10,5,8);
$duplicated=array();
foreach($value as $k=>$v)
{
if($kt=array_search($v,$value))!==false and
$k!=$kt)
{if (count(array_keys($array, $value)) > 1)
{
/* Execute code */
}
unset($value[$kt];$duplicated[]=$v;
}
}
$result=array_diff($values,$duplicated);
print_r($result);
output
Array([2]=>5[3]=>8)

Remove elements from beginning of array, which have certain value (PHP)

I want to remove elements from start of an array, but only certain value. For example, I want to remove all "1" from start of an array.
I want this array:
1,1,2,3,2,3,1,4,5
or this array:
1,1,1,1,2,3,2,3,1,4,5
to became this:
2,3,2,3,1,4,5
Note: There is one more "1", but not repeating on start of an array, I need that to remain in array. Only starting repetitive "1" should be removed.
One PHP line, without for, foreach or other loops, if possible. I know how to do this using loops. I want to know if there is another single line solution for this kind of problem.
Here is a little helper function that will achieve the task:
<?php
function recursively_remove_value_from_start_of_array($value, $arr) {
while(true) {
if( count($arr) > 0 && $arr[0] == $value )
array_shift($arr);
else
break;
}
return $arr;
}
$arr = array(1, 1, 1, 3, 4, 4);
$filtered_array = recursively_remove_value_from_start_of_array(1, $arr);
Here's my try with loops. I don't think there is a one liner solution unless you create a function that does it for you.
foreach($array as $k => $v){
if($v == 1) unset($array[$k]); else break;
}

Using PHP remove duplicates from an array without using any in- built functions?

Lets say I have an array as follows :
$sampArray = array (1,4,2,1,6,4,9,7,2,9)
I want to remove all the duplicates from this array, so the result should be as follows:
$resultArray = array(1,4,2,6,9,7)
But here is the catch!!! I don't want to use any PHP in built functions like array_unique().
How would you do it ? :)
Here is a simple O(n)-time solution:
$uniqueme = array();
foreach ($array as $key => $value) {
$uniqueme[$value] = $key;
}
$final = array();
foreach ($uniqueme as $key => $value) {
$final[] = $key;
}
You cannot have duplicate keys, and this will retain the order.
A serious (working) answer:
$inputArray = array(1, 4, 2, 1, 6, 4, 9, 7, 2, 9);
$outputArray = array();
foreach($inputArray as $inputArrayItem) {
foreach($outputArray as $outputArrayItem) {
if($inputArrayItem == $outputArrayItem) {
continue 2;
}
}
$outputArray[] = $inputArrayItem;
}
print_r($outputArray);
This depends on the operations you have available.
If all you have to detect duplicates is a function that takes two elements and tells if they are equal (one example will be the == operation in PHP), then you must compare every new element with all the non-duplicates you have found before. The solution will be quadratic, in the worst case (there are no duplicates), you need to do (1/2)(n*(n+1)) comparisons.
If your arrays can have any kind of value, this is more or less the only solution available (see below).
If you have a total order for your values, you can sort the array (n*log(n)) and then eliminate consecutive duplicates (linear). Note that you cannot use the <, >, etc. operators from PHP, they do not introduce a total order. Unfortunately, array_unique does this, and it can fail because of that.
If you have a hash function that you can apply to your values, than you can do it in average linear time with a hash table (which is the data structure behind an array). See
tandu's answer.
Edit2: The versions below use a hashmap to determine if a value already exists. In case this is not possible, here is another variant that safely works with all PHP values and does a strict comparison (Demo):
$array = array (1,4,2,1,6,4,9,7,2,9);
$unique = function($a)
{
$u = array();
foreach($a as $v)
{
foreach($u as $vu)
if ($vu===$v) continue 2
;
$u[] = $v;
}
return $u;
};
var_dump($unique($array)); # array(1,4,2,6,9,7)
Edit: Same version as below, but w/o build in functions, only language constructs (Demo):
$array = array (1,4,2,1,6,4,9,7,2,9);
$unique = array();
foreach($array as $v)
isset($k[$v]) || ($k[$v]=1) && $unique[] = $v;
var_dump($unique); # array(1,4,2,6,9,7)
And in case you don't want to have the temporary arrays spread around, here is a variant with an anonymous function:
$array = array (1,4,2,1,6,4,9,7,2,9);
$unique = function($a) /* similar as above but more expressive ... ... you have been warned: */ {for($v=reset($a);$v&&(isset($k[$v])||($k[$v]=1)&&$u[]=$v);$v=next($a));return$u;};
var_dump($unique($array)); # array(1,4,2,6,9,7)
First was reading that you don't want to use array_unique or similar functions (array_intersect etc.), so this was just a start, maybe it's still of som use:
You can use array_flip PHP Manual in combination with array_keys PHP Manual for your array of integers (Demo):
$array = array (1,4,2,1,6,4,9,7,2,9);
$array = array_keys(array_flip($array));
var_dump($array); # array(1,4,2,6,9,7)
As keys can only exist once in a PHP array and array_flip retains the order, you will get your result. As those are build in functions it's pretty fast and there is not much to iterate over to get the job done.
<?php
$inputArray = array(1, 4, 2, 1, 6, 4, 9, 7, 2, 9);
$outputArray = array();
foreach ($inputArray as $val){
if(!in_array($val,$outputArray)){
$outputArray[] = $val;
}
}
print_r($outputArray);
You could use an intermediate array into which you add each item in turn. prior to adding the item you could check if it already exists by looping through the new array.

How to convert a simple array to an associative array?

What is the fastest way to convert a simple array to an associative array in PHP so that values can be checked in the isset($array[$value])?
I.e. fastest way to do the following conversion:
$array = array(1, 2, 3, 4, 5);
$assoc = array();
foreach ($array as $i => $value) {
$assoc[$value] = 1;
}
Your code is the exact equivalent of:
$assoc = array_fill_keys(array(1, 2, 3, 4, 5), 1); // or
$assoc = array_fill_keys(range(1, 5), 1);
array_flip(), while it may work for your purpose, it's not the same.
PHP ref: array_fill_keys(), array_flip()
If anyone is still wondering how to do this, there is an easier solution for this by using the array_combine function.
$array = array(1, 2, 3, 4, 5);
$assoc = array_combine($array,$array);
array_flip() is exactly doing that:
array_flip() returns an array in flip order, i.e. keys from trans become values and values from trans become keys.
Note that the values of trans need to be valid keys, i.e. they need to be either integer or string. A warning will be emitted if a value has the wrong type, and the key/value pair in question will not be flipped.
If a value has several occurrences, the latest key will be used as its values, and all others will be lost.
But apart from that, there is only one type of array in PHP. Even numerical ("simple", as you call it) arrays are associative.
Simply use this logic
$var1 = json_encode($arr1, JSON_FORCE_OBJECT);
$var1 = json_decode($var1);
where $arr1 is the array that has to be converted to associative array.
This can be achieved by json_encode and the json_decode the same
function simple_to_associative($array) {
$new_array = [];
$i = 0;
$last_elem = end($array);
$nr_elems = count($array);
foreach ($array as $index=>$value) {
if($i % 2 == 0 && $last_elem == $value) {
$new_array[$value] = '';
} elseif($i % 2 == 0) {
$new_array[$value] = $array[$index + 1];
}
$i++;
}
return $new_array;
}
Would work on any simple array of unlimited elements.

Categories