php array reference passing to function - php

I have a question that I cannot find an answer.
I'm constructing a very big array that contain hex values of a file (like $array[0]=B5 $array[1]=82 and so on until $array[1230009])
When I create a function to manipulate some offsets in that array and pass the $array as reference ( function parse(&$array) { ... }) it takes WAY longer than if I pass the array normaly ( function parse($array) { ... }) ..
How is that possible ?
PS: Is there any faster way not to use array at all ? Just to use a $string = "B5 82 00 1E ..etc", but I need to track the Offset as i advance in reading hex values as some of this values contains lengths"

There are some informations that might help in the following article : Do not use PHP references
Close to the end of that post, Johannes posts the following portion of code (quoting) :
function foo(&$data) {
for ($i = 0; $i < strlen($data); $i++) {
do_something($data{$i});
}
}
$string = "... looooong string with lots of data .....";
foo(string);
And a part of the comment that goes with it is (still quoting) :
But now in this case the developer tried to be smart and save time by
passing a reference. But well, strlen() expects a copy.
copy-on-write can't be done on references so $data will be
copied for calling strlen(), strlen() will do an absolutely simple
operation - in fact strlen() is one of the most trivial functions
in PHP - and the copy will be destroyed immediately.
You might well be in a situation like this one, considering the question you are asking...

Related

Get a single value from a nested associative array

For a setup script I write in PHP (for CLI, not web) I use a settings file as well as other other "content" files.
The settings file is built up like a regular ini-file
[header.subheader.moreheaders...]
keyA = value
keyB = value1|value2|...
"header.subheader.moreheaders..." and "keyX" will form a nested associative array with "value" as a string or "value1|value2|..." as a simple array (0-...).
Thanks to this accepted answer, I got so far that I can split the headers into a recursive array recursively. So far, so good.
However, as the content files shall contain references to these variables, I would like to be able to read out single values from that multi-dimensional array with string placeholders like $#R[header.subheader.moreheaders.key] or $#R[header.subheader.moreheaders.key.0] depending on them being a string or an array.
In the script, $#R[header.subheader.moreheaders.key.0] should convert into $SettingsVar[header][subheader][moreheaders][key][0] to return the appropriate value.
Neither the script nor the content files will know what is inside the settings file. The script just knows the general structure and placeholder $#R[...].
This answer appears to know what value will be in order to search for it.
Since I do not fully understand this answer, I am not sure if that would be the right way.
Is there a similar easy way to get the reverse from building that array?
After some contemplation, I found a decent enough solution, which works for me (and hopefully others):
function GetNestedValue($aNestedKeys, $aNestedArray)
{
$vValue = $aNestedArray;
for($i = 0; $i < count($aNestedKeys); $i++)
{
if(array_key_exists($aNestedKeys[$i], $vValue))
{
$vValue = $vValue[$aNestedKeys[$i]];
}
else
{
$vValue = null;
break;
}
}
return $vValue;
}
Depending on what $aNestedKeys contain, it will either return a sub-array from $aNestedArray, a single value from it or null if any of the specified keys were not found.

Can you get value from an array without getting the array first?

