Avoid Nested Loop in PHP - php

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.

Related

Convert a nested Array to Scalar and then reconvert the scalar to nested array

I have an array that looks like this.
$array = [
0 => 'abc',
1 => [
0 => 'def',
1 => 'ghi'
],
'assoc1' => [
'nassoc' => 'jkl',
'nassoc2' => 'mno',
'nassoc3' => '',
'nassoc4' => false
]
];
The $array can have numeric keys or be an assoc array or a mixed one. The level of nesting is not known. Also the values of the array can also be bool or null or an empty string ''
I need to able to convert this into a scalar array with key value pairs. And then later reconvert it back to the exact same array.
So the scalar array could look like
$arrayScalar = [
'0' => 'abc',
'1[0]' => 'def',
'1[1]' => 'ghi',
'assoc1[nassoc]' => 'jkl',
'assoc1[nassoc2]' => 'mno',
'assoc1[nassoc3]' => '',
'assoc1[nassoc4]' => false
];
And then later be able to get back to the initial $array.
I wrote a parser and it does not currently handle bool values correctly.
I have a feeling this is at best a super hacky method to do what I am after. Also I have been able to test it only so much.
function flattenNestedArraysRecursively($nestedArray, $parent = '', &$flattened = [])
{
$keys = array_keys($nestedArray);
if (empty($keys)) {
$flattened[$parent] = 'emptyarray';
} else {
foreach ($keys as $value) {
if (is_array($nestedArray[$value])) {
$reqParent = (!empty($parent)) ? $parent . '|!|' . $value : $value;
$this->flattenNestedArraysRecursively($nestedArray[$value], $reqParent, $flattened);
} else {
$reqKey = (!empty($parent)) ? $parent . '|!|' . $value : $value;
$flattened[$reqKey] = $nestedArray[$value];
}
}
}
return $flattened;
}
function reCreateFlattenedArray($flatArray): array
{
$arr = [];
foreach ($flatArray as $key => $value) {
$keys = explode('|!|', $key);
$arr = $this->reCreateArrayRecursiveWorker($keys, $value, $arr);
}
return $arr;
}
function reCreateArrayRecursiveWorker($keys, $value, $existingArr)
{
//Outside to Inside
$keyCur = array_shift($keys);
//Check if keyCur Exists in the existingArray
if (key_exists($keyCur, $existingArr)) {
// Check if we have reached the deepest level
if (empty($keys)) {
//Return the Key => value mapping
$existingArr[$keyCur] = $value;
return $existingArr;
} else {
// If not then continue to go deeper while appending deeper levels values to current key
$existingArr[$keyCur] = $this->reCreateArrayRecursiveWorker($keys, $value, $existingArr[$keyCur]);
return $existingArr;
}
} else {
// If Key does not exists in current Array
// Check deepest
if (empty($keys)) {
//Return the Key => value mapping
$existingArr[$keyCur] = $value;
return $existingArr;
} else {
// Add the key
$existingArr[$keyCur] = $this->reCreateArrayRecursiveWorker($keys, $value, []);
return $existingArr;
}
}
}
Is there a better more elegant way of doing this, maybe http_build_query or something else I am not aware of.
Sandbox link -> http://sandbox.onlinephpfunctions.com/code/50b3890e5bdc515bc145eda0a1b34c29eefadcca
Flattening:
Your approach towards recursion is correct. I think we can make it more simpler.
We loop over the array. if the value is an array in itself, we recursively make a call to this new child subarray.
This way, we visit each key and each value. Now, we are only left to manage the keys to assign them when adding to our final resultant array, say $arrayScalar.
For this, we make a new function parameter which takes the parent key into account when assigning. That's it.
Snippet:
$arrayScalar = [];
function flatten($array,&$arrayScalar,$parent_key){
foreach($array as $key => $value){
$curr_key = empty($parent_key) ? $key : $parent_key . '[' . $key . ']';
if(is_array($value)){
flatten($value,$arrayScalar,$curr_key);
}else{
$arrayScalar[$curr_key] = $value;
}
}
}
flatten($array,$arrayScalar,'');
var_export($arrayScalar);
Demo: http://sandbox.onlinephpfunctions.com/code/1e3092e9e163330f43d495cc9d4acb672289a987
Unflattening:
This one is a little tricky.
You might have already noticed that the keys in the flattened array are of the form key1[key2][key3][key4] etc.
So, we collect all these individually in a new array, say $split_key. It might look like this.
array (
'key1',
'key2',
'key3',
'key4',
)
To achieve the above, we do a basic string parsing and added in-between keys to the array whenever we reach the end of the key string or [ or ].
Next, to add them to our final resultant array, we loop over the collected keys and check if they are set in our final array. If not so, set them. We now pass child array reference to our temporary variable $temp. This is to edit the same copy of the array. In the end, we return the result.
Snippet:
<?php
function unflatten($arrayScalar){
$result = [];
foreach($arrayScalar as $key => $value){
if(is_int($key)) $key = strval($key);
$split_key = [];
$key_len = strlen($key);
$curr = '';
// collect them as individual keys
for($i = 0; $i < $key_len; ++$i){
if($key[ $i ] == '[' || $key[ $i ] == ']'){
if(strlen($curr) === 0) continue;
$split_key[] = $curr;
$curr = '';
}else{
$curr .= $key[ $i ];
}
if($i === $key_len - 1 && strlen($curr) > 0){
$split_key[] = $curr;
}
}
// collecting them ends
//add them to our resultant array.
$temp = &$result;
foreach($split_key as $sk){
if(!isset($temp[ $sk ])){
$temp[ $sk ] = [];
}
$temp = &$temp[$sk];
}
$temp = $value;
}
return $result;
}
var_export(unflatten($arrayScalar));
Demo: http://sandbox.onlinephpfunctions.com/code/66136a699c3c5285eed3d3350ed4faa5bbce4b76

