Search value in multidimension array and keep keys association - php

I have this array converted from xml of webservice. I have 500 items in this array.
I want to search any value and return all found array items with key association ( similar to database select query ). So if I search 'dummy' then it should return first item of this array.
Array
(
[12.12.2014] => Array
(
[7] => Array
(
[id] => 1672
[date] => 12.12.2014
[description] => rummy dummy data
[room] => delux
[Type] => garden
[from] => 17:00
[to] => 17:45
[assets] => Array
(
[asset] => Array
(
[0] => Array
(
[number] => 5275
[detail] => primary one
)
[1] => Array
(
[number] => 19
[detail] => secondary one
)
)
)
[references] => Array
(
[reference] => Array
(
[personnumber] => 479470
[type] => worker
[name] => David
[department] => Sales
[cv] => Array
(
[pdetails] => follow later
)
[profile] => True
)
)
)
)
[13.12.2014] => Array
(
[17] => Array
(
[id] => 1672
[date] => 13.12.2014
[description] => brown fox jump
[room] => star
[Type] => city
[from] => 17:00
[to] => 17:45
[assets] => Array
(
[asset] => Array
(
[number] => 5275
[detail] => prime two
)
)
[references] => Array
(
[reference] => Array
(
[personnumber] => 479470
[type] => manager
[name] => Albert
[department] => Purchase
[cv] => Array
(
[pdetails] => follow later
)
[profile] => True
)
)
)
)
)
I tried stripos to search string in array value and in_array based functions but either it gives incorrect result or key association is not maintained.
I am unable to find a way to maintain key->value.
function search($array, $key, $value)
{
$results = array();
if (is_array($array))
{
if (isset($array[$key]) && $array[$key] == $value)
$results[] = $array;
foreach ($array as $subarray)
$results = array_merge($results, search($subarray, $key, $value));
}
return $results;
}
This may be worst function you have ever seen but this do the job. If some one can make it recursive ( array may be further deeper ).
function search_in_multi_array($srchvalue, $array)
{
$foundkey = '';
if (is_array($array) && count($array) > 0)
{
foreach($array as $pkey => $pvalue)
{
foreach($pvalue as $ckey => $cvalue)
{
if (is_array($cvalue) && count($cvalue) > 0)
{
if(in_array($srchvalue,$cvalue))
{
$foundkey[$pkey][$ckey] = $cvalue;
}
foreach($cvalue as $dkey => $dvalue)
{
if(!is_array($dvalue))
{
$pos = stripos($dvalue, $srchvalue);
if ($pos !== false)
{
$foundkey[$pkey][$ckey] = $cvalue;
}
}
}
}
}
}
}
return $foundkey;
}
Function call -
$needle = 'fox';
search_in_multi_array($needle, $my_array);
This is the output
Array
(
[13.12.2014] => Array
(
[17] => Array
(
[id] => 1672
[date] => 13.12.2014
[description] => brown fox jump
[room] => star
[Type1] => city
[from] => 17:00
[to] => 17:45
[assets] => Array
(
[asset] => Array
(
[number] => 5275
[detail] => prime two
)
)
[references] => Array
(
[reference] => Array
(
[personnumber] => 479470
[Type1] => manager
[name] => Albert
[department] => Purchase
[cv] => Array
(
[pdetails] => follow later
)
[profile] => 1
)
)
)
)
)

You can use array_search() function to look through the array values. But array_search only looks in single-dimensional array.
Since you have a multi-dimensional array, you can write custom recursive function to search recursively in the array
function recursive_array_search($needle,$haystack) {
foreach($haystack as $key=>$value) {
$current_key=$key;
if($needle===$value OR (is_array($value) && recursive_array_search($needle,$value) !== false)) {
return $current_key;
}
}
return false;
}
But please note, using array_search() is a easier approach but not a optimised function. Which means if you're more concerned about your memory utilisation then I would also addtionally suggest.
Create a new array say 'dictionary' and store them like
$dictonary[<key-searched>] = array('12.12.2014', '13.12.2014')
So you process them once, and cache them
So next time when you want search again for the same key, you can first check if the key exists in dictionary. If exists return from there, else use array_search and cache the results in dictionary
Its always easier to search by key-value than to search in a multi-dimensional array.

