Caching large arrays - php

I have following function:
function f($idx, $arr) {
static $a;
if ($a == NULL) {
foreach ($arr as $v) {
$key = $v['key'];
if (!isset($a[$key])) {
$a[$key] = array();
}
$a[$key][] = $v;
}
}
return $a[$idx];
}
And the initial conditions are:
function f() is called many-many times in 1 request
$arr is always very large
$arr may differ in different function calls (low cardinality)
$idx differs almost in every function call (high cardinality)
And now I need to know, if $arr is already cached and if not, then create this "cached version", but keep also all previous arrays.
According to 2. I'm not able to use md5(serialize($arr)) to use it as identifier, so I need another way to determine that. Do you have any idea how to achieve such hight-performance caching functionality (let's say I'm not able to do any changes outside this function)?

If it's not imperative that $arr not be modified, then I'd just add your optimized key access directly to it:
// note the argument has been changed to &$arr - we're passing by reference
function f($idx, &$arr) {
if (empty($arr['___cached'])) {
$arr['___cached'] = array();
foreach ($arr as $k => $v) {
if ($k === '___cached') continue;
if (!isset($arr['___cached'][$v['key']])) {
$arr['___cached'][$v['key']] = array();
}
$arr['___cached'][$v['key']][] = $v;
}
}
return $arr['___cached'][$idx];
}

Related

Find object keys that begin with one of the strings in an array