Bear with me, I'm learning.
I often see snippets like the one below:
<?p
$imageArray = get_field('image_field');
$imageAlt = $imageArray['alt'];
$imageURL = $imageArray['url'];
?>
It is pedagogical and clear and organized. But is it necessary to get the entire array before querying the array for values? Can I not define the variable in just a single line? Something like the below (which doesn't work, neither the other variants I have tried):
$imageAlt = get_field('image_field', ['alt']);
$imageURL = get_field('image_field', ['url']);
Yes, you can.
As of PHP 5.4 it is possible to array dereference the result of a function or method call directly. Before it was only possible using a temporary variable. - Source
$imageAlt = get_field('image_field')['alt'];
https://eval.in/548036
The question you are asking can be answered by asking 2 questions:
Is it doable ?
Is it a good idea to do it that way ?
Is it doable ?
Yes! You do not have to store the array in a variable and re-use it later.
For instance, you could do:
$imageAlt = get_field('image_field')['alt'];
Note: This will work in PHP 5.4+ and is called: Array dereferencing.
But that is not the only consideration...
Is it a good idea to do it that way ?
No. It's not a good idea in many cases. The get_field() function, depending on your context, is probably doing a lot of work and, each time you call it, the same work is don multiple times.
Let's say you use the count() function. It will count the number of items in an array. To do that, it must iterate through all items to get the value.
If you use the count() function each time you need to validate number of items in an array, you are doing the task of counting each and every time. If you have 10 items in your array, you probably won't notice. But if you have thousands of items in your array, this may cause a delay problem to compute your code (a.k.a. it will be slow).
That is why you would want to do something like: $count = count($myArray); and use a variable instead of calling the function.
The same applies to your question.
While PHP 5.4+ allows you to directly dereference a function return value like this:
get_field('image_field')['alt']
...in this particular case I would not suggest you do so, since you're using two values from the resulting array. A function call has a certain overhead just in itself, and additionally you don't know what the function does behind the scenes before it returns a result. If you call the function twice, you may incur a ton of unnecessary work, where a single function call would have done just as well.
This is not to mention keeping your code DRY; if you need to change the particulars of the function call, you now need to change it twice...
PHP allows you to play around quite a bit:
function foo(){
return array('foo' => 1, 'bar' => 2);
}
Option 1
echo foo()['foo']; // 1
# Better do this if you plan to reuse the array value.
echo ($tmp = foo())['foo']; // 1
echo $tmp['bar']; // 2
It is not recommended to call a function that returns an array, to specifically fetch 1 key and on the next line doing the same thing.
So it is better to store the result of the function in a variable so you can use it afterwards.
Option 2
list($foo, $bar) = array_values(foo());
#foo is the first element of the array, and sets in $foo.
#bar is the second element, and will be set in $bar.
#This behavior is in PHP 7, previously it was ordered from right to left.
echo $foo, $bar; // 12
Option 3
extract(foo()); // Creates variable from the array keys.
echo $foo, $bar;
extract(get_field('image_field'));
echo $alt, $url;
Find more information on the list constructor and extract function.

Returning by reference

In the PHP documentation it says:
Do not use return-by-reference to increase performance. The engine
will automatically optimize this on its own.
I wish to return a reference to an array (which is a property of my class). How does PHP optimize this, because the array is not an object?
If the array has 1 billion entries, won't I get two arrays with 1 billion entries stored in memory if I don't pass it by reference?
PHP uses copy on write. That means that if you pass the huge array as a function parameter and only read from it (such as a foreach), you won't be writing to it so it doesn't need to make a copy.
$count = count($huge_array); // read the reference at the bottom
for($i = 0; $i < $count $i++) {
$value = 2*$huge_array[$i]/15;
if($value > 3)
$sub_array []= $value;
}
The $subarray which should be smaller (it is a subset of the huge array), will contain only the needed (changed) data.
If you do not intend on changing the values of the original $huge_array it will never get copied so no extra memory is used.
If you intend on changing the original values of the array, you need to pass it by reference.
If you intend on making and returning an altered version of the original array then you do need the extra memory PHP is allocating for you.
If you intend on making and returning an altered smaller subset of the original array then you will create a new empty array into which you will copy some of data from the huge array and should be careful not to overwrite values in the $huge_array, so you'll avoid writing from $huge_array and emphasise on reading from it.
This link explains that PHP was optimised for pass by value use cases.
Copy on write only works if the variable isn't a reference being passed to a function expecting a value, if it is, passing it around triggers a copy.
That makes PHP native functions that expected an argument to be passed by value and received a referenced variable copy the value of the reference.
function foo(&$data) {
for ($i = 0; $i < strlen($data); $i++) {
do_something($data{$i});
}
}
$string = "... looooong string with lots of data .....";
foo(string);
Executing strlen in C would imply iterating over the entire string to count it. In PHP strings have an attached length value. So strlen returns the value read from there (fast).
But if you give it a referenced variable it will have to copy it before reading it's length so it will iterate over the value to copy it into it's argument list, read and return the length (and subsequently release the memory for the freshly copied string).

Is there a version of the PHP array that is pass-by-reference?

I'm asking this because I'm working with a recursive function that generates a large array tree and the pass-by-copy aspect of the arrays are completely screwing with my head. I've tried using ArrayObject, but that's really an object, isn't it? None of the array_keys type array functions work with it, and json_encode doesn't understand that it's an array.
I'd like a version of the PHP array that feels, smells and looks like the normal array, but is pass-by-reference. Is there anything like that in PHP?
Woah woah hold up people; I'm well aware of the & symbol but that's what I'm trying to avoid. As my question specifies (^) I'm looking for a version of the PHP array that is pass-by-reference by default
I'd like a version of the PHP array that feels, smells and looks like
the normal array, but is pass-by-reference. Is there anything like
that in PHP?
No, There is nothing like that in PHP.
Json encode should be able to pass objects. But if you for some reason NEED an array, you can't use objects and then cast it as array before encoding to json?
<?php
$object = (object)array("number"=>1);
function addToTen($object){
if($object->number<10){
$object->number++;
addToTen($object);
}
}
addToTen($object);
echo json_encode((array)$object);
//echoes {"number":10} with or without casting it as an array
?>
You could also wrap your array in an object of course, like this:
$object = new stdClass;
$object->a = array();
function fillUpArray($object){
if(count($object->a)<10){
$object->a[] = "someValue";
fillUpArray($object);
}
}
fillUpArray($object);
echo json_encode($object->a);
//echoes ["someValue","someValue","someValue","someValue","someValue","someValue","someValue","someValue","someValue","someValue"]
I must admit though I don't entirely get what you're trying to accomplish here :S
Yes, see the PHP manual page: http://php.net/manual/en/language.references.pass.php
Stop using &references altogether, in php they get cumbersome pretty quickly, (being, unlike C pointers, almost transparent, the only way to check you're actually using a reference is by assigning junk to it and check the effect this has on a tree) and you don't seem willing to handle that level of subtlety.
(Nor to wrap it with an ArrayObject, apparently)
Are you aware objects ARE references?
Object-wrap every aspect of your tree and your life will instantly get less miserable.
I am not aware of any such built-in functionality in PHP that you ask. Also, you are quite reluctant to use references. Hmmm...you could send a request to the PHP dev team to include such stuff in PHP v6, along with unicode that is supposed to come, that would us all happy :).
However, can you use a class and assign your initial array to one of the class variables and then process it and get it back after the recursion. Not sure if that would work, but anyway here it is:
<?php
class noReference {
public $myData;
public function __construct( $data ) {
$this->myData = $data; // this is your initial array.
}
// this function works on the myData array and changes it.
public function myRecursiveFunction() {
// your code here
$this->myRecursiveFunction(); // called as per your logic
// your code here
}
public function getData() {
return $this->myData;
}
public function __destruct() {
unset( $this->myData );
}
}
$data = array(/*WHATEVER_PLEASES_YOU*/);
$noref = new noReference( $data );
// this will be your recuresive function
$noref->myRecursiveFunction();
//your data here
$result = $noref->getData();
?>
Let me know if this works. Cheers!
you can force php to pass things by reference by adding an &-sign to the parameter. read the documentation for more information.

How To Get The Unique Name Count With PHP?

Let's say I have text file Data.txt with:
26||jim||1990
31||Tanya||1942
19||Bruce||1612
8||Jim||1994
12||Brian||1988
56||Susan||2201
and it keeps going.
It has many different names in column 2.
Please tell me, how do I get the count of unique names, and how many times each name appears in the file using PHP?
I have tried:
$counts = array_count_values($item[1]);
echo $counts;
after exploding ||, but it does not work.
The result should be like:
jim-2,
tanya-1,
and so on.
Thanks for any help...
Read in each line, explode using the delimiter (in this case ||), and add it to an array if it does not already exist. If it does, increment the count.
I won't write the code for you, but here a few pointers:
fread reads in a line
explode will split the line based on a delimiter
use in_array to check if the name has been found before, and to determine whether you need to add the name to the array or just increment the count.
Edit:
Following Jon's advice, you can make it even easier for you.
Read in line-by-line, explode by delimiter and dump all the names into an array (don't worry about checking if it already exists). After you're done, use array_count_values to get every unique name and its frequency.
Here's my take on this:
Use file to read the data file, producing an array where each element corresponds to a line in the input.
Use array_filter with trim as the filter function to remove blank lines from this array. This takes advantage that trim returns a string having removed whitespace from both ends of its argument, leaving the empty string if the argument was all whitespace to begin with. The empty string converts to boolean false -- thus making array_filter disregard lines that are all whitespace.
Use array_map with a callback that involves calling explode to split each array element (line of text) into three parts and returning the second of these. This will produce an array where each element is just a name.
Use array_map again with strtoupper as the callback to convert all names to uppercase so that "jim" and "JIM" will count as the same in the next step.
Finally, use array_count_values to get the count of occurrences for each name.
Code, taking things slowly:
function extract_name($line) {
// The -1 parameter (available as of PHP 5.1.0) makes explode return all elements
// but the last one. We want to do this so that the element we are interested in
// (the second) is actually the last in the returned array, enabling us to pull it
// out with end(). This might seem strange here, but see below.
$parts = explode('||', $line, -1);
return end($parts);
}
$lines = file('data.txt'); // #1
$lines = array_filter($lines, 'trim'); // #2
$names = array_map('extract_name', $lines); // #3
$names = array_map('strtoupper', $names); // #4
$counts = array_count_values($names); // #5
print_r($counts); // to see the results
There is a reason I chose to do this in steps where each steps involves a function call on the result of the previous step -- that it's actually possible to do it in just one line:
$counts = array_count_values(
array_map(function($line){return strtoupper(end(explode('||', $line, -1)));},
array_filter(file('data.txt'), 'trim')));
print_r($counts);
See it in action.
I should mention that this might not be the "best" way to solve the problem in the sense that if your input file is huge (in the ballpark of a few million lines) this approach will consume a lot of memory because it's reading all the input in memory at once. However, it's certainly convenient and unless you know that the input is going to be that large there's no point in making life harder.
Note: Senior-level PHP developers might have noticed that I 'm violating strict standards here by feeding the result of explode to a function that accepts its argument by reference. That's valid criticism, but in my defense I am trying to keep the code as short as possible. In production it would be indeed better to use $a = explode(...); return $a[1]; although there will be no difference as regards the result.
While I do feel that this website's purpose is to answer questions and not do homework assignments, I don't acknowledge the assumption that you are doing your homework, since that fact has not been provided. I personally learned how to program by example. We all learn our own ways, so here is what I would do if I were to attempt to answer your question as accurately as possible, based on the information you have provided.
<?php
$unique_name_count = 0;
$names = array();
$filename = 'Data.txt';
$pointer = fopen($filename,'r');
$contents = fread($pointer,filesize($filename));
fclose($pointer);
$lines = explode("\n",$contents);
foreach($lines as $line)
{
$split_str = explode('|',$line);
if(isset($split_str[2]))
{
$name = strtolower($split_str[2]);
if(!in_array($name,$names))
{
$names[] = $name;
$unique_name_count++;
}
}
}
echo $unique_name_count.' unique name'.(count($unique_name_count) == 1 ? '' : 's').' found in '.$filename."\n";
?>

Categories