PHP - Find previous value in multidimensional array - php

I have this array:
$test = array( "a" => "b",
"c" => array("foo" => "bar",
"3" => "4",
),
"e" => "f",);
I want to create a function that finds the previous value in this array, given a certain value, for example find("bar", $test); should return "b".
This is what I got:
function find($needle, $array, $parent = NULL)
{
//moves the pointer until it reaches the desired value
while (current($array) != $needle){
//if current value is an array, apply this function recursively
if (is_array(current($array))){
$subarray = current($array);
//passes the previous parent array
find($needle, $subarray, prev($array));
}
//once it reaches the end of the array, end the execution
if(next($array) == FALSE){
return;
}
}
//once the pointer points to $needle, run find_prev()
find_prev(prev($array), $parent);
}
function find_prev($prev, $parent = NULL)
{
// in case there is no previous value in array and there is a superior level
if (!$prev && $parent) {
find_prev($parent);
return;
}
// in case previous value is an array
// find last value of that array
if (is_array($prev) && $prev){
find_prev(end($prev), $parent));
return;
} else {
$GLOBALS['pre'] = $prev;
}
}
For pedagogical reasons and since I have devoted some time to this function, it would be great if you could provide any hints about why this isn't working rather than any other simpler solution that you might have.

You have an infinite loop. Your algorithm is a bit complicated for what you want to do. Maybe you should just keep the previous value in a variable and return it when you find the $needle. Here is the corresponding code. I tried to not modify your code as much as I could:
function find($needle, $array, $lastValue = NULL)
{
$previousValue = null;
//moves the pointer until it reaches the desired value
while (current($array) != FALSE) {
$value = current($array);
//if current value is an array, apply this function recursively
if (is_array($value)) {
$subarray = $value;
//passes the previous value as the last value for the embedded array
$value = find($needle, $subarray, $previousValue);
if ($value !== NULL) {
return $value;
}
} else if ($value === $needle) {
//returns the previous value of the current array
if ($previousValue !== NULL) {
return $previousValue;
//returns the last checked value of the parent array
} else if ($lastValue !== NULL) {
return $lastValue;
} else {
return;
}
} else {
$previousValue = $value;
}
next($array);
}
}
$test = array(
"a" => "b",
"c" => array(
"foo" => "bar",
"3" => "4"
),
"e" => "f"
);
$result = find("bar", $test);
if ($result === null) {
print('no previous value');
} else {
$GLOBALS['pre'] = $result;
print($GLOBALS['pre']);
}
You may try TDD to code this kind of algorithm. It could help you.

The function find() calls itself recursively for sub-arrays but it doesn't check if the inner call found something or not and keep searching. This is why the first call to find_prev() runs with $test['b'] as its first parameter (prev() of the last element in $test). You expect it to run with $test['a'].

Related

Foreach last item gets methode