Get keys from multidimensional array according to other keys

I have a multidimensional array like this which I converted from JSON:
Array (
[1] => Array (
[name] => Test
[id] => [1]
)
[2] => Array (
[name] => Hello
[id] => [2]
)
)
How can I return the value of id if name is equal to the one the user provided? (e.g if the user typed "Test", I want it to return "1")
Edit: Here's the code that works if anyone wants it:
$array = json_decode(file_get_contents("json.json"), true);
foreach($array as $item) {
if($item["name"] == "Test")
echo $item["id"];
}
The classical solution is to simply iterate over the array with foreach and check the name of each row. When it matches your search term you have found the id you are looking for, so break to stop searching and do something with that value.
If you are using PHP 5.5, a convenient solution that works well with less-than-huge data sets would be to use array_column:
$indexed = array_column($data, 'id', 'name');
echo $indexed['Test']; // 1
You can use this function
function searchObject($value,$index,$array) {
foreach ($array as $key => $val) {
if ($val[$index] === $value)
return $val;
}
return null;
}
$MyObject= searchObject("Hello","name",$MyArray);
$id = $MyObject["id"];
You can do it manually like, in some function:
function find($items, $something){
foreach($items as $item)
{
if ($item["name"] === $something)
return $item["id"];
}
return false;
}
here is the solution
$count = count($array);
$name = $_POST['name']; //the name which user provided
for($i=1;$i<=$count;$i++)
{
if($array[$i]['name']==$name)
{
echo $i;
break;
}
}
enjoy
Try this:
$name = "Test";
foreach($your_array as $arr){
if($arr['name'] == $name){
echo $arr['id'];
}
}

Get node from php array by one of the fields

