Filter array by first letter - php

I'm building a platform. Somewhere in my code, there's an array that looks like this (PHP):
$entries = array('p01','p02','g01','g02','a001','a002')
I need to write a script that filters the array based on the first letter. For example, asking for those with the starting letter "p" would give me
$filtered_entries = array('p01','p02');
Similarly, if I asked for those with starting letter "g" or "a" it would give me those as well. Any idea how to accomplish this?

There is an array_filter() function in PHP which you can use to accomplish this:
$filtered = array_filter($array, create_function('$a', 'return $a[0] == "' . $letter . '";'));
I'll leave it to you to generalize the function to handle all the letters.
See: http://www.php.net/manual/en/function.array-filter.php

class FirstCharFilter {
public $char = 'p';
function filter(array $array){
return array_filter($array,array($this,'checkFirstChar'));
}
public function checkFirstChar($a){
return $a[0] == $this->char;
}
}
$filter = new FirstCharFilter();
$filter->char = 'p';
var_dump($filter->filter($array));
$filter->char = 'g';
var_dump($filter->filter($array));
Or if you only need to loop, extend FilterIterator:
class FirstCharIterator extends FilterIterator {
public $char = '';
function accept(){
$string = $this->current();
return is_string($string) && $string[0] == $this->char;
}
}
$iter = new FirstCharIterator(new ArrayIterator($array));
$iter->char = 'p';
foreach($iter as $item) echo $item."\n";

$entries = array('p01','p02','g01','g02','a001','a002');
print_r(
preg_grep('~^p~', $entries) // or preg_grep("~^$letter~",.....
);
http://php.net/manual/en/function.preg-grep.php

function filter_array($array, $letter){
$filtered_array=array();
foreach($array as $key=>$val){
if($val[0]==$letter){
$filtered_array[]=$val;
}
}
return $filtered_array;
}
use it like this to get all p's
$entries = array('p01','p02','g01','g02','a001','a002')
$filtered=filter_array($entries, 'p');

$entries = array('p01','p02','g01','g02','a001','a002');
$filterVar = null;
function filterFunction($v) {
global $filterVar;
if (substr($v,0,1) == $filterVar) {
return $v;
}
}
$filterVar = 'a';
$newEntries = array_filter($entries,'filterFunction');
var_dump($newEntries);

Here's one way of generating filter functions using a closure.
function filter_factory($letter) {
return function ($input) use ($letter) {
return is_string($input) && $input[0] === $letter;
};
}
$entries = array('p01','p02','g01','g02','a001','a002');
$p_entries = array_filter($entries, filter_factory('p'));

This type of solution is much more intuitive and dynamic.
In this example, there are several types of solutions:
Search in the first letters
Sensitive to capital letters
is_array() so if it tends to avoid several errors
<?php
/*
* Search within an asociative array
* Examples:
* $array = array('1_p01','1_P02','2_g01','2_g02','3_a001','3_a002');
* find_in_array($array,'2');
* return: array( 2 => '2_g01',3 => '2_g02')
*
* find_in_array($array,'2',false);
* return: array( 1 => '1_P02')
*
* find_in_array($array,'P0',false,false);
* return: array( 0 => '1_p01',1 => '1_P02')
*
*/
function find_in_array($array, $find='', $FirstChar=true, $CaseInsensitive=true){
if ( is_array($array) ){
return preg_grep("/".($FirstChar ? '^':'')."{$find}/".($CaseInsensitive ? '':'i'), $array);
}
}
$array = array('1_p01','1_P02','2_g01','2_g02','3_a001','3_a002');
$a = find_in_array($array,'2');
var_export($a);
/*
Return:
array (
2 => '2_g01',
3 => '2_g02'
)
*/
$a = find_in_array($array,'P0',false);
var_export($a);
/*
Return:
array (
1 => '1_P02'
)
*/
$a = find_in_array($array,'P0',false,false);
var_export($a);
/*
Return:
array (
0 => '1_p01',
1 => '1_P02'
)
*/

Related

$$ array variable in PHP [duplicate]

