I have an array as follows
array(2) {
["operator"] => array(2) {
["qty"] => int(2)
["id"] => int(251)
}
["accessory209"] => array(2) {
["qty"] => int(1)
["id"] => int(209)
}
["accessory211"] => array(2) {
["qty"] => int(1)
["id"] => int(211)
}
}
I'm trying to find a way to verify an id value exists within the array and return bool. I'm trying to figure out a quick way that doesn't require creating a loop. Using the in_array function did not work, and I also read that it is quite slow.
In the php manual someone recommended using flip_array() and then isset(), but I can't get it to work for a 2-d array.
doing something like
if($array['accessory']['id'] == 211)
would also work for me, but I need to match all keys containing accessory -- not sure how to do that
Anyways, I'm spinning in circles, and could use some help.
This seems like it should be easy. Thanks.
array_walk() can be used to check whether a particular value is within the array; - it iterates through all the array elements which are passed to the function provided as second argument. For example, the function can be called as in the following code.
function checkValue($value, $key) {
echo $value['id'];
}
$arr = array(
'one' => array('id' => 1),
'two' => array('id' => 2),
'three' => array('id' => 3)
);
array_walk($arr, 'checkValue');
This function is useful in_array(211, $array['accessory']); It verifies the whole specified array to see if your value exists in there and returns true.
in_array
$map = array();
foreach ($arr as $v) {
$map[$v['id']] = 1;
}
//then you can just do this as when needed
$exists = isset($map[211]);
Or if you need the data associated with it
$map = array();
foreach ($arr as $k => $v) {
$map[$v['id']][$k] = $v;
}
print_r($map[211]);
<?php
//PHP 5.3 way to do it
function searchNestedArray(array $array, $search, $mode = 'value') {
foreach (new RecursiveIteratorIterator(new RecursiveArrayIterator($array)) as $key => $value) {
if ($search === ${${"mode"}})
return true;
}
return false;
}
$data = array(
array('abc', 'ddd'),
'ccc',
'bbb',
array('aaa', array('yyy', 'mp' => 555))
);
var_dump(searchNestedArray($data, 555));
I used a static method because i needed it in a class, but if you want you can use it as a simple function.
/**
* Given an array like this
* array(
* 'id' => "first",
* 'title' => "First Toolbar",
* 'class' => "col-md-12",
* 'items' => array(
* array(
* 'tipo' => "clientLink",
* 'id' => "clientLinkInsert"
* )
* )
* )
*
* and array search like this
* array("items", 0, "id")
*
* Find the path described by the array search, in the original array
*
* #param array $array source array
* #param array $search the path to the item es. ["items", 0, "tipo"]
* #param null|mixed $defaultIfNotFound the default value if the value is not found
*
* #return mixed
*/
public static function getNestedKey($array, $search, $defaultIfNotFound = null)
{
if( count($search) == 0 ) return $defaultIfNotFound;
else{
$firstElementSearch = self::array_kshift($search);
if (array_key_exists($firstElementSearch, $array)) {
if( count($search) == 0 )
return $array[$firstElementSearch];
else
return self::getNestedKey($array[$firstElementSearch], $search, $defaultIfNotFound);
}else{
return $defaultIfNotFound;
}
}
}
You can use
Arr::getNestedElement($array, $keys, $default = null)
from this library to get value from multidimensional array using keys specified like 'key1.key2.key3' or ['key1', 'key2', 'key3'] and fallback to default value if no element was found. Using your example it will look like:
if (Arr::getNestedElement($array, 'accessory.id') == 211)
Related
Just as the title implies, I am trying to create a parser and trying to find the optimal solution to convert something from dot namespace into a multidimensional array such that
s1.t1.column.1 = size:33%
would be the same as
$source['s1']['t1']['column']['1'] = 'size:33%';
Try this number...
function assignArrayByPath(&$arr, $path, $value, $separator='.') {
$keys = explode($separator, $path);
foreach ($keys as $key) {
$arr = &$arr[$key];
}
$arr = $value;
}
CodePad
It will loop through the keys (delimited with . by default) to get to the final property, and then do assignment on the value.
If some of the keys aren't present, they're created.
FYI In Laravel we have a array_set() helper function which translates in this function
Method to store in an array using dot notation
/**
* Set an array item to a given value using "dot" notation.
*
* If no key is given to the method, the entire array will be replaced.
*
* #param array $array
* #param string $key
* #param mixed $value
* #return array
*/
public static function set(&$array, $key, $value)
{
if (is_null($key)) {
return $array = $value;
}
$keys = explode('.', $key);
while (count($keys) > 1) {
$key = array_shift($keys);
// If the key doesn't exist at this depth, we will just create an empty array
// to hold the next value, allowing us to create the arrays to hold final
// values at the correct depth. Then we'll keep digging into the array.
if (! isset($array[$key]) || ! is_array($array[$key])) {
$array[$key] = [];
}
$array = &$array[$key];
}
$array[array_shift($keys)] = $value;
return $array;
}
It's simple as
$array = ['products' => ['desk' => ['price' => 100]]];
array_set($array, 'products.desk.price', 200);
// ['products' => ['desk' => ['price' => 200]]]
You may check it in the docs
If you need to instead get the data using dot notation the process is a bit longer, but served on a plate by array_get() which translates to this function (actually the linked source shows you all the helper array related class)
Method to read from an an array using dot notation
/**
* Get an item from an array using "dot" notation.
*
* #param \ArrayAccess|array $array
* #param string $key
* #param mixed $default
* #return mixed
*/
public static function get($array, $key, $default = null)
{
if (! static::accessible($array)) {
return value($default);
}
if (is_null($key)) {
return $array;
}
if (static::exists($array, $key)) {
return $array[$key];
}
if (strpos($key, '.') === false) {
return $array[$key] ?? value($default);
}
foreach (explode('.', $key) as $segment) {
if (static::accessible($array) && static::exists($array, $segment)) {
$array = $array[$segment];
} else {
return value($default);
}
}
return $array;
}
As you can see, it uses two submethods, accessible() and exists()
/**
* Determine whether the given value is array accessible.
*
* #param mixed $value
* #return bool
*/
public static function accessible($value)
{
return is_array($value) || $value instanceof ArrayAccess;
}
And
/**
* Determine if the given key exists in the provided array.
*
* #param \ArrayAccess|array $array
* #param string|int $key
* #return bool
*/
public static function exists($array, $key)
{
if ($array instanceof ArrayAccess) {
return $array->offsetExists($key);
}
return array_key_exists($key, $array);
}
Last thing it uses, but you can probably skip that, is value() which is
if (! function_exists('value')) {
/**
* Return the default value of the given value.
*
* #param mixed $value
* #return mixed
*/
function value($value)
{
return $value instanceof Closure ? $value() : $value;
}
}
You can use this function to convert dot notation arrray to multidimensional array.
function flattenToMultiDimensional(array $array, $delimiter = '.')
{
$result = [];
foreach ($array as $notations => $value) {
// extract keys
$keys = explode($delimiter, $notations);
// reverse keys for assignments
$keys = array_reverse($keys);
// set initial value
$lastVal = $value;
foreach ($keys as $key) {
// wrap value with key over each iteration
$lastVal = [
$key => $lastVal
];
}
// merge result
$result = array_merge_recursive($result, $lastVal);
}
return $result;
}
Example:
$array = [
'test.example.key' => 'value'
];
print_r(flattenToMultiDimensional($array));
Output:
Array
(
[test] => Array
(
[example] => Array
(
[key] => value
)
)
)
I would suggest using dflydev/dot-access-data.
If you're not familiar with using Composer, head over to https://getcomposer.org/ for an introduction so that you can download and autoload the package as as dependency for your project.
Once you have the package, you can load a multi-dimensional array into a Data object:
use Dflydev\DotAccessData\Data;
$data = new Data(array(
's1' => array(
't1' => array(
'column' => array(
'1' => 'size:33%',
),
),
),
);
And access the values using dot notation:
$size = $username = $data->get('s1.t1.column.1');
You should try this one : https://symfony.com/doc/current/components/property_access.html
It allows you to access anything like arrays, objects, setters, getters, etc... with doted notation.
You can use it standalone without symfony. If your project use composer, it's a one line install.
Here is my solution:
<?php
// worked with any lvl
function unFlatArr($array, $delimiter = '_')
{
$result = [];
foreach ($array as $notations => $value) {
$keys = explode($delimiter, $notations);
$keys = array_reverse($keys);
$lastVal = $value;
foreach ($keys as $key) {
$lastVal = [$key => $lastVal];
}
// merge result
$result = array_merge_recursive($result, $lastVal);
}
return $result;
}
$array = ['s1.t1.column.1' => 'size:33%'];
var_export(unFlatArr($array, '.'));
Although pasrse_ini_file() can also bring out multidimensional array, I will present a different solution. Zend_Config_Ini()
$conf = new Zend_COnfig_Ini("path/to/file.ini");
echo $conf -> one -> two -> three; // This is how easy it is to do so
//prints one.two.three
I found a solution that worked for me at: Convert Flat PHP Array to Nested Array based on Array Keys and since I had an array based on an .ini file with different keys I made a tiny modification of that script and made work for me.
My array looked like this:
[resources.db.adapter] => PDO_MYSQL
[resources.db.params.host] => localhost
[resources.db.params.dbname] => qwer
[resources.db.params.username] => asdf
...
On request, this is the code that I described was working for me:
<?php
echo "remove the exit :-)"; exit;
$db_settings = parse_ini_file($_SERVER['DOCUMENT_ROOT'].'/website/var/config/app.ini');
echo "<pre>";
print_r($db_settings);
echo "</pre>";
$resources = array();
foreach ($db_settings as $path => $value) {
$ancestors = explode('.', $path);
set_nested_value($resources, $ancestors, $value);
}
echo "<pre>";
print_r($resources);
echo "</pre>";
/**
* Give it and array, and an array of parents, it will decent into the
* nested arrays and set the value.
*/
function set_nested_value(array &$arr, array $ancestors, $value) {
$current = &$arr;
foreach ($ancestors as $key) {
// To handle the original input, if an item is not an array,
// replace it with an array with the value as the first item.
if (!is_array($current)) {
$current = array( $current);
}
if (!array_key_exists($key, $current)) {
$current[$key] = array();
}
$current = &$current[$key];
}
$current = $value;
}
This is the source of the .ini file read by the parse_ini_file():
Array
(
[resources.db.adapter] => PDO_MYSQL
[resources.db.params.host] => localhost
[resources.db.params.dbname] => dbname
[resources.db.params.username] => dbname_user
[resources.db.params.password] => qwerqwerqwerqwer
[resources.db.params.charset] => utf8
[externaldb.adapter] => PDO_MYSQL
[externaldb.params.host] => localhost
[externaldb.params.dbname] => dbname2
[externaldb.params.username] => dbname_user2
[externaldb.params.password] => qwerqwerwqerqerw
[externaldb.params.charset] => latin1
)
This is the outcome of the code above:
Array
(
[resources] => Array
(
[db] => Array
(
[adapter] => PDO_MYSQL
[params] => Array
(
[host] => localhost
[dbname] => dbname
[username] => dbname_user
[password] => qwerqwerqwerqwer
[charset] => utf8
)
)
)
[externaldb] => Array
(
[adapter] => PDO_MYSQL
[params] => Array
(
[host] => localhost
[dbname] => dbname2
[username] => dbname_user2
[password] => qwerqwerwqerqerw
[charset] => latin1
)
)
)
I am pretty sure you are trying to do this to store some configuration data or similar.
I highly suggest you to save such file as .ini and use parse_ini_file() function to change the configuration data into a multidimensional array. As simple as this
$confArray = parse_ini_file("filename.ini");
var_dump($confArray);
Quick and dirty...
<?php
$input = 'one.two.three = four';
list($key, $value) = explode('=', $input);
foreach (explode('.', $key) as $keyName) {
if (false === isset($source)) {
$source = array();
$sourceRef = &$source;
}
$keyName = trim($keyName);
$sourceRef = &$sourceRef[$keyName];
}
$sourceRef = $value;
unset($sourceRef);
var_dump($source);
I have a string, I need to know at what index is that string exist in the array. My array is as follows:
array(3)
{
[0]=>object(stdClass)#47170 (3)
{
["countries"]=>string(2) "HK"
["last_seen_date"]=>string(10) "2016-09-17"
["ad_uid"]=>string(14) "157d5908a1ca83"
}
[1]=>object(stdClass)#47171 (3)
{
["countries"]=>string(2) "HK"
["last_seen_date"]=>string(10) "2016-09-27"
["ad_uid"]=>string(14) "157d7978513bc3"
}
[2]=>object(stdClass)#47230 (3)
{
["countries"]=>string(2) "HK"
["last_seen_date"]=>string(10) "2016-09-27"
["ad_uid"]=>string(14) "157ea7239824e9"
}
}
The last seen date is:2016-09-27.
I would like to know at what index does 2016-09-27 exist in the array. So I know what is ad_uid related to that date. I have a method which does this.
public function getAd_uid($last_seen_date,$values){
$key = array_keys($values,$last_seen_date);
print_r($key);
}
The result gets an empty array. I have tried array_serach() has same empty results. Any other alternative solutions to achieve results?
To find all $ad_uids last_seen at particular date you can use array_filter which will return you all elements you are looking for. If you need ad_uids only, you can apply array_map to that array as following:
<?php
// $array is the array in question.
$filtered = array_filter($array, function($item) {
return $item->last_seen_date == "2016-09-27";
});
$ad_uids = array_map(function($item){return $item->ad_uid;}, $filtered);
Example
As each each entry of your array is an object and you know the attributs' names of theses objects (I assume they never change), I would do it like this :
/**
* #param string $last_seen_date
* #param array $values
* #return mixed null|int
*/
function getAdUid($last_seen_date, array $values) {
// Just in case no entry match
$matching_index = null;
// Loop through each entry: $entry is an object
foreach($values as $index => $entry) {
if($entry->last_seen_date == $last_seen_date) {
$matching_index = $index;
break; // end loop: we found that we are looking for
}
}
return $matching_index;
}
to do that just loop your array
foreach($values as $key => $row) {
// do something
}
then check if $last_seen_date is equal to the loop index last_seen_date $row->last_seen_date
if ($row->last_seen_date == $last_seen_date) {
return $key;
}
if it is just return it
return $key;
so your php code would be like this
$arr = array(
0 =>
(object)array(
"countries" => "HK",
"last_seen_date" => "2016-09-17",
"ad_uid"=> "157d5908a1ca83"
),
1 =>
(object)array(
"countries" => "HK",
"last_seen_date" => "2016-09-20",
"ad_uid" => "157d7978513bc3"
),
2 =>
(object)array(
"countries" => "HK",
"last_seen_date" => "2016-09-26",
"ad_uid" => "157ea7239824e9"
)
);
function getAd_uid($last_seen_date, $values){
foreach($values as $key => $row) {
if ($row->last_seen_date == $last_seen_date) {
return $key;
}
}
}
echo '2016-09-17 is on index => '.getAd_uid('2016-09-17', $arr).'<br>';
echo '2016-09-20 is on index => '.getAd_uid('2016-09-20', $arr).'<br>';
echo '2016-09-26 is on index => '.getAd_uid('2016-09-26', $arr).'<br>';
RESULT
Working Demo
Just as the title implies, I am trying to create a parser and trying to find the optimal solution to convert something from dot namespace into a multidimensional array such that
s1.t1.column.1 = size:33%
would be the same as
$source['s1']['t1']['column']['1'] = 'size:33%';
Try this number...
function assignArrayByPath(&$arr, $path, $value, $separator='.') {
$keys = explode($separator, $path);
foreach ($keys as $key) {
$arr = &$arr[$key];
}
$arr = $value;
}
CodePad
It will loop through the keys (delimited with . by default) to get to the final property, and then do assignment on the value.
If some of the keys aren't present, they're created.
FYI In Laravel we have a array_set() helper function which translates in this function
Method to store in an array using dot notation
/**
* Set an array item to a given value using "dot" notation.
*
* If no key is given to the method, the entire array will be replaced.
*
* #param array $array
* #param string $key
* #param mixed $value
* #return array
*/
public static function set(&$array, $key, $value)
{
if (is_null($key)) {
return $array = $value;
}
$keys = explode('.', $key);
while (count($keys) > 1) {
$key = array_shift($keys);
// If the key doesn't exist at this depth, we will just create an empty array
// to hold the next value, allowing us to create the arrays to hold final
// values at the correct depth. Then we'll keep digging into the array.
if (! isset($array[$key]) || ! is_array($array[$key])) {
$array[$key] = [];
}
$array = &$array[$key];
}
$array[array_shift($keys)] = $value;
return $array;
}
It's simple as
$array = ['products' => ['desk' => ['price' => 100]]];
array_set($array, 'products.desk.price', 200);
// ['products' => ['desk' => ['price' => 200]]]
You may check it in the docs
If you need to instead get the data using dot notation the process is a bit longer, but served on a plate by array_get() which translates to this function (actually the linked source shows you all the helper array related class)
Method to read from an an array using dot notation
/**
* Get an item from an array using "dot" notation.
*
* #param \ArrayAccess|array $array
* #param string $key
* #param mixed $default
* #return mixed
*/
public static function get($array, $key, $default = null)
{
if (! static::accessible($array)) {
return value($default);
}
if (is_null($key)) {
return $array;
}
if (static::exists($array, $key)) {
return $array[$key];
}
if (strpos($key, '.') === false) {
return $array[$key] ?? value($default);
}
foreach (explode('.', $key) as $segment) {
if (static::accessible($array) && static::exists($array, $segment)) {
$array = $array[$segment];
} else {
return value($default);
}
}
return $array;
}
As you can see, it uses two submethods, accessible() and exists()
/**
* Determine whether the given value is array accessible.
*
* #param mixed $value
* #return bool
*/
public static function accessible($value)
{
return is_array($value) || $value instanceof ArrayAccess;
}
And
/**
* Determine if the given key exists in the provided array.
*
* #param \ArrayAccess|array $array
* #param string|int $key
* #return bool
*/
public static function exists($array, $key)
{
if ($array instanceof ArrayAccess) {
return $array->offsetExists($key);
}
return array_key_exists($key, $array);
}
Last thing it uses, but you can probably skip that, is value() which is
if (! function_exists('value')) {
/**
* Return the default value of the given value.
*
* #param mixed $value
* #return mixed
*/
function value($value)
{
return $value instanceof Closure ? $value() : $value;
}
}
You can use this function to convert dot notation arrray to multidimensional array.
function flattenToMultiDimensional(array $array, $delimiter = '.')
{
$result = [];
foreach ($array as $notations => $value) {
// extract keys
$keys = explode($delimiter, $notations);
// reverse keys for assignments
$keys = array_reverse($keys);
// set initial value
$lastVal = $value;
foreach ($keys as $key) {
// wrap value with key over each iteration
$lastVal = [
$key => $lastVal
];
}
// merge result
$result = array_merge_recursive($result, $lastVal);
}
return $result;
}
Example:
$array = [
'test.example.key' => 'value'
];
print_r(flattenToMultiDimensional($array));
Output:
Array
(
[test] => Array
(
[example] => Array
(
[key] => value
)
)
)
I would suggest using dflydev/dot-access-data.
If you're not familiar with using Composer, head over to https://getcomposer.org/ for an introduction so that you can download and autoload the package as as dependency for your project.
Once you have the package, you can load a multi-dimensional array into a Data object:
use Dflydev\DotAccessData\Data;
$data = new Data(array(
's1' => array(
't1' => array(
'column' => array(
'1' => 'size:33%',
),
),
),
);
And access the values using dot notation:
$size = $username = $data->get('s1.t1.column.1');
You should try this one : https://symfony.com/doc/current/components/property_access.html
It allows you to access anything like arrays, objects, setters, getters, etc... with doted notation.
You can use it standalone without symfony. If your project use composer, it's a one line install.
Here is my solution:
<?php
// worked with any lvl
function unFlatArr($array, $delimiter = '_')
{
$result = [];
foreach ($array as $notations => $value) {
$keys = explode($delimiter, $notations);
$keys = array_reverse($keys);
$lastVal = $value;
foreach ($keys as $key) {
$lastVal = [$key => $lastVal];
}
// merge result
$result = array_merge_recursive($result, $lastVal);
}
return $result;
}
$array = ['s1.t1.column.1' => 'size:33%'];
var_export(unFlatArr($array, '.'));
Although pasrse_ini_file() can also bring out multidimensional array, I will present a different solution. Zend_Config_Ini()
$conf = new Zend_COnfig_Ini("path/to/file.ini");
echo $conf -> one -> two -> three; // This is how easy it is to do so
//prints one.two.three
I found a solution that worked for me at: Convert Flat PHP Array to Nested Array based on Array Keys and since I had an array based on an .ini file with different keys I made a tiny modification of that script and made work for me.
My array looked like this:
[resources.db.adapter] => PDO_MYSQL
[resources.db.params.host] => localhost
[resources.db.params.dbname] => qwer
[resources.db.params.username] => asdf
...
On request, this is the code that I described was working for me:
<?php
echo "remove the exit :-)"; exit;
$db_settings = parse_ini_file($_SERVER['DOCUMENT_ROOT'].'/website/var/config/app.ini');
echo "<pre>";
print_r($db_settings);
echo "</pre>";
$resources = array();
foreach ($db_settings as $path => $value) {
$ancestors = explode('.', $path);
set_nested_value($resources, $ancestors, $value);
}
echo "<pre>";
print_r($resources);
echo "</pre>";
/**
* Give it and array, and an array of parents, it will decent into the
* nested arrays and set the value.
*/
function set_nested_value(array &$arr, array $ancestors, $value) {
$current = &$arr;
foreach ($ancestors as $key) {
// To handle the original input, if an item is not an array,
// replace it with an array with the value as the first item.
if (!is_array($current)) {
$current = array( $current);
}
if (!array_key_exists($key, $current)) {
$current[$key] = array();
}
$current = &$current[$key];
}
$current = $value;
}
This is the source of the .ini file read by the parse_ini_file():
Array
(
[resources.db.adapter] => PDO_MYSQL
[resources.db.params.host] => localhost
[resources.db.params.dbname] => dbname
[resources.db.params.username] => dbname_user
[resources.db.params.password] => qwerqwerqwerqwer
[resources.db.params.charset] => utf8
[externaldb.adapter] => PDO_MYSQL
[externaldb.params.host] => localhost
[externaldb.params.dbname] => dbname2
[externaldb.params.username] => dbname_user2
[externaldb.params.password] => qwerqwerwqerqerw
[externaldb.params.charset] => latin1
)
This is the outcome of the code above:
Array
(
[resources] => Array
(
[db] => Array
(
[adapter] => PDO_MYSQL
[params] => Array
(
[host] => localhost
[dbname] => dbname
[username] => dbname_user
[password] => qwerqwerqwerqwer
[charset] => utf8
)
)
)
[externaldb] => Array
(
[adapter] => PDO_MYSQL
[params] => Array
(
[host] => localhost
[dbname] => dbname2
[username] => dbname_user2
[password] => qwerqwerwqerqerw
[charset] => latin1
)
)
)
I am pretty sure you are trying to do this to store some configuration data or similar.
I highly suggest you to save such file as .ini and use parse_ini_file() function to change the configuration data into a multidimensional array. As simple as this
$confArray = parse_ini_file("filename.ini");
var_dump($confArray);
Quick and dirty...
<?php
$input = 'one.two.three = four';
list($key, $value) = explode('=', $input);
foreach (explode('.', $key) as $keyName) {
if (false === isset($source)) {
$source = array();
$sourceRef = &$source;
}
$keyName = trim($keyName);
$sourceRef = &$sourceRef[$keyName];
}
$sourceRef = $value;
unset($sourceRef);
var_dump($source);
Is there a cleaner way to extract a nested value from a 3 level deep multidimensional array, where i want to pull the result stacked inside the 3rd level, hwoever, i want to keep this dynamic so i can also grab elem from 2nd or 4th level by using an array as a parameter to determine this.
what im trying to do in the end is SORT using this element, but i cant find a way to conveniently indicate the element chain except for this way which i had to create myself:
public function keyBySubElement($nestedArray, array $subElemStack){
//essentially the loop below is doing this, but it is dynamic now so a user can specify different nested levels in the $subElemStack param.
//$nestedValue = $nestedArray[$subElemStack[0]][$subElemStack[1]];
foreach($subElemStack as $nestedElement){
if(isset($nestedValue) && is_array($nestedValue))
{
$nestedValue = $nestedValue[$nestedElement];
}
else
{
$nestedValue = $nestedArray[$nestedElement];
}
}
return $nestedValue;
}
e.g. to use this method:
assume the following data
$searchResults = array(
0 => array(
'title' => 'one',
array(
'ratings' => array(
'count' => '1'
)
)
),
1 => array(
'title' => 'two',
array(
'ratings' => array(
'count' => '5'
)
)
),
2 => array(
'title' => 'three',
array(
'ratings' => array(
'count' => '2'
)
)
),
);
foreach($searchResults as $k => $v){
$count = $this->keyBySubElement($v, array('ratings','count'));
$sortData[$k] = $count;
}
this gives me something like this
array(4) {
[0]=>
int(1)
[1]=>
int(5)
[2]=>
int(2)
}
now that i have access to my sub-sub elements value, tied in with its top level parent key, i can use it to sort the top level array by key using my new array $sortData as the reference key which can be reordered by the sub elements value that i want to sort with. i was next going just re-sort the original array by the new key values or something.
i saw a couple potential good examples, but i wasn't able to make them work. those examples are as follows:
[PHP sort: user function][1]
e.g. 1)
http://php.net/manual/en/function.sort.php#99419
e.g. 2)
Sort php multidimensional array by sub-value
e.g. 3)
/**
* Sort a 2 dimensional array based on 1 or more indexes.
*
* msort() can be used to sort a rowset like array on one or more
* 'headers' (keys in the 2th array).
*
* #param array $array The array to sort.
* #param string|array $key The index(es) to sort the array on.
* #param int $sort_flags The optional parameter to modify the sorting
* behavior. This parameter does not work when
* supplying an array in the $key parameter.
*
* #return array The sorted array.
*/
public function msort($array, $key, $sort_flags = SORT_REGULAR) {
if (is_array($array) && count($array) > 0) {
if (!empty($key)) {
$mapping = array();
foreach ($array as $k => $v) {
$sort_key = '';
if (!is_array($key)) {
$sort_key = $v[$key];
} else {
// #TODO This should be fixed, now it will be sorted as string
foreach ($key as $key_key) {
$sort_key .= $v[$key_key];
}
$sort_flags = SORT_STRING;
}
$mapping[$k] = $sort_key;
}
asort($mapping, $sort_flags);
$sorted = array();
foreach ($mapping as $k => $v) {
$sorted[] = $array[$k];
}
return $sorted;
}
}
return $array;
}
e.g. 4)
/**
* #param $array
* #param $cols
* #return array
*/
public function array_msort($array, $cols)
{
$colarr = array();
foreach ($cols as $col
=> $order) {
$colarr[$col] = array();
foreach ($array as $k => $row) {
$colarr[$col]['_'.$k] = strtolower($row[$col]);
}
}
$eval = 'array_multisort(';
foreach ($cols as $col => $order) {
$eval .= '$colarr[\''.$col.'\'],'.$order.',';
}
$eval = substr($eval,0,-1).');';
eval($eval);
$ret = array();
foreach ($colarr as $col => $arr) {
foreach ($arr as $k => $v) {
$k = substr($k,1);
if (!isset($ret[$k])) $ret[$k] = $array[$k];
$ret[$k][$col] = $array[$k][$col];
}
}
return $ret;
}
Since the data structure that they return is rather ugly and not condusive to sorting, my first move would be to reformat it into something that can be easily sorted. For example:
# create a new key, 'ratings', and put the contents of [0][ratings][count] in it
foreach ($searchResults as &$s) {
print_r($s);
# you could use your keybysubelement function to retrieve the value here
# rather than hardcoding it
$s['ratings'] = $s[0]['ratings']['count'];
unset($s[0]);
}
print_r($searchResults);
resulting data structure:
Array
(
[0] => Array
(
[title] => one
[ratings] => 1
)
[1] => Array
(
[title] => two
[ratings] => 5
)
[2] => Array
(
[title] => three
[ratings] => 2
)
)
It's then easy to create a sort function that will operate on this array to sort it according to the value in 'ratings':
# create a closure that will sort by a given key and in a given direction
# by default the order is ascending
function by_key($key, $dir = 'asc') {
return function ($a, $b) use ($key, $dir) {
if ($a[$key] > $b[$key]) {
if ($dir === 'asc')
return 1;
return -1;
}
elseif ($a[$key] < $b[$key]) {
if ($dir === 'asc')
return -1;
return 1;
}
return 0;
};
}
# sort by ratings, descending, using uasort and the custom search function:
uasort( $searchResults, by_key('ratings','desc') );
# print the results
foreach ($searchResults as $i) {
echo $i['title'] . ', ' . $i['ratings'] . PHP_EOL;
}
array order after sort:
two, 5
three, 2
one, 1
Sort by title:
uasort( $searchResults, by_key('title') );
Output:
one, 1
three, 2
two, 5
I need to unset elements from arrays that are nested into another array, in a way that only the first N elements would be kept (N being predefined). Only elements that have a numerical index should be affected.
Input array:
Array
(
[0] => Array (
[a] => 'w'
[b] => Array (
[0]=> 'x'
[1]=> 'x'
[2]=> 'x'
)
)
[1] => Array (
[a] => 'y'
)
[2] => Array (
[0] => 'z'
[1] => 'z'
[2] => 'z'
)
)
Desired output (with N=2):
Array
(
[0] => Array (
[a] => 'w'
[b] => Array (
[0]=> 'x'
[1]=> 'x'
)
)
[1] => Array (
[a] => 'y'
)
)
Based on the above definition, only [0][b][2] and [2] got unset because they had a numerical index and because they both represnted the 3rd element of their respective array.
Haven't tested but something like this might work.
function myFunc(&$array){
foreach($array as $key=>&$value){
if(is_array($value)){
myFunc($value);
}
if(is_numeric($key) && $key > 1){
unset($array[$key]);
}
}
}
About array_walk. php.net says:
the programmer cannot add, unset or
reorder elements. If the callback does
not respect this requirement, the
behavior of this function is
undefined, and unpredictable.
Write yourself a function that does exactly what you want. Then document the function so if you need to use it in about two weeks, you might want to know what exactly that function is doing.
I say this because the data structure you want to handle seems to be very specific. So it's worth to encapsulate it in a function on it's own to hide away the complexity. Name the function properly.
Inside the function you can process the data in the various ways and with the various conditions you need to formulate. Parameters from the outside can be passed as function parameters. The return value is the result you aim to achieve.
This worked for me, perhaps not the cleanest code.
$array = array
(
array(
'a' => 'w',
'b' => array('x','x','x')
),
array(
'a' => 'y'
),
array(
'z','z','z'
)
);
function recurse_and_strip ($array, &$size=2)
{
foreach ($array as $key => &$element)
{
if (is_hash($element))
{
$element = recurse_and_strip($element,$size);
} else if (is_array($element))
{
$deletefrom = false;
// now the tricky part.. see how many must be deleted
for ($i=0; $i < count($element); $i++ )
{
if ($size == 0)
{
echo "Delete from " . $i;
$deletefrom = $i;
break 1;
}
$size--;
}
if ($deletefrom !== false)
{
if ($deletefrom == 0)
{
unset($array[$key]);
} else {
array_splice($element,$deletefrom);
}
}
}
}
return $array;
}
// source http://www.benjaminkeen.com/?p=23
function is_hash($var)
{
if (!is_array($var))
return false;
return array_keys($var) !== range(0,sizeof($var)-1);
}
var_dump(recurse_and_strip($array,2));
array_walk_recursive itself cannot achieve what you want. Even though you can pass array by reference, unsetting the variable in the callback will only unset it in that scope.
However, you can use walk_recursive_remove function:
/**
* http://uk1.php.net/array_walk_recursive implementation that is used to remove nodes from the array.
*
* #param array The input array.
* #param callable $callback Function must return boolean value indicating whether to remove the node.
* #return array
*/
function walk_recursive_remove (array $array, callable $callback) {
foreach ($array as $k => $v) {
if (is_array($v)) {
$array[$k] = walk_recursive_remove($v, $callback);
} else {
if ($callback($v, $k)) {
unset($array[$k]);
}
}
}
return $array;
}
You will need to implement your own logic using the $callback to unset the specific values.
Here's a more general solution to modifying the array to which the leaf belongs. You can unset the current key, or add siblings, etc.
/**
* Modified version of array_walk_recursive that passes in the array to the callback
* The callback can modify the array or value by specifying a reference for the parameter.
*
* #param array The input array.
* #param callable $callback($value, $key, $array)
*/
function array_walk_recursive_array(array &$array, callable $callback) {
foreach ($array as $k => &$v) {
if (is_array($v)) {
array_walk_recursive_array($v, $callback);
} else {
$callback($v, $k, $array);
}
}
}