I have an array with structure generated like this:
$groups = array();
while ($group = mysql_fetch_array($groups_result)) {
$groups[] = array( 'id' => $group['id'], 'name' => $group['name']);
}
How can I later in the code get the name of the group by its id? For example, I would like a function like:
function get_name_by_id($id, $array);
But I'm looking for some solution which is already implemented in PHP. I know it would be easier to make arrays with array[5] = array('name' => "foo") etc where 5 in this case is id, but in my code there is a lot of arrays already created like i mentioned above and I cannot easily switch it.
$groups = array();
while ($group = mysql_fetch_assoc($groups_result)) {
$groups[$group['id']] = array( 'name' => $group['name']);
}
$name = $groups['beer']['name'];
also please not using fetch_assoc is more efficient than fetch array
function get_name_by_id($id, $data) {
foreach($data as $d) {
if ($d['id'] == $id) {
return $d['name'];
}
}
// return something else, id was not found
}

Wildcard array loop in PHP

I have an array which contains a great deal of data, which has been generated from a JSON file. Most of the data is used to populateinput elements, but some of the keys contain hidden, default values to be used for calculations later.
The array looks something like this:
[name] => 'My App'
[default_E17] => 0.009
[default_H17] => 0.0236
[default_K17] => 50
[default_P17] => 0.0118
[default_E19] => 0.03
I want to loop over all default_* keys, and output them using HTML. Essentially, I want a foreach loop, but only for the keys whose format matches default_*. Does anyone know if this is possible?
Please note that the values in the array before [default_*] keys is variable length, so I cannot easily use an array_splice().
You use strpos($key, "default_") === 0 to show that it start with default_ and its not in the middle or end
$array = array();
$array['name'] = 'My App';
$array['default_E17'] = "0.009";
$array['default_H17'] = "0.0236";
$array['default_K17'] = "50";
$array['default_P17'] = "0.0118";
$array['default_E19'] = "0.03";
$array['E19_default_test'] = "1.03";
echo "<pre>";
* You can use foreach *
$list = array();
foreach ( $array as $key => $value ) {
if (strpos($key, "default_") === 0) {
$list[$key] = $value;
}
}
var_dump($list);
You can also use array_flip with array_filter
$array = array_flip(array_filter(array_flip($array),function($var) { return (strpos($var, "default_") === 0);}));
var_dump($array);
You can also use FilterIterator
class RemoveDefaultIterator extends FilterIterator {
public function accept() {
return (strpos($this->key(), "default_") === 0);
}
}
$list = new RemoveDefaultIterator(new ArrayIterator($array));
var_dump(iterator_to_array($list));
They would all Output
array
'default_E17' => string '0.009' (length=5)
'default_H17' => string '0.0236' (length=6)
'default_K17' => string '50' (length=2)
'default_P17' => string '0.0118' (length=6)
'default_E19' => string '0.03' (length=4)
foreach( $arr as $k => $v ) { //iterate the array
if( strpos($k, 'default_') !== FALSE ) //search if the key contains 'default_'
$default_values[] = $v; // if so, store the values for the 'default_' keys
}
Just iterate over your array
Foreach( $inputArray AS $key=>$val ) {
// check if key is the one we need
if( ... ) {
// it is - deal with it
}
}
depending of the keys you use if() can be simple substr() or regexp matching.
You can use a FilterIterator for this.
Its essetially the same as looping over the whole array, because that's the only way really, but it strips you from doing this in your productive loop, thus generating less noise.
Here's how:
class Default_FilterIterator extends FilterIterator
{
public function accept()
{
if (strpos($this->key(), 'default') === 0) {
return true;
}
return false;
}
}
$yourArray = array('default_stuff' => 'foo', 'otherstuff' => 'bar');
$filterdArray = new Default_FilterIterator(new ArrayIterator($yourArray));
foreach ($filteredArray as $key => $value) {
//deal only with default values here
}
foreach ($your_array as $key => $value) {
// check if the $key starst with 'default_'
if (substr($key, 0, 8) == "default_") {
//do your thing...
echo "<input type='text' value='" . $value . "'>";
}
}