This question already has answers here:
Access array using dynamic path
(3 answers)
Closed 7 years ago.
I can use $$ variables to refer variable like this
$var = 'car';
$car = 'Lamborghini';
echo $$var;
Above code will echo Lamborghini.
However I am having a code like this:-
$var = "['acct_1']['etc']['anotherInfo']['sing']";
$var = 'arr'.$var;
echo $arr['acct_1']['etc']['anotherInfo']['sing'] ;
echo $$var;
First echo prints the correct value but $$var doesn't give the correct value.
Any help is much appreciated.
Thanks
You can always keep the keys in an array, and then iterate on them to resolve the value correctly:
$keys = ['acct_1', 'etc', 'anotherInfo', 'sing'];
$val = $arr;
foreach($keys as $key) {
$val = $val[$key];
}
Now, both $arr['acct_1']['etc']['anotherInfo']['sing'] and $val have the same value.
Try it in this demo.
Edit:
You already have the $keys array in $indexInfo. You should be able to use it like so:
function replaceValue($arr, $indexInfo, $char)
{
// $indexInfo is all you need!
$var = $arr;
foreach($indexInfo as $key) {
$var = $var[$key];
}
echo $arr['acct_1']['etc']['anotherInfo']['sing'] . "\n";
echo $var . "\n";
die($var);
}
That won't work unfortunately, however why not do something line this
/**
* Search into a multi dimensional array to find arbitrary data
* #param array $array The array to search
* #param string ... Any number of array keys
* #return mixed
*/
function deepArraySearch(array $array) {
$keys = func_get_args();
array_shift($keys); // First element is the array
// If no more keys to use
if(!$keys) {
return $array;
}
$nextKey = array_shift($keys);
$nextData = $array[$nextKey];
// Nothing left to search
if(!is_array($nextData )) {
return $nextData ;
}
array_unshift($keys, $nextData);
return call_user_func_array('deepArraySearch', $keys);
}
$arr = ['one' => ['two' => ['three' => 'data']]];
print_r(deepArraySearch($arr, 'one'));
print_r(deepArraySearch($arr, 'one', 'two'));
print_r(deepArraySearch($arr, 'one', 'two', 'three'));
echo PHP_EOL;
In your case I guess it would work like this
$arr = ['acct_1' => ['etc' => ['anotherInfo' => ['sing' => 'song']]]];
print_r(deepArraySearch($arr, 'acct_1', 'etc', 'anotherInfo', 'sing')); // song
Final note:
If you're using PHP 5.6, 7, or HHVM, this function is way nicer:
<?php
/**
* Search into a multi dimensional array to find arbitrary data
* #param array $array The array to search
* #param string ... Any number of array keys
* #return mixed
*/
function deepArraySearch(array $array, ...$keys) {
// If no more keys to use
if(!$keys) {
return $array;
}
$nextKey = array_shift($keys);
$nextData = $array[$nextKey];
// Nothing left to search
if(!is_array($nextData )) {
return $nextData ;
}
return deepArraySearch($nextData, ...$keys);
}
Demo: http://3v4l.org/vmocO

PHP Better way to Replace/Match $1

