Imagine that you have the following data structures. The config one is a hash of config values. The search is an array of hash keys to pull a config value from the config hash.
$config['users']['students']['default']['school'] = 'Garfield High';
$config['users']['students']['default']['domain'] = 'ghs.com';
$config['users']['teacher']['default']['fruit'] = 'apple';
$config['school']['superintendent'] = 'Boris York';
$search[] = 'users';
$search[] = 'students';
$search[] = 'default';
$search[] = 'school';
What's the most efficient way to use the $search array to get the value "Garfield High."
This sounds like a school assignment, but really it isn't. I've wandered down a rabbit hole, and while I'll probably abandon this thing that I'm doing, I'm curious how best to solve this problem. It seems like it should be easy, but for some reason, I'm stumped.
The method to extract values from $config should work with any $search array size. It needs to work with $search = array('school', 'superintendent') as well.
public function get($search, $config) {
// Somehow pull value from $config
}
It's a simple loop, using each element of $search as the key in the next level of the array being searched.
public function get($search, $config) {
$result = $config;
foreach ($search as $key) {
if (is_array($result) && isset($result[$key])) {
$result = $result[$key];
} else {
return false; // not found
}
}
return $result;
}
DEMO
Alternative solution with RecursiveIteratorIterator class:
$config = ['users' => ['students' => ['default' => ['school' => 'Garfield High'], 'highschool' => ['domain' => 'highschool.ghs.com']]]];
$search1 = ['users', 'students', 'default', 'school'];
$search2 = ['users', 'students', 'highschool', 'domain'];
function getConfigItem($search = [], $config = []) {
$iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($config), \RecursiveIteratorIterator::SELF_FIRST);
$result = "";
foreach ($iterator as $k => $v) {
if ($search[$iterator->getDepth()] == $k && is_string($v)) {
$result = $v;
}
}
return $result;
}
var_dump(getConfigItem($search1, $config)); // string 'Garfield High'
var_dump(getConfigItem($search2, $config)); // string 'highschool.ghs.com'
http://php.net/manual/en/class.recursiveiteratoriterator.php
Related
I am writing a method which takes an array of $topicNames and an array of $app and concatenates each $app to $topicNames like the following
public function getNotificationTopicByAppNames(array $topicNames, array $apps)
{
$topics = [];
foreach ($topicNames as $topicName) {
foreach ($apps as $app) {
$topic = $app . '_' . $topicName;
$topics[] = $topic;
}
}
return $topics;
}
}
The input and result are like the following...
$topicNames = [
'one_noti',
'two_noti',
'three_noti'
];
$apps = [
'one_app',
'two_app'
];
// The return result of the method will be like the following
[
'one_app_one_noti',
'two_app_one_noti',
'one_app_two_noti',
'two_app_two_noti',
'one_app_three_noti',
'two_app_three_noti'
]
My question is instead of doing nested loops, is there any other way I can do? Why do I want to avoid nested loops? Because currently, I have $topic. Later, I might want to add languages, locations etc...
I know I can use map, reduce, array_walks, each those are basically going through one by one. Instead of that which another alternative way I can use? I am okay changing different data types instead of the array as well.
If you dont care about the order you can use this
function getNotificationTopicByAppNames(array $topicNames, array $apps)
{
$topics = [];
foreach($apps as $app){
$topics = array_merge($topics, preg_filter('/^/', $app.'_', $topicNames));
}
return $topics;
}
print_r(getNotificationTopicByAppNames($topicNames,$apps));
Output
Array
(
[0] => one_app_one_noti
[1] => one_app_two_noti
[2] => one_app_three_noti
[3] => two_app_one_noti
[4] => two_app_two_noti
[5] => two_app_three_noti
)
Sandbox
You can also switch loops and use the $ instead to postfix instead of prefix. Which turns out to be in the same order you had. I thought of prefixing as a way to remove the loop. Then i thought why not flip it.
function getNotificationTopicByAppNames(array $topicNames, array $apps)
{
$topics = [];
foreach($topicNames as $topic){
$topics = array_merge($topics, preg_filter('/$/', '_'.$topic, $apps));
}
return $topics;
}
print_r(getNotificationTopicByAppNames($topicNames,$apps));
Output
Array
(
[0] => one_app_one_noti
[1] => two_app_one_noti
[2] => one_app_two_noti
[3] => two_app_two_noti
[4] => one_app_three_noti
[5] => two_app_three_noti
)
Sandbox
The trick here is using preg_filter.
http://php.net/manual/en/function.preg-filter.php
preg_filter — Perform a regular expression search and replace
So we search with ^ start or $ end which doesn't capture anything to replace and then we just add on what we want. I've used this before when I wanted to prefix a whole array with something, etc.
I couldn't test it in a class, so I made it a regular function, so adjust as needed.
Cheers!
You can use :
<?php
public function mergeStacks(...$stacks)
{
$allStacks = call_user_func_array('array_merge', $stacks);
return $this->concatString($allStacks);
}
private function concatString(&$stack, $index = 0, &$result = [])
{
if(count($stack) == 0){
return '';
}
if($index == count($stack)){
return $result;
}
array_walk($stack, function($value, $key) use($index, &$result, $stack){
if($key > $index){
array_push($result, $stack[$index] . '_' . $value);
}
});
$index = $index + 1;
return $this->concatString($stack, $index, $result);
}
And then when you want to get the array, no matter if you have languages or topics etc, you can just do :
$this->mergeStacks($languages, $topics, $locations, .....);
Where $languages, $topics, $locations are simple arrays.
Instead of accepting only topics name parameter try something like this:
function getNotificationTopicByAppNames(array $apps, array ...$names)
{
$topics = [];
foreach ($names as $nameArray) {
foreach ($nameArray as $topicName) {
foreach ($apps as $app) {
$topic = $app . '_' . $topicName;
$topics[] = $topic;
}
}
}
return $topics;
}
$topicNames = [
'one_noti',
'two_noti',
'three_noti'
];
$languagesNames = [
'test_en',
'test_other',
'test_other2'
];
$apps = [
'one_app',
'two_app'
];
print_r(getNotificationTopicByAppNames($apps,$topicNames,$languagesNames));
you can pass any number of arrays to array.
I got an array like this:
$array[0][name] = "Axel";
$array[0][car] = "Pratzner";
$array[0][color] = "black";
$array[1][name] = "John";
$array[1][car] = "BMW";
$array[1][color] = "black";
$array[2][name] = "Peggy";
$array[2][car] = "VW";
$array[2][color] = "white";
I would like to do something like "get all names WHERE car = bmw AND color = white"
Could anyone give advice on how the PHP spell would look like?
function getWhiteBMWs($array) {
$result = array();
foreach ($array as $entry) {
if ($entry['car'] == 'bmw' && $entry['color'] == 'white')
$result[] = $entry;
}
return $result;
}
Edited: This is a more general solution:
// Filter an array using the given filter array
function multiFilter($array, $filters) {
$result = $array;
// Removes entries that don't pass the filter
$fn = function($entry, $index, $filter) {
$key = $filter['key'];
$value = $filter['value'];
$result = &$filter['array'];
if ($entry[$key] != $value)
unset($result[$index]);
};
foreach ($filters as $key => $value) {
// Pack the filter data to be passed into array_walk
$filter = array('key' => $key, 'value' => $value, 'array' => &$result);
// For every entry, run the function $fn and pass in the filter data
array_walk($result, $fn, $filter);
}
return array_values($result);
}
// Build a filter array - an entry passes this filter if every
// key in this array corresponds to the same value in the entry.
$filter = array('car' => 'BMW', 'color' => 'white');
// multiFilter searches $array, returning a result array that contains
// only the entries that pass the filter. In this case, only entries
// where $entry['car'] = 'BMW' AND $entry['color'] = 'white' will be
// returned.
$whiteBMWs = multiFilter($array, $filter);
Doing this in code is more or less emulating what a RDBMS is perfect for. Something like this would work:
function getNamesByCarAndColor($array,$color,$car) {
$matches = array();
foreach ($array as $entry) {
if($entry["color"]== $color && $entry["car"]==$car)
matches[] = $entry["name"];
}
return $matches;
}
This code would work well for smaller arrays, but as they got larger and larger it would be obvious that this isn't a great solution and an indexed solution would be much cleaner.
I am trying to make first array value to uppercase.
Code:
$data = $this->positions_model->array_from_post(array('position', 'label'));
$this->positions_model->save($data, $id);
So before save($data, $id) to database I want to convert position value to uppercase. I have tried by this
$data['position'] = strtoupper($data['position']);
but than it is not storing the value in db with uppercase but as it is what user inputs.
Current output of $data:
Array ( [position] => it [label] => Information Technology )
And I want it in uppercase as IT
Added Model Method
public function get_positions_array($id = NULL, $single = FALSE)
{
$this->db->get($this->_table_name);
$positions = parent::get($id, $single);
$array = array();
foreach($positions as $pos){
$array[] = get_object_vars($pos);
}
return $array;
}
Main MY_Model method
public function array_from_post($fields)
{
$data = array();
foreach ($fields as $field) {
$data[$field] = $this->input->post($field);
}
return $data;
}
This should work:
$data = $this->positions_model->array_from_post(array('position', 'label'));
$data['position'] = strtoupper($data['position']);
$this->positions_model->save($data, $id);
If Its not, then $data array have only read attribute.
The array_from_post() method returns an array with the format below:
$data = array(
'position' => 'it',
'label' => 'Information Technology'
);
So, you could make first value of the array to uppercase, by using array_map or array_walk functions as follows:
$data = array_map(function($a) {
static $i = 0;
if ($i === 0) { $i++; return strtoupper($a); }
else return $a;
}, $array);
Note: This only works on PHP 5.3+, for previous versions, use the function name instead.
Here is the array_walk example, which modifies the $data:
array_walk($data, function(&$value, $key) {
static $i = 0;
if ($i == 0) { $i++; $value = strtoupper($value); }
});
Again, if you're using PHP 5.2.x or lower, you could pass the function name instead.
I've been working on some code today where I got stuck at a little multidimensional array problem. First of all it's maybe handy to read some code I wrote to get a better view on the problem itself:
public function treeLeaves(array $elements, $parent = 0) {
$branch = array();
foreach($elements as $element) {
$leaf = array('pageid' => $element['pageid'],
'page_parent' => $element['page_parent'],
'label' => ucfirst($element['page_print'][0]['print_title']),
'uri' => $element['page_alias']);
if($element['page_parent'] == $parent) {
$children = $this->treeLeaves($elements, $leaf['pageid']);
if($children) {
foreach($children as $key => $child) {
$leaf['pages'][] = $children[$key];
}
}
$branch[] = $leaf;
}
}
return $branch; }
For some reason I can't figure out how to glue the parent URI alias onto all the separate child URIs. The desired result I'm looking for should look something like this: http://pastebin.com/Eh9ExBjG
I hope some master can help me out here. I've been trying so many different stuff, but can't figure this thing out, even though I feel that it is relatively easy to solve.
Somewhat simplified, but I think you would get the idea:
function treeLeaves($elements, $parent = 0, $baseUri = '/index') {
$branch = array();
foreach($elements as $element) {
if ($element['page_parent'] == $parent) {
$leaf = array(
'uri' => $baseUri . '/' . $element['page_alias'];
);
$leaf['pages'] = treeLeaves($elements, $element['pageid'], $leaf['uri']);
$branch[] = $leaf;
}
}
return $branch;
}
ok..I'm trying to re-map the keynames of a key-value array in php using a fieldmap array ie.
i want the $outRow array to hold $inRow['name1'] = 10 to $outRow['name_1'] = 10 for a large set of pre-mapped values..
$fieldmap=array("name1"=>"name_1","name2"=>"name_2");
private function mapRow($inRow) {
$outRow = array();
foreach($inRow as $key => $value) {
$outRow[$this->fieldmap[$key]][] = $value;
}
return $outRow;
} // end mapRow
public function getListings($inSql) {
// get data from new table
$result = mysql_query($inSql);
if (!result) {
throw new exception("retsTranslate SQL Error: $inSql");
}
while ($row = mysql_fetch_assoc($result)) {
$outResult[] = $this->mapRow($row);
}
return $outResult;
} // end getListings
this is not working..I'm getting the array but its using $outResult[0][keyname]...I hope this is clear enough :)
$fieldmap=array("name1"=>"name_1","name2"=>"name_2");
private function mapRow($inRow) {
$outRow = array();
foreach($inRow as $key => $value) {
$outRow[$this->fieldmap[$key]][] = $value;
}
return $outRow;
} // end mapRow
while ($row = mysql_fetch_assoc($result)) {
//$outResult[] = $this->mapRow($row);
$outResult[= $this->mapRow($row);
}
I commented your line of code and added new one..it definitely got what you mentioned in question.
If you can structure your arrays to where the keys align with the values (see example below) you can use PHP array_combine(). Just know that you will need to make absolutely sure the array is ordered correctly.
<?php
$fieldmap = array( 'name_1', 'name_2', 'name_3' );
private function mapRow($inRow)
{
$outRow = array_combine( $this->fieldmap, $inRow );
return $outRow;
}
For example, if your array was:
array( 'name1' => 10, 'name2' => 20, 'name3' => 30 );
The new result would be:
array( 'name_1' => 10, 'name_2' => 20, 'name_3' => 30 );
Let me know if this helps.
Try this:
function mapRow($inRow) {
$outRow = array();
foreach($inRow as $key => $value) {
$outRow[preg_replace('/\d/', '_$0', $key,1)] = $value;
}
return $outRow;
}