PHP: check if object/array is a reference

Sorry to ask, its late and I can't figure a way to do it... anyone can help?
$users = array(
array(
"name" => "John",
"age" => "20"
),
array(
"name" => "Betty",
"age" => "22"
)
);
$room = array(
"furniture" => array("table","bed","chair"),
"objects" => array("tv","radio","book","lamp"),
"users" => &$users
);
var_dump $room shows:
...
'users' => &
...
Which means "users" is a reference.
I would like to do something like this:
foreach($room as $key => $val) {
if(is_reference($val)) unset($room[$key]);
}
The main goal is to copy the array WITHOUT any references.
Is that possible?
Thank you.
You can test for references in a multi-dimensional array by making a copy of the array, and then altering and testing each entry in turn:
$roomCopy = $room;
foreach ($room as $key => $val) {
$roomCopy[$key]['_test'] = true;
if (isset($room[$key]['_test'])) {
// It's a reference
unset($room[$key]);
}
}
unset($roomCopy);
With your example data, $room['furniture'] and $roomCopy['furniture'] will be separate arrays (as $roomCopy is a copy of $room), so adding a new key to one won't affect the other. But, $room['users'] and $roomCopy['users'] will be references to the same $users array (as it's the reference that's copied, not the array), so when we add a key to $roomCopy['users'] it is visible in $room['users'].
The best I can manage is a test of two variables to determine if one is a reference to the other:
$x = "something";
$y = &$x;
$z = "something else";
function testReference(&$xVal,&$yVal) {
$temp = $xVal;
$xVal = "I am a reference";
if ($yVal == "I am a reference") { echo "is reference<br />"; } else { echo "is not reference<br />"; }
$xVal = $temp;
}
testReference($x,$y);
testReference($y,$x);
testReference($x,$z);
testReference($z,$x);
testReference($y,$z);
testReference($z,$y);
but I doubt if it's much help
Really dirty method (not well tested either):
$x = "something";
$y = &$x;
$z = "something else";
function isReference(&$xVal) {
ob_start();
debug_zval_dump(&$xVal);
$dump = ob_get_clean();
preg_match('/refcount\((\d*)\)/',$dump,$matches);
if ($matches[1] > 4) { return true; } else { return false; }
}
var_dump(isReference($x));
var_dump(isReference($y));
var_dump(isReference($z));
To use this last method in your code, you'd need to do something like:
foreach($room as $key => $val) {
if(isReference($room[$key])) unset($room[$key]);
}
because $val is never a reference as it's a copy of the original array element; and using &$val makes it always a reference
something recursive maybe.
function removeReferences($inbound)
{
foreach($inbound as $key => $context)
{
if(is_array($context))
{
$inbound[$key] = removeReferences($context)
}elseif(is_object($context) && is_reference($context))
{
unset($inbound[$key]); //Remove the entity from the array.
}
}
return $inbound;
}
function var_reference_count(&$xVal) {
$ao = is_array($xVal)||is_object($xVal);
if($ao) { $temp= $xVal; $xVal=array(); }
ob_start();
debug_zval_dump(&$xVal);
$dump = ob_get_clean();
if($ao) $xVal=$temp;
preg_match('/refcount\((\d*)\)/',$dump,$matches);
return $matches[1] - 3;
}
//-------------------------------------------------------------------------------------------
This works with HUDGE objects and arrays.
if you want to get rid of recursive elements:
<?php
$arr=(object)(NULL); $arr->a=3; $arr->b=&$arr;
//$arr=array('a'=>3, 'b'=>&$arr);
print_r($arr);
$arr_clean=eval('return '.strtr(var_export($arr, true), array('stdClass::__set_state'=>'(object)')).';');
print_r($arr_clean);
?>
output:
stdClass Object ( [a] => 3 [b] => stdClass Object *RECURSION* )
stdClass Object ( [a] => 3 [b] => )

Categories