I've made a simple routine that will extract the values in the array you're looking for:
function search_keys($needle,$haystack)
{
foreach($haystack as $key => $value)
{
if (is_array($value)) $output[$key] = search_keys($needle,$value);
else if (strpos($value,$needle) !== FALSE) $output[$key] = $value;
}
return isset($output) ? $output : NULL; // prevent warning
}
echo '<pre>';
print_r(search_keys('garden',$data));
echo '</pre>';
This will return 'garden' as:
Array
(
[12.12.2014] => Array
(
[7] => Array
(
[Type] => garden
)
)
)
You can further process the output of this function, or change the function, as needed.

You may want to have a look at XPath to run queries against the "raw" XML, see DOMXPath for example.
Something like /myRootTag/someDayTag[.//text()[contains(.,'MY_SEARCH_VALUE')]] should do the trick, selecting and returning all the someDayTag XML elements below the myRootTag which have the text MY_SEARCH_VALUE in any child node.

Related

php recursive function doesn't work

I have a problem to understand why this doesn't work. I have recursive function who check if value in multidimensional array exists.
Then I have get_teams() function from which I want to return only unique values. I check for unique values with my find_value() function but it still return all the values.. Can someone explain me what happens? Thanks in advance
function find_value( $array, $searched_val ) {
foreach ( $array as $key => $val ) {
if ( $val === $searched_val ) {
return true;
}
if ( is_array( $val ) ) {
return find_value( $val, $searched_val );
}
}
return 0;
}
get_teams();
function get_teams() {
$people = get_data( 'some/file.json' );
$teams = [];
foreach ( $people as $person ) {
if ( ! find_value( $teams, $person['team'] ) ) {
$teams[] = [ 'text' => $person['team'] ];
}
}
return $teams;
}
This is sample input
Array
(
[0] => Array
(
[id] => 1
[name] => Friedrich Robel
[team] => WordPress
[position] => Frontend Developer
[salary] => 4400
)
[1] => Array
(
[id] => 2
[name] => Mr. Christop Veum
[team] => HTML
[position] => Manager
[salary] => 1200
)
[2] => Array
(
[id] => 3
[name] => Demarco Rippin
[team] => HTML
[position] => QA
[salary] => 4400
)
[3] => Array
(
[id] => 4
[name] => Felicia Farrell
[team] => HTML
[position] => QA
[salary] => 1200
)
[4] => Array
(
[id] => 5
[name] => Torrance Fritsch
[team] => HTML
[position] => Assistant Manager
[salary] => 2500
)
[5] => Array
(
[id] => 6
[name] => Erica Daugherty
[team] => Mail
[position] => Assistant Manager
[salary] => 500
)
)
And I want this output
Array
(
[0] => WordPress,
[1] => HTML,
[2] => Mail
)
Your problem is that you returns the result of the sub-search without testing it.
Instead of return find_value(...), you have to test its value.
function find_value( $array, $searched_val ) {
foreach ( $array as $key => $val ) {
if ( $val === $searched_val ) {
return true;
}
if ( is_array( $val ) ) {
// Here, you have to test the result before to return TRUE.
if (find_value( $val, $searched_val )) {
return true ;
}
}
}
return 0;
}
The fix is, as Syscall posted, to test the return of find_value(), and only return if it is true.
As your code is structured now, it will return either true or false from the recursion call, which will always be required, since you are storing your values as [ 'text' => $person['team'] ];
The way your code is designed, with the return 0 outside the foreach is only to return false in the case a match was not found. For this reason, returning a false from the contents of that conditional is premature. Your loop does not have a chance to iterate and test all the values of the passed array. This early returning results in duplicate array entries.
In any case, the results at the end will be an array of ['text' => '<job>'] array members. If you want only the job string, assign like this instead:
$teams[] = $person['team'];
If you make this change, the other is not required. Note that it also removes the need for recursion, as $teams will contain only an array of strings.

recursively find all array values

So I have a big response from an API in a multi-dimensional array, and I need to find all the instances of a certain key->value pair, specifically ['type'] => PhotoField My task would be easy if they were all the same depth, but they vary, so I am using a recursive function to get all the key value pairs of a specific value. I have morphed a standard recursive array searching algorithm for my purposes. But I still have the problem that when it pushes the path of each instance to the $path array, it just merges to the path of the previous instance.
What I really need is for each instance's path to be a subarray within $path.
Here is my function:
function array_searchRecursive($needle, $haystack, $strict=false, $path=array() )
{
if(!array_key_exists('elements', $haystack)) {
return false;
}
foreach( $haystack['elements'] as $key => $val ) {
if( is_array($val) && $subPath = array_searchRecursive($needle, $val, $strict, $path) ) {
$path = array_merge($path, array($key), $subPath);
return $path;
} elseif( (!$strict && $val['type'] == $needle) || ($strict && $val['type'] === $needle) ) {
$path[] = $val['key'];
}
}
if (!(empty($path))){
return $path;
}
return false;
}
I call it with:
array_searchRecursive($resp['form']);
And here is some sample data:
$resp =
Array
(
[form] => Array
(
[name] => Site Inspection
[elements] => Array
(
[0] => Array
(
[type] => Section
[key] => 86d2
[elements] => Array
(
[0] => Array
(
[type] => ChoiceField
[key] => 450c
)
)
)
[1] => Array
(
[type] => Section
[key] => 6021
[elements] => Array
(
[0] => Array
(
[type] => TextField
[key] => c8e5
)
[1] => Array
(
[type] => PhotoField
[key] => 01dd
[label] => Photos of Protective Structure
)
[2] => Array
(
[type] => PhotoField
[key] => 8e1c
[label] => Photos of Degradation to Protective Structures
)
)
)
[2] => Array
(
[type] => Section
[key] => 9335
[elements] => Array
(
[0] => Array
(
[type] => TextField
[key] => b614
)
[1] => Array
(
[type] => Repeatable
[key] => 6b00
(
[0] => Array
(
[type] => TextField
[key] => b646
)
[1] => Array
(
[type] => PhotoField
[key] => 9747
)
)
)
)
)
)
)
Sincere thanks for any help. It is greatly appreciated.
This should work:
foreach($resp['form']['elements'] as $v)
{
if($v['type'] === 'PhotoField')
{
// we found it!
}
}

How to remove branches that don't contain a certain value in a php array

I've spent the day playing with deceze's answer but I'm no closer to making it work. I may have part of it, but not sure how to get recursion in array_filter.
My Array looks like this (sample):
Array
(
[name] => root
[ChildCats] => Array
(
[0] => Array
(
[name] => Air Conditioning
[ChildCats] => Array
(
[0] => Array
(
[name] => Ducted Air Conditioning
[ChildCats] => Array
(
[0] => Array
(
[name] => Supply & Install
[ChildCats] => Array
(
[0] => Array
(
[name] => Daiken
[S] => 6067
)
)
)
[1] => Array
(
[name] => Supply Only
[ChildCats] => Array
(
[0] => Array
(
[name] => Mitsubishi
[S] => 6026
)
)
)
)
)
[1] => Array
(
[name] => Split System Air Conditioning
[ChildCats] => Array
(
[0] => Array
(
[name] => Supply & Install
[ChildCats] => Array
(
[0] => Array
(
[name] => Daiken
[S] => 6067
)
[1] => Array
(
[name] => Fujitsu Split Air Conditioning Systems
[S] => 6464
)
[2] => Array
(
[name] => Mitsubishi Electric Split Air Conditioning Systems
[S] => 6464
)
)
)
)
)
)
)
[1] => Array
(
[name] => Appliance / White Goods
[ChildCats] => Array
(
[0] => Array
(
[name] => Clearance
[S] => 6239
)
[1] => Array
(
[name] => Cooktops
[ChildCats] => Array
(
[0] => Array
(
[name] => Ceramic Cooktops
[S] => 6239
)
[1] => Array
(
[name] => Element Cooktops
[S] => 6067
)
[2] => Array
(
[name] => Gas Cooktops
[S] => 6239
)
[3] => Array
(
[name] => Induction Cooktops
[S] => 6239
)
)
)
)
)
Now lets say I try to extract just the parts of the array relevent to the following keypair:
S => 6067.
I'd like the result to look like:
Array
(
[name] => root
[ChildCats] => Array
(
[0] => Array
(
[name] => Air Conditioning
[ChildCats] => Array
(
[0] => Array
(
[name] => Ducted Air Conditioning
[ChildCats] => Array
(
[0] => Array
(
[name] => Supply & Install
[ChildCats] => Array
(
[0] => Array
(
[name] => Daiken
[S] => 6067
)
)
)
)
)
[1] => Array
(
[name] => Split System Air Conditioning
[ChildCats] => Array
(
[0] => Array
(
[name] => Supply & Install
[ChildCats] => Array
(
[0] => Array
(
[name] => Daiken
[S] => 6067
)
)
)
)
)
)
)
[1] => Array
(
[name] => Appliance / White Goods
[ChildCats] => Array
(
[0] => Array
(
[name] => Cooktops
[ChildCats] => Array
(
[0] => Array
(
[name] => Element Cooktops
[S] => 6067
)
)
)
)
)
)
)
What I cannot get my head arround is should I be creating a new array or using array filter.
Playing with deceze code I've got the search working using the following:
function recursive_assoc_in_array(array $haystack, array $needle, $childKey = 'ChildCats') {
if (array_intersect_assoc($haystack, $needle)) {
echo "Found via array_intersect_assoc ".$haystack[name]."\n";
return true;
}
foreach ($haystack[$childKey] as $child) {
if (recursive_assoc_in_array($child, $needle, $childKey)) return true;
}
return false;
}
But if I try to process with,
$array = array_filter($array, function (array $values) {
return recursive_assoc_in_array($values, array('S' => '6067'));
});
I get the original array which leads me to think I have to get recursion running on the array_filter query.
At this point I just go blank.
Additionally, the array keys will need to be reindexed on the produced new array. Any ideas?
--Additional 7/7/14
How about if I try to build a new array from the old one?
I'm trying:
function exploreArrayandAdd($Array) {
if ($Array['ChildCats']) {
foreach ($Array['ChildCats'] as $key => $value) {
$NewArray['ChildCats'][] = exploreArrayandAdd($value);
}
} else {
if ($Array['S'] == 6026) {
//echo "1";
return $Array;
}
}
}
But cannot work out how to pass the new array out of the function?
Tried removing branches that don't match using:
function exploreArray(&$Array) {
if ($Array['ChildCats']) {
foreach ($Array['ChildCats'] as $key => $value) {
$result = exploreArray($Array['ChildCats'][$key]);
if ($result === false)
unset($Array['ChildCats'][$key]);
}
} else {
// print_r($Array);
if ($Array['S'] == 6026) {
return true;
} else {
unset($Array);
return false;
}
}
//if ($NoChildCat==true) print_r($Array);
}
But I believe it is the wrong way as it does work at the bottom of the array but not back up towards the top as siblings make result true.
Also this won't reindex the array keys.
function recursive_array_filter(array $array, $childKey, callable $test) {
if (isset($array[$childKey]) && is_array($array[$childKey])) {
foreach ($array[$childKey] as $key => &$child) {
if (!$child = recursive_array_filter($child, $childKey, $test)) {
unset($array[$childKey][$key]);
}
}
if (!$array[$childKey]) {
unset($array[$childKey]);
}
}
return !empty($array[$childKey]) || $test($array) ? $array : [];
}
$array = recursive_array_filter($array, 'ChildCats', function (array $array) {
return array_intersect_assoc($array, ['S' => 6026]);
});
To express the algorithm in words: you descend down into the array first, following all ChildCats branches to their end. In each level you return the values as they are back to the caller if they match your test or if they have children, or you return an emptied array (you could also return false if you prefer). If some child turns out empty, you prune it with unset.
I have implemented the test as a callback function here for best reusability of the code.

Get values (plural) from multi-dimensional array with key

I know the key, now I need ALL of the results that would yield when I search the 5000+ user database. Any user may have none, one or multiple locations, identified by an id and name field. Therefore I need the results in an array, not just the first/last one, but all of them. Below is an example of a user (actual array setup, fake data ;) )
Array
(
[ID] => 2
[user_login] => SomeGuy
[user_pass] => GreatEncryptedPassword
[user_nicename] => Some Guy
[user_email] => someguy#has-email.com
[user_url] => someguy.com
[user_registered] => 2013-04-11 11:18:58
[user_activation_key] =>
[user_status] => 0
[display_name] => Some Guy
[umeta_id] => 31
[user_id] => 2
[meta_key] => facebookmeta
[meta_value] => Array
(
[id] => 1234567890
[name] => Some Guy
[first_name] => Some
[last_name] => Guy
[link] => http://www.facebook.com/someguy
[username] => someguy
[birthday] => 02/21/1983
[location] => Array
(
[id] => 108276092536515 //actual ID, try at facebook.com/108276092536515
[name] => Arnhem, Netherlands
)
[gender] => male
[relationship_status] => In a Relationship
[significant_other] => Array
(
[name] => Some Chick
[id] => 12345678906789
)
[email] => someguy#has-email.com
[timezone] => 2
[locale] => nl_NL
[verified] => 1
[updated_time] => 2013-04-02T09:28:30+0000
)
)
As you can make out, this guy has 1 location in his facebook data, but other locations include the same [location] => Array ( [id] => 123 [name] => SomePlace ) for example for [work] or [education], or, or, or...
I've tried a recursive function like this:
function get_value_by_key( $array, $key ){
foreach( $array as $k => $each ){
if( $k == $key ){
return $each;
}
if( is_array( $each )){
if( $return = get_value_by_key($each,$key)){
return $return;
}
}
}
}
print_r( get_value_by_key( $users, 'location' );
How can I modify the above function to return an array of location arrays? This one return the above data of the first user containing a location, not the location itself or an array of them.
EDIT:
What I need as a result is something like this:
Array(
[0] => Array(
[id] => 1234567890
[name] => GreatCity, World
)
[1] => Array(
[id] => 2345678901
[name] => SomeCity, Blop
)
[2] => Array(
[id] => 3456789012
[name] => AnotherCity, Boo
)
)
Edit based on answer
Answer from Thomas David Plat gives me the correct results, but still a not very usable data structure. How to get the results out of the structure and into that shown in the above edit?
Results from David's answer:
Array
(
[0] => Array
(
[0] => Array
(
[0] => Array
(
[id] => 108276092536515
[name] => Arnhem, Netherlands
)
[1] => Array()
)
)
[1] => Array
(
[0] => Array
(
[0] => Array
(
[id] => 108276092536515
[name] => Arnhem, Netherlands
)
[1] => Array()
)
)
[2] => Array()
[3] => Array()
[4] => Array()
I've tried variations of array_filter, array_map, array_values and others like below:
$locations = array_map( 'array_filter', find_array_values( $users, 'location' ));
But the structure seems to stay...
UPDATED ANSWER
$matches = array();
function find_array_values($haystack, $needle)
{
global $matches;
$collection = array();
foreach($haystack AS $key => $value)
{
if($key === $needle)
{
$matches[] = $value;
}
else if(is_array($value))
{
find_array_values($value, $needle);
}
}
}
find_array_values($array, 'location');
print_r($matches);
?>
This will work, but as I already said in the comments it's a bad practice since it uses the global keyword. If you implement this function as a method into a class, instead of using the global keyword you could use a class variable which would be a neat solution.
OLD Answer
function find_array_values($haystack, $needle)
{
$collection = array();
foreach($haystack AS $key => $value)
{
if($key === $needle)
{
$collection[] = $value;
}
else if(is_array($value))
{
$collection[] = find_array_values($value, $needle);
}
}
return $collection;
}
echo "<pre>";
print_r(find_array_values($users, 'location'));
echo "</pre>";
Here you are. Just pass an array of your users into the function. The function will return an array of arrays of locations.
There's one restriction. The function will not search trough locations that are within locations.
you can modify ur location part of your array like this
[location] => Array
(
[work]=> Array(
[id] => 108276092536515
[name] => Arnhem, Netherlands
)
[home] => Array(
[id] => 108276092536515
[name] => Arnhem, Somewhere else
)
)
if you modify your array like this then your function will return array of arrays for location

Remove item from multidimensional array php

Here is the array:
[cart] => Array
(
[ProductId] => Array
(
[0] => P121100001
[1] => P121100002
)
[SellerId] => Array
(
[0] => S12110001
[1] => S12110001
)
[SpecifyId] => Array
(
[0] => 1
[1] => 2
)
[Quantity] => Array
(
[0] => 1
[1] => 1
)
[Price] => Array
(
[0] => 12
[1] => 29
)
[TotalPrice] => 41
)
I have the ProductId and I want to remove all the other items matching P121100002's key.
Is there an easy way to do this I can't can seem to come up with one?
You can loop through the full array and use unset() to, well, "unset" the specified index:
$index = array_search($cart['ProductId'], 'P121100002');
if ($index !== false) {
foreach ($cart as $key => $arr) {
unset($cart[$key][$index]);
}
}
The slight caveat to this approach is that it may disrupt your index orders. For instance, say you have:
[ProductId] => Array (
[0] => P121100001
[1] => P121100002
[2] => P121100003
)
And you want to remove P121100002, which has a corresponding index of 1. Using unset($cart['ProductId'][1]) will cause your array's to become:
[ProductId] => Array (
[0] => P121100001
[2] => P121100003
)
This may be something to remain concerned with if you're going to use a for loop to iterate through in the future. If it is, you can use array_values() to "reset" the indexes in the unset() loop from above:
foreach ($cart as $key => $arr) {
unset($cart[$key][$index]);
$cart[$key] = array_values($cart[$key]);
}
foreach($yourArray['ProductId'] as $key => $value) {
if ($value == $productIdToRemove) {
foreach($yourArray as $deleteKey => $deleteValue) {
unset($yourArray[$deleteKey][$key]);
}
break;
}
}
Use array_key_exists along with the unset() function

Categories