I am trying to replace $1, $2, $3 variables in a URL with another URL.
You can copy paste my example below and see my solution.
But I feel like there is a more elegant way with an array mapping type function or a better preg_replace type of thing. I just need a kick in the right direction, can you help?
<?php
/**
* Key = The DESIRED string
* Value = The ORIGINAL value
*
* Desired Result: project/EXAMPLE/something/OTHER
*/
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/something/OTHER'
);
foreach($data as $desiredString => $findMe)
{
/**
* Turn these URI's into arrays
*/
$desiredString = explode('/', $desiredString);
$findMe = explode('/', $findMe);
/**
* Store the array position of the match
*/
$positions = array();
foreach($desiredString as $key => $value) {
/**
* Look for $1, $2, $3, etc..
*/
if (preg_match('#(\$\d)#', $value)) {
$positions[$key] = $value;
}
}
/**
* Loop through the positions
*/
foreach($positions as $key => $value){
$desiredString[$key] = $findMe[$key];
}
/**
* The final result
*/
echo implode('/', $desiredString);
}
Sometimes you are out of luck and the functions you need to solve a problem directly just aren't there. This happens with every language regardless of how many libraries and builtins it has.
We're going to have to write some code. We also need to solve a particular problem. Ultimately, we want our solution to the problem to be just as clean as if we had the ideal functions given to us in the first place. Therefore, whatever code we write, we want most of it to be out of the way, which probably means we want most of the code in a separate function or class. But we don't just want to just throw around arbitrary code because all of our functions and classes should be reusable.
My approach then is to extract a useful general pattern out of the solution, write that as a function, and then rewrite the original solution using that function (which will simplify it). To find that general pattern I made the problem bigger so it might be applicable to more situations.
I ended up making the function array array_multi_walk(callback $callback [, array $array1 [, array $array2 ... ]]). This function walks over each array simultaneously and uses $callback to select which element to keep.
This is what the solution looks like using this function.
$chooser = function($a, $b) {
return strlen($a) >= 2 && $a[0] == '$' && ctype_digit($a[1])
? $b : $a;
};
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/something/OTHER'
);
$explodeSlashes = function($a) { return explode('/', $a); };
$find = array_map($explodeSlashes, array_keys($data));
$replace = array_map($explodeSlashes, array_values($data));
$solution = array_multi_walk(
function($f, $r) use ($chooser) {
return array_multi_walk($chooser, $f, $r);
},
$find, $replace);
And, as desired, array_multi_walk can be used for other problems. For example, this sums all elements.
$sum = function() {
return array_sum(func_get_args());
};
var_dump(array_multi_walk($sum, array(1,2,3), array(1,2,3), array(10)));
// prints the array (12, 4, 6)
You might want to make some tweaks to array_multi_walk. For example, it might be better if the callback takes the elements by array, rather than separate arguments. Maybe there should be option flags to stop when any array runs out of elements, instead of filling nulls.
Here is the implementation of array_multi_walk that I came up with.
function array_multi_walk($callback)
{
$arrays = array_slice(func_get_args(), 1);
$numArrays = count($arrays);
if (count($arrays) == 0) return array();
$result = array();
for ($i = 0; ; ++$i) {
$elementsAti = array();
$allNull = true;
for ($j = 0; $j < $numArrays; ++$j) {
$element = array_key_exists($i, $arrays[$j]) ? $arrays[$j][$i] : null;
$elementsAti[] = $element;
$allNull = $allNull && $element === null;
}
if ($allNull) break;
$result[] = call_user_func_array($callback, $elementsAti);
}
return $result;
}
So at the end of the day, we had to write some code, but not only is the solution to the original problem slick, we also gained a generic, reusable piece of code to help us out later.
Why there should not be $2,$4 but $1,$2 ?if you can change your array then it can be solved in 3 or 4 lines codes.
$data = array(
'project/$2/details/$4' => 'newby/EXAMPLE/something/OTHER'
);
foreach($data as $desiredString => $findMe)
{
$regexp = "#(".implode(')/(',explode('/',$findMe)).")#i";
echo preg_replace($regexp,$desiredString,$findMe);
}
I've shortened your code by removing comments for better readability. I'm using array_map and the mapping function decides what value to return:
<?php
function replaceDollarSigns($desired, $replace)
{
return preg_match('#(\$\d)#', $desired) ? $replace : $desired;
}
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/something/OTHER',
);
foreach($data as $desiredString => $findMe)
{
$desiredString = explode('/', $desiredString);
$findMe = explode('/', $findMe);
var_dump(implode('/', array_map('replaceDollarSigns', $desiredString, $findMe)));
}
?>
Working example: http://ideone.com/qVLmn
You can also omit the function by using create_function:
<?php
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/something/OTHER',
);
foreach($data as $desiredString => $findMe)
{
$desiredString = explode('/', $desiredString);
$findMe = explode('/', $findMe);
$result = array_map(
create_function(
'$desired, $replace',
'return preg_match(\'#(\$\d)#\', $desired) ? $replace : $desired;'
),
$desiredString,
$findMe);
var_dump(implode('/', $result));
}
?>
Working example: http://ideone.com/OC0Ak
Just saying, why don't use an array pattern/replacement in preg_replace? Something like this:
<?php
/**
* Key = The DESIRED string
* Value = The ORIGINAL value
*
* Desired Result: project/EXAMPLE/something/OTHER
*/
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/details/OTHER'
);
$string = 'project/$1/details/$2';
$pattern[0] = '/\$1/';
$pattern[1] = '/\$2/';
$replacement[0] = 'EXAMPLE';
$replacement[1] = 'OTHER';
$result = preg_replace($pattern, $replacement, $string);
echo $result;
I think that it's much easier than what you're looking for. You can see that it works here: http://codepad.org/rCslRmgs
Perhaps there's some reason to keep the array key => value to accomplish the replace?

Most efficient way to search for object in an array by a specific property's value