Guys I have an array with objects,
I want the last item in the foreach loop do something else then the rest.
How do I archive that?
if(sizeof($testDup) > 3){
} else {
foreach ($testDup as $d) {
}
}
$test array(3)
432 => test_id -> 21
431 => test_id -> 21
435 => test_id -> 21
This will process the array of objects and do something else with the last element:
$data = '';
$arrayWithObjects = array(
(object)array('test1', 'test2'),
(object)array('test1', 'test2'),
(object)array('test1', 'test2'),
);
foreach ($arrayWithObjects as $object) {
// Can't get next in the array, so is last element
if (!next($arrayWithObjects)) {
// Process last element
$data .= $object->{1};
} else {
// Process all other elements
$data .= $object->{0};
}
}
var_dump($data); // "test1test1test2"
you can compare the current one with the end():
class Test {
public function __construct(private string $name) {}
public function read(): string {
return sprintf('%s: hurray', $this->name);
}
public function readLast():string {
return sprintf('%s: am I last?', $this->name);
}
}
$array = [
new Test('first'),
new Test('second'),
new Test('third'),
new Test('fourth'),
];
foreach( $array as $object ){
if($object === end($array)) {
echo $object->readLast().PHP_EOL;
}else{
echo $object->read().PHP_EOL;
}
}
As an alternative to checking if the current item is the last one (which the other answers show), you could use array_slice() to get the start of the array to loop over and then end() to get the last element of the array.
$data = [/*...*/]
foreach ($item as array_splice($data, 0, -1, true) {
$item->foo();
}
if (($item = end($data) !== false) {
$item->bar();
}
In my opinion, this code is easier to read (and metrics like cyclomatic complexity agree) than the nested if $item === end($data) check. If the same is true on your specific case will depend on what, exactly is in the loop and how much of it is different.
In addition, if your array is large, this approach may offer (slightly) better performance (but if your array is large and a small performance difference is important, don't take my word for this - benchmark both solutions with read data).
It's so easy: When the loop is finished, you still got the last element!!
if (!empty($arr)) {
foreach ($arr as $item) {
; // Do something with $item
}
// Here you still got last $item
echo var_export($item, true);
}

PHP: How to get last array element in one array

i am trying to create function to check if array element is the last array element in one array. Original array looks like:
array(1001,
1002,
array('exam'=>true, 'index'=>10),
1003,
1004,
1005,
array('exam'=>true, 'index'=>20),
1006,
1007,
array('exam'=>true, 'index'=>30),
1008,
1009
)
I this case to prove if "array('exam'=>true, 'index'=>30)" is the last.
I have index position of that element, but I do not know how to check if that is the last array element.
function is_last_exam_in_survey($array, $exam_position) {
foreach($array as $element) {
if(!is_numeric($element) {
// check if that is the last array element in array
//return true;
} else {
// return false;
}
}
}
I would be grateful for any advice:)
function get_last_exam_in_survey($array) {
$last = null;
foreach($array as $element) {
if(is_array($element) && !empty($element['exam'])) {
$last = $element;
}
}
return $last;
}
function is_last_exam_in_survey($array, $exam_position) {
$last_exam = get_last_exam_in_survey($array);
return !empty($last_exam) && ($last_exam['index']==$exam_position);
}
I think this would be the quickest solution:
function is_last_exam_in_survey($array, $exam_position) {
$last_index = array_key_last( $array );
if( $exam_position == $last_index ){
return true;
}else{
return false;
}
}
You can still change the conditional statement if you are trying to compare values from the last element, for example:
if( isset($last_index['index']) && $exam_position == $last_index['index'] ){
Also, if you want to get the latest array value instead of key, you could use this:
$last_index = end( $array );
I would reverse the array, and look for the first element. Something like:
function is_last_exam_in_survey($array, $exam_position) {
foreach(array_reverse($array) as $element) {
if(!is_numeric($element) {
return $element['index'] === $exam_position;
}
}
return false;
}
Seems like the most efficient and simplest solution to me.
this solution avoid loop. at first we find out the last index of array.Then we use is_array() function for check the last element is array or not.
function get_last_exam_in_survey(array $arr)
{
$lastOfset = count($arr) - 1;
if(is_array($arr[$lastOfset])){
return true;
}else{
return false;
}
}
I think you can use array_column function to do that
function is_last_exam_in_survey($array,$exam_position){
$ac = array_column($array, 'index'); // return array(10,20,30)
$la = $ac[count($ac) - 1]; // 30
foreach ($array as $element) {
if (!is_numeric($element)) {
// check if $element['index'] === 30
if($element['index'] === $la){
return true;
}
}
}
}
How about using array_slice to extract the values in the array that are after the position you are looking at, then array_filter to remove any values that are not arrays. If there are any values left, then the entry you are looking at is not the last array entry in the array.
This may not be very efficient if you are calling it a lot of times with a large array. It may be better to rethink the way the data is stored or loaded into the array.
function is_last_exam_in_survey($array, $exam_position)
{
return isset($array[$exam_position])
&& is_array($array[$exam_position])
&& !array_filter(array_slice($array, $exam_position + 1), 'is_array');
}

Check if at least one item in a group of items, is not empty

I have this kind of list of variables:
$item[1]['Value']
$item[2]['Value']
...
$item[50]['Value']
Some of them are null.
How do I check if at least one of them is not empty, and in this case, define that one (whether first case or last case) as a new variable?
P.S. What is the name of an $var[number]['foo'] format? Is it an array?
A simple test in using empty() function
if(!empty($item[$i]['value'])
{
echo "exists";
}else{
echo "is not set";
}
If you want affect a new value if it not exists or not defined, you can use null coalescing operator
$item[$i]['value'] = $item[$i]['value'] ?? $newvalue;
to check atleast one of them is not empty
<?php
$atleast=0;//all are empty
for($i=0;$i<count($item);$i++)
{
if(!empty($item[$i]['value']))
$atleast=1;//one is empty;
}
if($atleast==1)
echo "there is more than one element which are not empty";
else
echo "all are emtpy";
?>
This sets the last found used element as newVariable.
for($i = 0; $i < sizeOf($item); i ++)
{
if(isset($item[$i]['Value']))
{
$newVariable = $item[$i]['Variable'];
}
}
If you want to use the inner key as a variable name, then you can check whether you can create it and create it if so:
function any($array) {
foreach ($array as $key => $item) {
if ($item) return $key;
}
return false;
}
$variableName = any($var);
if ($variableName !== false) {
{$variableName} = "yourvalue";
} else {
//We ran out of variable names
}
P.S. The format of $var[number]['foo'] is an array with number keys on the outside and an array with string keys in the inside.
You could array_filter() and use empty() like this :
// example data
$items = [
['Value' => null],
['Value' => 2],
['Value' => null],
];
// remove elements with 'Value' = null
$filtered = array_filter($items, function($a) { return !is_null($a['Value']); }) ;
$element = null;
// If there is some values inside $filtered,
if (!empty($filtered)) {
// get the first element
$element = reset($filtered) ;
// get its value
$element = $element['Value'];
}
if ($element)
echo $element ;
will outputs:
2
array_filter() return an array with non-empty values (using use function).
empty() checks if an array is empty or not.
reset() gets the first element in an array.
If you want to check all the values of all items of the array you can use foreach.
Example:
$item[1]['Value'] = "A";
$item[2]['Value'] = "B";
$item[4]['Value'] = "";
$item[3]['Value'] = NULL;
foreach ($item as $key => $value) {
if (!empty($value['Value'])) {
echo "$key It's not empty \n";
} else {
echo "$key It's empty \n";
}
}
Result:
1 It's not empty
2 It's not empty
3 It's empty
4 It's empty
Also you can see that the empty function consider NULL as empty.
----- EDIT -----
I forget to add the method to detect if at least one item it's empty, you can use:
Example
$item[1]['Value'] = "A";
$item[2]['Value'] = "B";
$item[4]['Value'] = "";
$item[3]['Value'] = NULL;
foreach ($item as $key => $value) {
if (!empty($value['Value'])) {
$item_result = false;
} else {
$item_result = true;
}
}
if ($item_result) {
echo "At least one item is empty";
} else {
echo "All items have data";
}
Result:
At least one item is empty
<?php
$data =
[
1 => ['foo' => ''],
2 => ['bar' => 'big'],
10 => ['foo' => 'fat']
];
$some_foo = function ($data) {
foreach($data as $k => $v)
if(!empty($v['foo'])) return true;
return false;
};
var_dump($some_foo($data));
$data[10]['foo'] = null;
var_dump($some_foo($data));
Output:
bool(true)
bool(false)
Alternatively you can filter foo values that evaluate to false:
$some_foo = (bool) array_filter(array_column($data, 'foo'));

PHP Function that can return value from an array key a dynamic number of levels deep

Using PHP, I would like to write a function that accomplishes what is shown by this pseudo code:
function return_value($input_string='array:subArray:arrayKey')
{
$segments = explode(':',$input_string);
$array_depth = count(segments) - 1;
//Now the bit I'm not sure about
//I need to dynamically generate X number of square brackets to get the value
//So that I'm left with the below:
return $array[$subArray][$arrayKey];
}
Is the above possible? I'd really appreciate some pointer on how to acheive it.
You can use a recursive function (or its iterative equivalent since it's tail recursion):
function return_value($array, $input_string) {
$segments = explode(':',$input_string);
// Can we go next step?
if (!array_key_exists($segments[0], $array)) {
return false; // cannot exist
}
// Yes, do so.
$nextlevel = $array[$segments[0]];
if (!is_array($nextlevel)) {
if (1 == count($segments)) {
// Found!
return $nextlevel;
}
// We can return $nextlevel, which is an array. Or an error.
return false;
}
array_shift($segments);
$nextsegments = implode(':', $segments);
// We can also use tail recursion here, enclosing the whole kit and kaboodle
// into a loop until $segments is empty.
return return_value($nextlevel, $nextsegments);
}
Passing one object
Let's say we want this to be an API and pass only a single string (please remember that HTTP has some method limitation in this, and you may need to POST the string instead of GET).
The string would need to contain both the array data and the "key" location. It's best if we send first the key and then the array:
function decodeJSONblob($input) {
// Step 1: extract the key address. We do this is a dirty way,
// exploiting the fact that a serialized array starts with
// a:<NUMBEROFITEMS>:{ and there will be no "{" in the key address.
$n = strpos($input, ':{');
$items = explode(':', substr($input, 0, $n));
// The last two items of $items will be "a" and "NUMBEROFITEMS"
$ni = array_pop($items);
if ("a" != ($a = array_pop($items))) {
die("Something strange at offset $n, expecting 'a', found {$a}");
}
$array = unserialize("a:{$ni}:".substr($input, $n+1));
while (!empty($items)) {
$key = array_shift($items);
if (!array_key_exists($key, $array)) {
// there is not this item in the array.
}
if (!is_array($array[$key])) {
// Error.
}
$array = $array[$key];
}
return $array;
}
$arr = array(
0 => array(
'hello' => array(
'joe','jack',
array('jill')
)));
print decodeJSONblob("0:hello:1:" . serialize($arr));
print decodeJSONblob("0:hello:2:0" . serialize($arr));
returns
jack
jill
while asking for 0:hello:2: would get you an array { 0: 'jill' }.
you could use recursion and array_key_exists to walk down to the level of said key.
function get_array_element($key, $array)
{
if(stripos(($key,':') !== FALSE) {
$currentKey = substr($key,0,stripos($key,':'));
$remainingKeys = substr($key,stripos($key,':')+1);
if(array_key_exists($currentKey,$array)) {
return ($remainingKeys,$array[$currentKey]);
}
else {
// handle error
return null;
}
}
elseif(array_key_exists($key,$array)) {
return $array[$key];
}
else {
//handle error
return null;
}
}
Use a recursive function like the following or a loop using references to array keys
<?php
function lookup($array,$lookup){
if(!is_array($lookup)){
$lookup=explode(":",$lookup);
}
$key = array_shift($lookup);
if(!isset($array[$key])){
//throw exception if key is not found so false values can also be looked up
throw new Exception("Key does not exist");
}else{
$val = $array[$key];
if(count($lookup)){
return lookup($val,$lookup);
}
return $val;
}
}
$config = array(
'db'=>array(
'host'=>'localhost',
'user'=>'user',
'pass'=>'pass'
),
'data'=>array(
'test1'=>'test1',
'test2'=>array(
'nested'=>'foo'
)
)
);
echo "Host: ".lookup($config,'db:host')."\n";
echo "User: ".lookup($config,'db:user')."\n";
echo "More levels: ".lookup($config,'data:test2:nested')."\n";
Output:
Host: localhost
User: user
More levels: foo

unset inside eval not working

I'm trying to remove an item from an array based on string;
public function delete($path){
// a key path given
if(strpos($path, '.') !== false){
$parts = explode('.', $path);
$first_key = array_shift($parts);
$data = $this->get($path);
// first key doesn't exist
if($data === false)
return false;
$parts = implode('"]["', $parts);
if(eval('if(isset($data["'.$parts.'"])){ unset($data["'.$parts.'"]); return true; } return false;'))
return $this->set($first_key, $data);
}
// a single key given
if(isset($this->data[$path]){
unset($this->data[$path]);
return true;
}
return false;
}
And it only works for single keys. Apparently the eval doesn't modify $data for some reason.
delete('test') works, but delete('test.child') doesn't...
I don't see why you'd need eval() here. See the following to replace your eval() construct:
<?php
function removeFromArray(&$array, $path)
{
if (!is_array($path)) {
$path = explode('.', trim($path, '.'));
}
$current = &$array;
while ($path) {
$key = array_shift($path);
// isset() would fail on `$array[$key] === null`
if (!array_key_exists($key, $current)) {
// abort if the array element does not exist
return false;
}
if (!$path) {
// reached the last element
unset($current[$key]);
return true;
}
if (!is_array($current[$key])) {
// can't go deeper, so abort
return false;
}
// continue with next deeper element
$current = &$current[$key];
}
return false;
}
$data = array(
'a' => 1,
'b' => array(
'c' => 2,
'd' => 3,
'e' => array(
'f' => 4,
),
),
);
var_dump(
removeFromArray($data, 'b.e.f'),
$data,
removeFromArray($data, 'b.c'),
$data
);
function unset_multiple($arr = [], $keys = [], $limitKeys = 30){
if($keys && count($keys) <= $limitKeys && is_array($arr) && count($arr) > 0){
foreach($keys as $key){
$keys[$key] = null;
}
return array_diff_key($arr, $keys);
} else{
throw new Exception("Input array is invalid format or number of keys to remove too large");
}
}
Example called:
$arr = array("name" => "Vuong", "age" => 20, "address" => "Saigon");
$res = unset_multiple($arr, ["name", "age"]);
//Result: ["address" => "Saigon"]
Make sure $keys param has all available keys in $arr param (only two-dimensional arrays). Need to remember this function is a helper to quickly removing multiple elements of array, not a function returns the absolute accurate results for all cases.

Categories