Is there a function that allows me to search inside a multidimensional array that goes multiple levels deep? An example of an array can be found below.
What I want is to be able to search in the entire array, regardless of how deep it goes (3 levels deep would be the practical limit though). The search must be done in all of the strings inside the array elements, and to make it even more complex, it needs to be able to find parts of a string in the array (preferably case insensitive).
I've searched for a good class or function that can handle this in a fast and efficient way, but haven't found one so far.
Array
(
[0] => Array
(
[OrderReferenceNumber] => 201100196
[OrderCustomerID] => 01239123
[OrderCustomerName] => test
[OrderHistoryItems] => Array
(
[0] => Array
(
[OrderItem] => productID
[OrderItemGroup] => productName
)
)
)
Much appreciated!
This will search all elements on all levels of the array, and the search is case sensitive. Just pass the search term and the array to search.
class mySearcher
{
protected $search = '';
public function search($search, $arr)
{
$this->search = $search;
$this->searchArr($arr);
}
protected function searchArr($arr)
{
if(is_array($arr))
{
foreach($arr as $value)
{
if(is_array($value))
{
$this->searchArr($value); // this element is an array, so recursively search it
}
else
{
if(stripos($value, $this->search) !== false)
{
// found the search term, do something
echo 'found in: ' . $value . '<br />';
}
}
}
}
}
}
$obj = new mySearcher();
$obj->search('test', $arr); // search for 'test' in the array called $arr
Related
I have a class with method add() that accepts strings and arrays. I need to have an array with all users, but I cannot seem to get it. All I get is multiple arrays with all users. How could I merge those arrays into one?
class Users {
function add($stringOrArray) {
$arr = array();
if(is_array($stringOrArray)) {
$arr = $stringOrArray;
} else if(is_string($stringOrArray)) {
$arr[] = $stringOrArray;
} else {
echo('errrrror');
}
print_r($arr);
}
When I use this test:
public function testOne() {
$users = new Users();
$users->add('Terrell Irving');
$users->add('Magdalen Sara Tanner');
$users->add('Chad Niles');
$users->add(['Mervin Spearing', 'Dean Willoughby', 'David Prescott']);
This is what I get, multiple arrays but I need one array.
Array
(
[0] => Terrell Irving
)
Array
(
[0] => Magdalen Sara Tanner
)
Array
(
[0] => Chad Niles
)
Array
(
[0] => Mervin Spearing
[1] => Dean Willoughby
[2] => David Prescott
)
You can cut a lot of unnecessary bloat from your method.
You can cast ALL incoming data to array type explicitly. This will convert a string into an array containing a single element. If the variable is already an array, nothing will change about the value.
Use the spread operator (...) to perform a variadic push into the class property.
Code: (Demo)
class Users
{
public $listOfUsers = [];
function add($stringOrArray): void
{
array_push($this->listOfUsers, ...(array)$stringOrArray);
}
}
$users = new Users;
$users->add('Terrell Irving');
$users->add(['Magdalen Sara Tanner', 'Chad Niles']);
$users->add(['Mervin Spearing']);
var_export($users->listOfUsers);
Output:
array (
0 => 'Terrell Irving',
1 => 'Magdalen Sara Tanner',
2 => 'Chad Niles',
3 => 'Mervin Spearing',
)
All you need is to store the added users in a class property, for example $listOfUsers.
If adding the array you use the array_merge() function otherwise just add new user at the end of indexed array.
<?php
class Users {
// here will be all the users stored
public $listOfUsers = array();
function add($stringOrArray) {
//$arr = array();
if(is_array($stringOrArray)) {
// merge two arrays - could create duplicate records
$this->listOfUsers = array_merge($this->listOfUsers, $stringOrArray);
} else if(is_string($stringOrArray)) {
// simply add new item into the array
$this->listOfUsers[] = $stringOrArray;
} else {
echo('errrrror');
}
print_r($this->listOfUsers);
}
}
In your example you are storing the data locally within the method add() and it is not kept for future usage. This behavior is corrected using the class property $listOfUsers that can be accesed using $this->listOfUsers within the class object and if needed outside of the class.
$q = $_POST['q'];
$inCart = isset($_COOKIE['cart']) ? unserialize($_COOKIE['cart']) : array();
function alreadyInCart() {
global $inCart, $good, $q;
foreach ($inCart as $inCart1) {
if ($inCart1[0] == $good->id) { // if this good already in cart
$inCart1[1] = $inCart1[1] + $q; // write sum of q's to existing array
return true; // and return true
}
}
return false; // return false if not
}
if (alreadyInCart() == false) { // if good added to cart for the first time
$inCart[] = array($good->id, $q); // add array at the end of array
}
Hello. So my problem is that I'm running a function to find out if $good->id is already inside of 2d $inCart array.
$inCart looks something like this:
Array
(
[0] => Array
(
[0] => 6
[1] => 1
)
[1] => Array
(
[0] => 5
[1] => 1
)
)
Where [0] is a good ID and [1] is an amount of this good in a cart.
So I tracked that function actually does what I want and returns true/false as expected, but looks like it only does it inside of itself. Cause if I put print_r($inCart1[1]) inside of a function it does add up and outputs the sum, as expected. But when I output the array at the end of the code (outside the function) the amount doesn't add up, just stays how it was before the function run.
Any ideas why that happens?
Ok, in case someone faces the same problem: found a solution.
Or should I say found a mistake?
The problem was with the foreach ($inCart as $inCart1). Must be replaced with foreach ($inCart as &$inCart1) in order to change array values in a loop. The other way, it just reads values, bit can't change them.
Here is the function I wrote to flatten the multidimensional PHP array:
function flattenArray(array $array) {
if (! is_array($array)) {
throw new Exception ("Please specify an array.");
}
$resultArray = [];
$arrayObject = new RecursiveArrayIterator($array);
foreach(new RecursiveIteratorIterator($arrayObject) as $key => $value) {
$resultArray[$key] = $value;
}
return $resultArray;
}
And using it:
$arr = [
["sitepoint", "phpmaster"],
["buildmobile", "rubysource"],
["designfestival", "cloudspring"],
"not an array"
];
print_r(flattenArray($arr));
Result:
Array
(
[0] => designfestival
[1] => cloudspring
[3] => not an array
)
However, I was expecting:
0: sitepoint
1: phpmaster
2: buildmobile
3: rubysource
4: designfestival
5: cloudspring
6: not an array
But it is re-generating indexes as in:
0: sitepoint
1: phpmaster
0: buildmobile
1: rubysource
0: designfestival
1: cloudspring
3: not an array
So how do I modify function to get all elements of the array not just three:
Array
(
[0] => designfestival
[1] => cloudspring
[3] => not an array
)
Thanks for the help
if (!is_array($array)) is superfluous, since you have the array type hint in the function signature and PHP will enforce that.
You are overwriting the keys. Those elements all have the same keys in their respective subarray. Since it's not an associative array, you don't need to preserve the keys. Instead of
$resultArray[$key] = $value;
just do
$resultArray[] = $value;
I too hit this limitation with RecursiveIteratorIterator.
At first I had been using this concise, one-line array flattener wherever needed:
$outputs = iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveArrayIterator([$inputs])), FALSE);
similar to your longer function above.
All was great: I was able to "normalize" my data structure into a 1D array, no matter if the incoming $inputs parameter came into my Symfony2 Controller as a single String/float value, 1D or 2+D multidimensional array. (I was writing a callback from AJAX that is to respond with JSON-formatted tables for an interactive Highcharts.com chart to be able to render, in my financial app.)
However, it refused to draw because in the final step, each data cell was in the form
0 => float 100.662
even though I had taken care that my $inputs creature only contained cells in the form:
'2002-04-30' => float 100.662
So basically the above array-flattening line had killed the keys (DateStamp).
Fed up with studying RecursiveIteratorIterator, I just broke down and came up with my own array_flatten that preserves keys, if any:
static public function array_flatten($inObj)
{
$outObj = []; $inObj=[$inObj];
array_walk_recursive($inObj, function ($incell, $inkey) use (&$outObj)
{
$outObj[$inkey] = $incell;
} );
return $outObj;
}
Note that you are responsible for ensuring that the keys in $inObj are globally unique (and either string or int type), otherwise, I don't know how my function behaves. Probably overwrites the value using the same key name?
I have function that returns the following multidimensional array. I don't have control of how the array is formed. Im trying to access the 'Result' elements. This issue is, the name of the parent elements constantly changing. The location of the 'Result' element is always the same (as the is the name "Result"). Is it possible to access that element without know the name of the parent elements?
Array
(
[sHeader] => Array
(
[aAction] => ActionHere
)
[sBody] => Array
(
[CreatePropertyResponse] => Array
(
[CreatePropertyResult] => Array
(
[Message] => Successfully completed the operation
[Result] => 0
[TransactionDate] => 2013-05-19T21:54:35.765625Z
[bPropertyId] => 103
)
)
)
)
An easy option to search the array keys/values recursively is to use a recursive iterator; these are built-in classes, part of the Standard PHP Library.
$result = false;
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
foreach ($iterator as $key => $value) {
if ($key === 'Result') {
$result = $value;
break;
}
}
var_dump($result);
The bonus here is that you could, if you wanted to, check the depth of the Result item ($iterator->getDepth()) in the array structure and/or check one or more ancestor keys ($iterator->getSubIterator(…)->key()).
If the parent elements have only one child, you can solve it by getting the only element given back by array_keys(), and going two levels deep.
Anyway, if your array changes that much, and you systematically have to access a nested property, you have a design issue for sure.
Edit: array_column won't actually work in this case. You could search through each level, recursively, until you find the given key. Something like:
function find_key_value($array, $search_key) {
if (isset($array[$search_key])) return $array[$search_key];
$found = false;
foreach ($array as $key=>$value) {
if (is_array($value)) $found = find_key_value($value, $search_key);
if ($found) return $found;
}
return false;
}
function findkeyval($arr,$key) {
if(isset($arr[$key])) {
return $arr[$key];
}else {
foreach($arr as $a) {
if(is_array($a)) {
$val=findkeyval($a,$key);
if($val) {
return $val;
}
}
}
}
}
I need to selectively flatten an array in PHP, but do it selectively. If a key matches a pattern then all sub elements below that key should be included in the 'flat' output.
SO If I had a catalogue of music:
=> array of albums => each of which is an array of song titles
Then I could search for a string, and would get a flat array in reponse. SO if I searched for 'sun' then I would get the entire catalogue for any artist with 'sun' in their name, plus the albums for other artists where 'sun' was in the album name.
Hopefully that makes sense.
Anyone got any thoughts?
Is there a reason you're not using a database to store what sounds like a significant amount of info? It would be fairly simple to write a query in SQL to pull the data out that you want.
Ok, I'm going to assume your data looks like this:
$data = array(
"Bill Withers" => array (
"Lovely Day",
"Use Me",
"Ain't No Sunshine"
),
"Fleet Foxes" => array (
"Sun It Rises",
"White Winter Hymnal"
),
"Billy Joel" => array (
"Piano Man"
)
);
...and that given the input "Bill", you want the output: ["Lovely Day", "Use Me", "Ain't No Sunshine", "Piano Man"]. Here's one way you could do it.
function getSongs($data, $searchTerm) {
$output = array();
foreach ($data as $artist => $songs) {
if (stripos($artist, $searchTerm) !== false)) {
$output = array_merge($output, $songs);
}
}
return $output;
}
...I'll also assume you've got a good reason to not use a database for this.
Do you mean something like that?
$a = array(
"Sunny" => "Bla-bla1",
"Sun" => "Bla-bla2",
"Sunshine" => "Bla-bla3",
);
foreach ($a as $k => $v)
{
// check here whenever you want for $k
// apply something to $v
if ( ... )
$b[$i++] = $v;
}
$sample_data = array(
'artist_name' => array(
'album_name' => array(
'song_name',
'other_mp3_id_info'
)
)
);
function s2($needle,$haystack) {
$threads = array();
foreach ($haystack as $artist_name => $albums) {
if (stripos($artist_name, $needle) !== false)) {
$threads[] = $haystack[$artist_name]; //Add all artist's albums
} else {
foreach ($albums as $album_name => $songs) {
if (stripos($album_name, $needle) !== false)) {
$threads[$artist_name][] = $haystack[$album_name]; //add matching album
}
}
}
}
return $threads;
}
To build off NickF's work, assuming your data looks like his, I'd write the function this way.
function getSongs($data, $searchTerm) {
foreach ($data as $artist => $songs) {
if (stripos($artist, $searchTerm) !== false)) {
$output[$artist] = $songs;
}
}
return $output or null;
}
The results of this function, when no matches are found will obviously return null instead of a blank array; when several matches are found they will then be grouped by their artist. I find direct assignment, $output[$artist] = $songs, to provide more predictable results than array_merge in my own experience. (This also preserves the artist for outputting that data.)
Like NickF said, I would assume you've good reason to not do this with a database? SQL for this would be very simple, such as,
SELECT artist, song FROM songs WHERE artist LIKE '%Bill%' GROUP BY artist;
You can use preg_grep for the searches, be sure to sanitize the input, though:
$matchingArtists = preg_grep('/' . preg_quote($searchString) . '/', array_keys($data));
$matchingSongs = array();
foreach ($data as $artist => $songs) {
$matchingSongs = array_merge($matchingSongs, preg_grep('/' . preg_quote($searchString) . '/', $songs));
}