What would be the fastest, most efficient way to implement a search method that will return an object with a qualifying id?
Sample object array:
$array = [
(object) ['id' => 'one', 'color' => 'white'],
(object) ['id' => 'two', 'color' => 'red'],
(object) ['id' => 'three', 'color' => 'blue']
];
What do I write inside of:
function findObjectById($id){
}
The desired result would return the object at $array[0] if I called:
$obj = findObjectById('one')
Otherwise, it would return false if I passed 'four' as the parameter.
You can iterate that objects:
function findObjectById($id){
$array = array( /* your array of objects */ );
foreach ( $array as $element ) {
if ( $id == $element->id ) {
return $element;
}
}
return false;
}
Edit:
Faster way is to have an array with keys equals to objects' ids (if unique);
Then you can build your function as follow:
function findObjectById($id){
$array = array( /* your array of objects with ids as keys */ );
if ( isset( $array[$id] ) ) {
return $array[$id];
}
return false;
}
It's an old question but for the canonical reference as it was missing in the pure form:
$obj = array_column($array, null, 'id')['one'] ?? false;
The false is per the questions requirement to return false. It represents the non-matching value, e.g. you can make it null for example as an alternative suggestion.
This works transparently since PHP 7.0. In case you (still) have an older version, there are user-space implementations of it that can be used as a drop-in replacement.
However array_column also means to copy a whole array. This might not be wanted.
Instead it could be used to index the array and then map over with array_flip:
$index = array_column($array, 'id');
$map = array_flip($index);
$obj = $array[$map['one'] ?? null] ?? false;
On the index the search problem might still be the same, the map just offers the index in the original array so there is a reference system.
Keep in mind thought that this might not be necessary as PHP has copy-on-write. So there might be less duplication as intentionally thought. So this is to show some options.
Another option is to go through the whole array and unless the object is already found, check for a match. One way to do this is with array_reduce:
$obj = array_reduce($array, static function ($carry, $item) {
return $carry === false && $item->id === 'one' ? $item : $carry;
}, false);
This variant again is with the returning false requirement for no-match.
It is a bit more straight forward with null:
$obj = array_reduce($array, static function ($carry, $item) {
return $carry ?? ($item->id === 'one' ? $item : $carry);
}, null);
And a different no-match requirement can then be added with $obj = ...) ?? false; for example.
Fully exposing to foreach within a function of its own even has the benefit to directly exit on match:
$result = null;
foreach ($array as $object) {
if ($object->id === 'one') {
$result = $object;
break;
}
}
unset($object);
$obj = $result ?? false;
This is effectively the original answer by hsz, which shows how universally it can be applied.
You can use the function array_search of php like this
$key=array_search("one", array_column(json_decode(json_encode($array),TRUE), 'color'));
var_dump($array[$key]);
i: is the index of item in array
1: is the property value looking for
$arr: Array looking inside
'ID': the property key
$i = array_search(1, array_column($arr, 'ID'));
$element = ($i !== false ? $arr[$i] : null);
Well, you would would have to loop through them and check compare the ID's unless your array is sorted (by ID) in which case you can implement a searching algorithm like binary search or something of that sort to make it quicker.
My suggestion would be to first sort the arrays using a sorting algorithm (binary sort, insertion sort or quick sort) if the array is not sorted already. Then you can implement a search algorithm which should improve performance and I think that's as good as it gets.
http://www.algolist.net/Algorithms/Binary_search
This is my absolute favorite algorithm for very quickly finding what I need in a very large array, quickly. It is a Binary Search Algorithm implementation I created and use extensively in my PHP code. It hands-down beats straight-forward iterative search routines. You can vary it a multitude of ways to fit your need, but the basic algorithm remains the same.
To use it (this variation), the array must be sorted, by the index you want to find, in lowest-to-highest order.
function quick_find(&$array, $property, $value_to_find, &$first_index) {
$l = 0;
$r = count($array) - 1;
$m = 0;
while ($l <= $r) {
$m = floor(($l + $r) / 2);
if ($array[$m]->{$property} < $value_to_find) {
$l = $m + 1;
} else if ($array[$m]->{$property} > $value_to_find) {
$r = $m - 1;
} else {
$first_index = $m;
return $array[$m];
}
}
return FALSE;
}
And to test it out:
/* Define a class to put into our array of objects */
class test_object {
public $index;
public $whatever_you_want;
public function __construct( $index_to_assign ) {
$this->index = $index_to_assign;
$this->whatever_you_want = rand(1, 10000000);
}
}
/* Initialize an empty array we will fill with our objects */
$my_array = array();
/* Get a random starting index to simulate data (possibly loaded from a database) */
$my_index = rand(1256, 30000);
/* Say we are needing to locate the record with this index */
$index_to_locate = $my_index + rand(200, 30234);
/*
* Fill "$my_array()" with ONE MILLION objects of type "test_object"
*
* 1,000,000 objects may take a little bit to generate. If you don't
* feel patient, you may lower the number!
*
*/
for ($i = 0; $i < 1000000; $i++) {
$searchable_object = new test_object($my_index); // Create the object
array_push($my_array, $searchable_object); // Add it to the "$my_array" array
$my_index++; /* Increment our unique index */
}
echo "Searching array of ".count($my_array)." objects for index: " . $index_to_locate ."\n\n";
$index_found = -1; // Variable into which the array-index at which our object was found will be placed upon return of the function.
$object = quick_find($my_array, "index", $index_to_locate, $index_found);
if ($object == NULL) {
echo "Index $index_to_locate was not contained in the array.\n";
} else {
echo "Object found at index $index_found!\n";
print_r($object);
}
echo "\n\n";
Now, a few notes:
You MAY use this to find non-unique indexes; the array MUST still be sorted in ascending order. Then, when it finds an element matching your criteria, you must walk the array backwards to find the first element, or forward to find the last. It will add a few "hops" to your search, but it will still most likely be faster than iterating a large array.
For STRING indexes, you can change the arithmetic comparisons (i.e. " > " and " < " ) in quick_find() to PHP's function "strcasecmp()". Just make sure the STRING indexes are sorted the same way (for the example implementation): Alphabetically and Ascending.
And if you want to have a version that can search arrays of objects sorted in EITHER ascending OR decending order:
function quick_find_a(&$array, $property, $value_to_find, &$first_index) {
$l = 0;
$r = count($array) - 1;
$m = 0;
while ($l <= $r) {
$m = floor(($l + $r) / 2);
if ($array[$m]->{$property} < $value_to_find) {
$l = $m + 1;
} else if ($array[$m]->{$property} > $value_to_find) {
$r = $m - 1;
} else {
$first_index = $m;
return $array[$m];
}
}
return FALSE;
}
function quick_find_d(&$array, $property, $value_to_find, &$first_index) {
$l = 0;
$r = count($array) - 1;
$m = 0;
while ($l <= $r) {
$m = floor(($l + $r) / 2);
if ($value_to_find > $array[$m]->{$property}) {
$r = $m - 1;
} else if ($value_to_find < $array[$m]->{$property}) {
$l = $m + 1;
} else {
$first_index = $m;
return $array[$m];
}
}
return FALSE;
}
function quick_find(&$array, $property, $value_to_find, &$first_index) {
if ($array[0]->{$property} < $array[count($array)-1]->{$property}) {
return quick_find_a($array, $property, $value_to_find, $first_index);
} else {
return quick_find_d($array, $property, $value_to_find, $first_index);
}
}
The thing with performance of data structures is not only how to get but mostly how to store my data.
If you are free to design your array, use an associative array:
$array['one']->id = 'one';
$array['one']->color = 'white';
$array['two']->id = 'two';
$array['two']->color = 'red';
$array['three']->id = 'three';
$array['three']->color = 'blue';
Finding is then the most cheap: $one = $array['one];
UPDATE:
If you cannot modify your array constitution, you could create a separate array which maps ids to indexes. Finding an object this way does not cost any time:
$map['one'] = 0;
$map['two'] = 1;
$map['three'] = 2;
...
getObjectById() then first lookups the index of the id within the original array and secondly returns the right object:
$index = $map[$id];
return $array[$index];
Something I like to do in these situations is to create a referential array, thus avoiding having to re-copy the object but having the power to use the reference to it like the object itself.
$array['one']->id = 'one';
$array['one']->color = 'white';
$array['two']->id = 'two';
$array['two']->color = 'red';
$array['three']->id = 'three';
$array['three']->color = 'blue';
Then we can create a simple referential array:
$ref = array();
foreach ( $array as $row )
$ref[$row->id] = &$array[$row->id];
Now we can simply test if an instance exists in the array and even use it like the original object if we wanted:
if ( isset( $ref['one'] ) )
echo $ref['one']->color;
would output:
white
If the id in question did not exist, the isset() would return false, so there's no need to iterate the original object over and over looking for a value...we just use PHP's isset() function and avoid using a separate function altogether.
Please note when using references that you want use the "&" with the original array and not the iterator, so using &$row would not give you what you want.
This is definitely not efficient, O(N). But it looks sexy:
$result = array_reduce($array, function ($found, $obj) use ($id) {
return $obj['id'] == $id ? $obj : $found;
}, null);
addendum:
I see hakre already posted something akin to this.
Here is what I use. Reusable functions that loop through an array of objects. The second one allows you to retrieve a single object directly out of all matches (the first one to match criteria).
function get_objects_where($match, $objects) {
if ($match == '' || !is_array($match)) return array ();
$wanted_objects = array ();
foreach ($objects as $object) {
$wanted = false;
foreach ($match as $k => $v) {
if (is_object($object) && isset($object->$k) && $object->$k == $v) {
$wanted = true;
} else {
$wanted = false;
break;
};
};
if ($wanted) $wanted_objects[] = $object;
};
return $wanted_objects;
};
function get_object_where($match, $objects) {
if ($match == '' || !is_array($match)) return (object) array ();
$wanted_objects = get_objects_where($match, $objects);
return count($wanted_objects) > 0 ? $wanted_objects[0] : (object) array ();
};
The easiest way:
function objectToArray($obj) {
return json_decode(json_encode($obj), true);
}

How to dynamically set array keys in php

I have some logic that is being used to sort data but depending on the user input the data is grouped differently. Right now I have five different functions that contain the same logic but different groupings. Is there a way to combine these functions and dynamically set a value that will group properly. Within the function these assignments are happening
For example, sometimes I store the calculations simply by:
$calcs[$meter['UnitType']['name']] = ...
but other times need a more specific grouping:
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] =...
As you can see sometimes it is stored in a multidiminesional array and other times not. I have been trying to use eval() but without success (not sure that is the correct approach). Storing the data in a temporary variable does not really save much because there are many nested loops and if statements so the array would have to be repeated in multiple places.
EDIT
I hope the following example explains my problem better. It is obviously a dumbed down version:
if(){
$calcs[$meter['UnitType']['name']] = $data;
} else {
while () {
$calcs[$meter['UnitType']['name']] = $data;
}
}
Now the same logic can be used but for storing it in different keys:
if(){
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
} else {
while () {
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
}
}
Is there a way to abstract out the keys in the $calc[] array so that I can have one function instead of having multiple functions with different array keys?
You can use this if you want to get&set array values dynamically.
function getVal($data,$chain){
$level = $data;
for($i=0;$i<count($chain);$i++){
if(isset($level[$chain[$i]]))
$level = $level[$chain[$i]];
else
return null; // key does not exist, return null
}
return $level;
}
function setVal(&$data,$chain,$value){
$level = &$data;
for($i=0;$i<count($chain);$i++){
$level = &$level[$chain[$i]]; // set reference (&) in order to change the value of the object
}
$level = $value;
}
How it works:
Calling getVal($data,array('foo','bar','2017-08')) will return the equivalent of $data['foo']['bar']['2017-08'].
Calling setVal($data,array('foo','bar','2017-08'),'hello') will set value as if you called
$data['foo']['bar']['2017-08'] = 'hello'. non-existent keys will be created automatically by php magic.
This can be useful if you want to build the structure of the array dynamically.
Here's a function I wrote for setting deeply nested members on arrays or objects:
function dict_set($var, $path, $val) {
if(empty($var))
$var = is_array($var) ? array() : new stdClass();
$parts = explode('.', $path);
$ptr =& $var;
if(is_array($parts))
foreach($parts as $part) {
if('[]' == $part) {
if(is_array($ptr))
$ptr =& $ptr[];
} elseif(is_array($ptr)) {
if(!isset($ptr[$part]))
$ptr[$part] = array();
$ptr =& $ptr[$part];
} elseif(is_object($ptr)) {
if(!isset($ptr->$part))
$ptr->$part = array();
$ptr =& $ptr->$part;
}
}
$ptr = $val;
return $var;
}
Using your example data:
$array = [];
$array = dict_set($array, 'resource1.unit1.2017-10', 'value1');
$array = dict_set($array, 'resource1.unit2.2017-11', 'value2');
$array = dict_set($array, 'resource2.unit1.2017-10', 'value3');
print_r($array);
Results in output like:
Array
(
[resource1] => Array
(
[unit1] => Array
(
[2017-10] => value1
)
[unit2] => Array
(
[2017-11] => value2
)
)
[resource2] => Array
(
[unit1] => Array
(
[2017-10] => value3
)
)
)
The second argument to dict_set() is a $path string in dot-notation. You can build this using dynamic keys with period delimiters between the parts. The function works with arrays and objects.
It can also append incremental members to deeply nested array by using [] as an element of the $path. For instance: parent.child.child.[]
Would it not be easier to do the following
$calcs = array(
$meter['Resource']['name'] => array(
$meter['UnitType']['name'] => 'Some Value',
$meter['UnitType']['name2'] => 'Some Value Again'
),
);
or you can use Objects
$calcs = new stdClass();
$calcs->{$meter['UnitType']['name']} = 'Some Value';
but I would advice you build your structure in arrays and then do!
$calcs = (object)$calcs_array;
or you can loop your first array into a new array!
$new = array();
$d = date('Y-m',$start);
foreach($meter as $key => $value)
{
$new[$key]['name'][$d] = array();
}
Give it ago and see how the array structure comes out.
Try to use a switch case.
<?php
$userinput = $calcs[$meter['UnitType']['name']] = $data;;
switch ($userinput) {
case "useriput1":
while () {
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
}
break;
case "userinput2":
while () {
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
}
break;
...
default:
while () {
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
}
}
?>
I agree with the comment on the OP by #Jake N that perhaps using objects is a better approach. Nonetheless, if you want to use arrays, you can check for the existence of keys in a conditional, like so:
if(
array_key_exists('Resource', $meter)
) {
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
} else {
$calcs[$meter['UnitType']['name']] = $data;
}
On the other hand, if you want to use objects, you can create a MeterReading object type, and then add MeterReading instances as array elements to your $calcs array, like so:
// Object defintion
class MeterReading {
private $data;
private $resource;
private $startDate;
private $unitType;
public function __construct(Array $meter, $start, $data) {
$this->unitType = $meter['UnitType']['name'];
$this->resource = $meter['Resource']['name'];
$this->startDate = date('Y-m',$start);
}
public function data() {
return $this->data;
}
public function resource() {
return $this->resource;
}
public function startDate() {
return $this->startDate;
}
public function unitType() {
return $this->unitType;
}
}
// Example population
$calcs[] = new MeterReading($meter, $start, $data);
// Example usage
foreach($calcs as $calc) {
if($calc->resource()) {
echo 'Resource: ' . $calc->resource() . '<br>';
}
echo 'Unit Type: ' . $calc->unitType() . '<br>';
echo 'Start Date: ' . $calc->startDate() . '<br>';
echo 'Data: ' . $calc->data() . '<br>';
}
Obviously you can take this further, such as checking the existence of array keys in the object constructor, giving the object property resource a default value if not provided, and so on, but this is a start to an OO approach.
You can use this library to get or set value in multidimensional array using array of keys:
Arr::getNestedElement($calcs, [
$meter['Resource']['name'],
$meter['UnitType']['name'],
date('Y-m', $start)
]);
to get value or:
Arr::handleNestedElement($calcs, [
$meter['Resource']['name'],
$meter['UnitType']['name'],
date('Y-m', $start)
], $data);
to set $data as value.