Note: obviously, this can "easily" be done with multiple nested foreach() loops, however I am looking for a way that is less expensive (and, if possible, also less spagetti).
Given a data array of objects like this:
$data (array)
[] (obj)
->id
->name
->a_one
->a_two
->b_three
->b_four
->c_five
[] (obj)
...
and this array of matching prefixes:
$prefixes (array)
[] (str) "a_"
[] (str) "b_"
[] (str) "c_"
how can I find (and unset) all properties of all objects in $data that begin with one of the defined $prefixes, without using multiple foreach() loops?
Solution using foreach():
foreach($data as $datakey => $obj) {
foreach($prefixes as $prefix) {
foreach($obj as $property => $value) {
if(strpos($property, $prefix) === 0) {
unset($data[$datakey]->{$property});
}
}
}
}
Is there a better way?
Construct a regexp pattern that matches all of the prefixes, and check each key against the regexp:
$pattern = "^(" . join("|", $prefixes) . ")";
foreach($data as $datakey => $obj) {
foreach($data as $datakey => $obj) {
foreach($obj as $property => $value) {
if (preg_match($pattern, $property))
...
}
}
This still requires two loops, but there is no redundancy; each object in your data is examined only once. If you were to find a way to loop over all the objects' contents without two PHP loops, it will still take just as many steps. The code in your question looped over each object multiple times (once for each prefix), which is indeed redundant.
Not sure whether you'll prefer this or not but;
$prefixes = ['a_', 'b_'];
$filtered = array_map(function($object) use ($prefixes) {
return unsetPrefixedProperties($object, $prefixes);
}, $data);
function unsetPrefixedProperties($object, $prefixes) {
$regex = '/('. implode('|', $prefixes) .').+/';
foreach ($object as $key => $value) {
if (preg_match($regex, $key)) {
unset($object->{$key});
}
}
return $object;
}
Similar to alexis basically using regex to match, removing the need to loop through your prefixes. Utilising array map to form a new array of filtered objects just because in my personal opinion it seems cleaner and finally extracted the other loop into its own function just to improve readability.
Still not perfect, but it's a start.
Edit: Sandbox Example
$matchedKeys = [];
foreach ($data as $key => $value) {
foreach ($prefixes as $val) {
$length = strlen($val);
if (substr($key, 0, $length) === $val) {
$matchedKeys[] = $key;
}
}
}
The $matchedKeys array will hold the matching keys from object $data.

Php, check if object with property = value exists in array of objects

I think I could do this with a foreach loop like this:
foreach ($haystack as $item)
if (isset($item->$needle_field) && $item->$needle_field == $needle)
return true;
}
but i was wandering if it could be done without a loop?
something like:
if(in_array($item->$needle_field == $needle,$haystack)
return true;
Yes, in modern PHP you can determine if a specific object property contains a specific value without a classic loop by combining the forces of array_column() (which has evolved to also handle arrays of objects) and in_array().
Code: (Demo)
$objects = [
(object)['cats' => 2],
(object)['dogs' => 2],
(object)['fish' => 10],
(object)['birds' => 1],
];
$needleField = 'cats';
$needleValue = 2;
var_export(
in_array($needleValue, array_column($objects, $needleField))
);
// output: true
The advantage of this technique is the obviously concise syntax. This is a perfectly acceptable approach for relatively small volumes of data.
A possible disadvantage to this technique is that array_column() will be generating a new array of all of values that relate to the $needleField.
In my above demo, array_column() will only generate a single-element array because there is only one cats property in all of the objects. If we were processing a relatively large volume of data, then it would be inefficient to bother collecting all of the qualifying cats values and then run in_array() when only one match is necessary to return true.
For "large" volumes of data where performance is a primary criterion for script design, a classic foreach loop would be a better choice and as soon as an object satisfies the rules, then the loop should be halted via return or break.
Code: (Demo)
function hasPropertyValue(array $objects, $property, $value): bool {
foreach ($objects as $object) {
if (property_exists($object, $property) && $object->{$property} === $value) {
return true;
}
}
return false;
}
var_export(
hasPropertyValue($objects, $needleField, $needleValue)
);
It's possible, but it's not any better:
<?php
function make_subject($count, $success) {
$ret = array();
for ($i = 0; $i < $count; $i++) {
$object = new stdClass();
$object->foo = $success ? $i : null;
$ret[] = $object;
}
return $ret;
}
// Change true to false for failed test.
$subject = make_subject(10, true);
if (sizeof(array_filter($subject, function($value) {
return $value->foo === 3;
}))) {
echo 'Value 3 was found!';
} else {
echo 'Value 3 was not found.';
}
Outputs Value 3 was found!.
I advise you remain with the for loop: it's legible, unlike any tricks to save a line that you might find.
This will not work if the array you are searching is out of your control. But, if you are the one building the array of objects to be searched, you can structure it using the needle as array keys to be used with array_key_exists when you are searching.
For example, instead of making your $haystack array like this:
[
{
'needle_field' => $needle
},
...
]
Make it like this:
[
$needle => {
'needle_field' => $needle
},
...
]
And search like this:
if (array_key_exists($needle, $haystack)) {
return true;
}
Finally, if you need to, you can convert back to an integer indexed array by using array_values
$haystack = array_values($haystack);
This may not work in all situations but it worked great for me.
Maybe with array_key_exists:
if (array_key_exists($needle_field, $haystack) {
if ($haystack[$needle_field] == $needle) {
echo "$needle exists";
}
}

How can I do the same thing that "any?" in Ruby does in PHP

In Ruby, any? takes a block and check if given collection has a value that fulfills the block like this:
[1, 2, 3].any? {|v| v > 2} # true
Is there any way to do this in PHP? My current idea is to use array_reduce():
array_reduce(array(1, 2, 3), function($acc, $val) {
return $acc || ($val > 2);
}, false);
But it will itereate all elements in the array, so I guess it's not as good as "any?" in Ruby. How can I return the boolean value as soon as expected value is found in PHP?
function any(array $array, callable $test) {
foreach ($array as $value) {
if ($test($value)) {
return true;
}
}
return false;
}
You could use a simple array_filter instead, but it will iterate the whole array every time:
if (array_filter($array, function ($v) { return $v > 2; })) ...
You're too keen about functional programming. PHP is not a functional language; at least that is not its main job.
$array = array(1,2,3);
foreach ($array as $value) {
if ($value > 2) return $value;
}

how to Extract variables without using extract()?

I would like a function to export variables to the scope it's called within without using extract(), e.g.
function get_vars(){
$return['a'] = 1;
$return['b'] = 2
return $return;
}
and then rather than using :
exctract(get_vars());
I would just use
get_vars();
is there anyway available??
Saw that possible solution at the php manuel of the extract function.
(http://www.php.net/manual/en/function.extract.php#62727)
function free_args (&$V) {
foreach ($V as $k => &$v) {
$$k =& $v;
}
unset ($k); unset ($v); unset ($V);
// be careful that if you need to extract $k, $v or $V variables you should find other names for them in the lines above (ie. $__k, $__v and $__V)
}
You can try to use global keyword
function get_vars() {
global $a, $b;
$a = 1;
$b =2;
}
But as for me, it is a very weird approach. I prefer to use OOP.

php: array_replace_recursive alternative

I need a solution for array_replace_recursive, because my php-version isn't high enough. I want to use this code:
$_GET = array_replace_recursive($_GET, array("__amp__"=>"&"));
easy, isn't it?
On the PHP docs page for array_replace_recursive, someone posted the following source code to use in place of it:
<?php
if (!function_exists('array_replace_recursive'))
{
function array_replace_recursive($array, $array1)
{
function recurse($array, $array1)
{
foreach ($array1 as $key => $value)
{
// create new key in $array, if it is empty or not an array
if (!isset($array[$key]) || (isset($array[$key]) && !is_array($array[$key])))
{
$array[$key] = array();
}
// overwrite the value in the base array
if (is_array($value))
{
$value = recurse($array[$key], $value);
}
$array[$key] = $value;
}
return $array;
}
// handle the arguments, merge one by one
$args = func_get_args();
$array = $args[0];
if (!is_array($array))
{
return $array;
}
for ($i = 1; $i < count($args); $i++)
{
if (is_array($args[$i]))
{
$array = recurse($array, $args[$i]);
}
}
return $array;
}
}
?>
The code above by #Justin is ok, save for 2 things:
Function is not readily available at start of php execution be cause it is wrapped in if(). PHP docu says
When a function is defined in a conditional manner such as the two examples shown. Its definition must be processed prior to being called.
Most importantly; calling the function twice results in fatal error.
PHP docu says
All functions and classes in PHP have the global scope - they can be called outside a function even if they were defined inside and vice versa.
So I just moved the recurse function outside array_replace_recursive function and it worked well. I also removed the if() condition and renamed it to array_replace_recursive_b4php53 for fear of future upgradings

Categories