Is there a php function like python's zip?

Python has a nice zip() function. Is there a PHP equivalent?
As long as all the arrays are the same length, you can use array_map with null as the first argument.
array_map(null, $a, $b, $c, ...);
If some of the arrays are shorter, they will be padded with nulls to the length of the longest, unlike python where the returned result is the length of the shortest array.
array_combine comes close.
Otherwise nothing like coding it yourself:
function array_zip($a1, $a2) {
for($i = 0; $i < min(length($a1), length($a2)); $i++) {
$out[$i] = [$a1[$i], $a2[$i]];
}
return $out;
}
Try this function to create an array of arrays similar to Python’s zip:
function zip() {
$args = func_get_args();
$zipped = array();
$n = count($args);
for ($i=0; $i<$n; ++$i) {
reset($args[$i]);
}
while ($n) {
$tmp = array();
for ($i=0; $i<$n; ++$i) {
if (key($args[$i]) === null) {
break 2;
}
$tmp[] = current($args[$i]);
next($args[$i]);
}
$zipped[] = $tmp;
}
return $zipped;
}
You can pass this function as many array as you want with as many items as you want.
This works exactly as Python's zip() function, and is compatible also with PHP < 5.3:
function zip() {
$params = func_get_args();
if (count($params) === 1){ // this case could be probably cleaner
// single iterable passed
$result = array();
foreach ($params[0] as $item){
$result[] = array($item);
};
return $result;
};
$result = call_user_func_array('array_map',array_merge(array(null),$params));
$length = min(array_map('count', $params));
return array_slice($result, 0, $length);
};
It merges the arrays in the manner Python's zip() does and does not return elements found after reaching the end of the shortest array.
The following:
zip(array(1,2,3,4,5),array('a','b'));
gives the following result:
array(array(1,'a'), array(2,'b'))
and the following:
zip(array(1,2,3,4,5),array('a','b'),array('x','y','z'));
gives the following result:
array(array(1,'a','x'), array(2,'b','y'))
Check this demonstration for a proof of the above.
EDIT: Added support for receiving single argument (array_map behaves differently in that case; thanks Josiah).
Solution
The solution matching zip() very closely, and using builtin PHP functions at the same time, is:
array_slice(
array_map(null, $a, $b, $c), // zips values
0, // begins selection before first element
min(array_map('count', array($a, $b, $c))) // ends after shortest ends
);
Why not simple array_map(null, $a, $b, $c) call?
As I already mentioned in my comment, I tend to favor nabnabit's solution (array_map(null, $a, $b, ...)), but in a slightly modified way (shown above).
In general this:
array_map(null, $a, $b, $c);
is counterpart for Python's:
itertools.izip_longest(a, b, c, fillvalue=None)
(wrap it in list() if you want list instead of iterator). Because of this, it does not exactly fit the requirement to mimic zip()'s behaviour (unless all the arrays have the same length).
You can find zip as well as other Python functions in Non-standard PHP library. Including operator module and defaultarray.
use function nspl\a\zip;
$pairs = zip([1, 2, 3], ['a', 'b', 'c']);
I wrote a zip() functions for my PHP implementation of enum.
The code has been modified to allow for a Python-style zip() as well as Ruby-style. The difference is explained in the comments:
/*
* This is a Python/Ruby style zip()
*
* zip(array $a1, array $a2, ... array $an, [bool $python=true])
*
* The last argument is an optional bool that determines the how the function
* handles when the array arguments are different in length
*
* By default, it does it the Python way, that is, the returned array will
* be truncated to the length of the shortest argument
*
* If set to FALSE, it does it the Ruby way, and NULL values are used to
* fill the undefined entries
*
*/
function zip() {
$args = func_get_args();
$ruby = array_pop($args);
if (is_array($ruby))
$args[] = $ruby;
$counts = array_map('count', $args);
$count = ($ruby) ? min($counts) : max($counts);
$zipped = array();
for ($i = 0; $i < $count; $i++) {
for ($j = 0; $j < count($args); $j++) {
$val = (isset($args[$j][$i])) ? $args[$j][$i] : null;
$zipped[$i][$j] = $val;
}
}
return $zipped;
}
Example:
$pythonzip = zip(array(1,2,3), array(4,5), array(6,7,8));
$rubyzip = zip(array(1,2,3), array(4,5), array(6,7,8), false);
echo '<pre>';
print_r($pythonzip);
print_r($rubyzip);
echo '<pre>';
// create
$a = array("a", "c", "e", "g", "h", "i");
$b = array("b", "d", "f");
$zip_array = array();
// get length of the longest array
$count = count(max($a, $b));
// zip arrays
for($n=0;$n<$count;$n++){
if (array_key_exists($n,$a)){
$zip_array[] = $a[$n];
}
if (array_key_exists($n,$b)){
$zip_array[] = $b[$n];
}
}
// test result
echo '<pre>'; print_r($zip_array); echo '<pre>';
function zip() {
$zip = [];
$arrays = func_get_args();
if ($arrays) {
$count = min(array_map('count', $arrays));
for ($i = 0; $i < $count; $i++) {
foreach ($arrays as $array) {
$zip[$i][] = $array[$i];
}
}
}
return $zip;
}
This works like in Python
function zip(...$arrays) {
return array_filter(
array_map(null, ...(count($arrays) > 1 ? $arrays : array_merge($arrays, [[]]))),
fn($z) => count($z) === count(array_filter($z)) || count($arrays) === 1
);
}
/**
* Takes an arbitrary number of arrays and "zips" them together into a single
* array, taking one value from each array and putting them into a sub-array,
* before moving onto the next.
*
* If arrays are uneven lengths, will stop at the length of the shortest array.
*/
function array_zip(...$arrays) {
$result = [];
$args = array_map('array_values',$arrays);
$min = min(array_map('count',$args));
for($i=0; $i<$min; ++$i) {
$result[$i] = [];
foreach($args as $j=>$arr) {
$result[$i][$j] = $arr[$i];
}
}
return $result;
}
Usage:
print_r(array_zip(['a','b','c'],[1,2,3],['x','y']));
Output:
Array
(
[0] => Array
(
[0] => a
[1] => 1
[2] => x
)
[1] => Array
(
[0] => b
[1] => 2
[2] => y
)
)
To overcome the issues with passing a single array to map_array, you can pass this function...unfortunately you can't pass "array" as it's not a real function but a builtin thingy.
function make_array() { return func_get_args(); }
Dedicated to those that feel like it should be related to array_combine:
function array_zip($a, $b)
{
$b = array_combine(
$a,
$b
);
$a = array_combine(
$a,
$a
);
return array_values(array_merge_recursive($a,$b));
}
you can see array_map method:
$arr1 = ['get', 'method'];
$arr2 = ['post'];
$ret = array_map(null, $arr1, $arr2);
output:
[['get', 'method'], ['post', null]]
php function.array-map

Categories