How can I write a solution for which the current PHP interpreter (5.4) is smart enough to simply do about 3-5 copies instead of a full on item-by-item array sort?
Note, I know a few methods to insert an element into an indexed array. However this does not satisfy my understanding. For instance in C++, you can do something using std::copy or make a struct or union as a multi-element array cursor.
So I wonder if I play by PHP's rules somehow, what syntax can one use to have, under the hood, something closer to
Copy the [range of elements from some index to the end of A] into temp C
Copy B into A[Index],
Copy C into A[Index+count(B)]
Than this...
$MasterItemList = $Page[$CurrentPage]->GetItems(); /* Returns an array with 512 Items. */
$UpdateList = GetUpdatePage(); /* Returns multi-dimensional array such that:
$result[][0]=an index and
$result[][1]=a list of items */
foreach($UpdateList as $Update)
{ foreach($Update as $cursor => $ItemList)
{
$cursor=$cursor+0; //to int..
$numitems=count($ItemList);
if($ItemList[0]->NewAddition)
{
$BeforeUpdate=array_splice($MasterItemList,0, $cursor, true);
$AfterUpdate=array_splice($MasterItemList, $cursor+$numitems, 0);
$MasterItemList=array_merge($BeforeUpdate,$ItemList,$AfterUpdate);
$Page[$CurrentPage]->OffsetCorrection+=$numitems;
}
else
{
$i=0;
foreach($ItemList as $LineItem)
{
$MasterItemList[$cursor+$i] = $LineItem;
$i++;
}
}
}
}
Forgive me if I've a few errors jotting this down, let me know and I'll correct them.
Namely though, I dont think proper referencing and scope are available to the interpreter for it to be able to do the logic directly using this method. It's already a woefully expensive looking thing.. What can be done to do this 'the right way' for PHP?
Examples:
// An Update List
Array(
[0] => Array(
[0] => 31
[1] => Array(
[1] => stdClass Object
(
[NewAddition] => false
[Name] => "********"
[Date] => 1364920943
[Active] => 1
.
.
.
)
[2] => stdClass Object
(
[NewAddition] => false
[Name] => "********"
[Date] => 1364920943
[Active] => 1
.
.
.
)
[3] => stdClass Object
(
[NewAddition] => false
[Name] => "********"
[Date] => 1364920943
[Active] => 1
.
.
.
)
)
)
)
And MasterItemList is simply an array of these same objects (class Item).
A few things to note:
This data is only accessed in a purely sequential manner anywhere it would matter for this script.
Only the first item in a newly inserted set needs to be checked for update in this part of the script. All items following in the set will be always be new.
Items trailing over 512 are auto-adjusted into the next page load. I can adjust size of pages to trade between array sorting performance & data fetch performance (async buffered).
First of all, PHP arrays are not "arrays" in the data structure sense; they are actually hash tables and doubly linked lists rolled into one. When you are indexing into an array e.g. with $list[$i] $i is hashed to find the corresponding element; it's not simple arithmetic as it is in e.g. C++.
Additionally, since arrays are also linked lists the implementation of array_splice is much more efficient than it might appear, at least if the portion being removed is small enough (hashing the new items is normally fast, and interposing items at a certain place of a linked list is constant time).
Of course this means that PHP arrays consume much more memory than a "pure" array would and they are also slower if all you are intending is index-based access. In those situations the SPL offers SplFixedArray which is an implementation of an array in the data structure sense of the word.
In your particular case, array_splice should be your first option; you can insert an array chunk with just one call:
array_splice($MasterItemList, $cursor, 0, $ItemList);
Related
I have a php stdObject that is an object, and sometimes an array of Objects.
EDIT: It's been returned by SoapClient::__soapCall.
If it has one entry:
stdClass Object
(
[Event] => stdClass Object
(
[Nr] => 7050
[Date] => 2016-11-30T00:00:00
)
)
If it's got multiple entries:
stdClass Object
(
[Event] => Array
(
[0] => stdClass Object
(
[Nr] => 7015
[Date] => 2016-04-28T00:00:00
)
[1] => stdClass Object
(
[Nr] => 7016
[Date] => 2016-04-29T00:00:00
)
[2] => stdClass Object
(
[Nr] => 7017
[Date] => 2016-06-08T00:00:00
)
)
)
I don't see why, and I'd like to correct it.
Here's how I got there:
$items = $this->getAll(); // returns an Object with n entries
foreach($items as $item){
$fullItem = $this->item->getElementByID($item->Nr); // returns the full data for this entry
$item->Days = $fullItem->Days; // that's the one that can contain objects or just the data if it has one entry
}
My Questions:
Is that normal, that the same request returns different data types?
Should I "normalize" this and make an array of objects if there's only one entry?
Or is there an other recommended way to cope with it?
I don't think it's ok in this case. You should keep the interface the same. So if you are expecting an unknown number of elements it should always be a collection, even if in some cases only one element is returned. However if you expect only one element then the collection should be omitted.
$this->getAll(); //should always return a collection
$this->getOneById($id); //should return single item without collection, return null or throw exception if no element is found
On the question should you "normalize" (refactor) it I wold say it depends on the effort estimation and the benefit you'll gain form the refactoring. If this occurs in few places only and it's not likely to be used in any other place in the future I think you shouldn't bother. In case you decide to refactor be very careful and ensure the whole application is working properly. If you have tests - great.
I didn't declare it in the question, sorry: The data is the result of a soap client call:
$this->soapClient = new \SoapClient($wsdlUrl, array('trace' => DEBUG_SOAP));
// ...
$result = $this->soapClient->__soapCall($method, array($method => $params));
I think what confused me is the standard behaviour of SoapClient::__soapCall
SOAP functions may return one, or multiple values. If only one value
is returned by the SOAP function, the return value of __soapCall will
be a simple value (e.g. an integer, a string, etc). If multiple values
are returned, __soapCall will return an associative array of named
output parameters.
http://php.net/manual/en/soapclient.soapcall.php
Also, a very similar question: PHP SoapClient type mapping behaves differently
And here are two solutions from there:
"Normalize" (what's the correct word?) the data AFTER we got it:
if (is_object($variable)){
$variable = array($variable);
}
https://stackoverflow.com/a/6408780/160968
And, that would be the equivalent to refactoring the request BEFORE we get it – there's a setting for the soapClient!
$soapConfig = array(
'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
'trace' => true
);
$client = new SoapClient($wsdlUrl, $soapConfig);
https://stackoverflow.com/a/8008715/160968
Awesome!
I am using an API which has a lot of data inside lots of arrays which as you may know can be quite confusing.I am relatively new to API's and this one in particular has no documentation.
My code below is grabbing the recent_games() function which is pulling the whole API then I am using foreach loops to get inside the data.
$games = $player->recent_games();
foreach($games['gameStatistics']['array'] as $key => $gameStatistic) {
$game_date[strtotime($gameStatistic['createDate'])] = $gameStatistic;
}
// order data
krsort($game_date);
foreach ($game_date as $game => $data) {
$statistics[$data] = $data['statistics'];
}
I am getting errors such as illegal offset for:
$statistics[$data] = $data['statistics'];
Is there a way to continue down the nesting of arrays ($game_date) to get to the data that I need?
Let me know if you need more info.
Thanks
EDIT more info:
The first foreach loop at the top loops a unix timestamp key per game. Looks like this:
[1370947566] => Array
(
[skinName] => Skin_name
[ranked] => 1
[statistics] => Array
(
[array] => Array
(
[0] => Array
(
[statType] => stat_data
[value] => 1234
)
[1] => Array
(
[statType] => stat_data
[value] => 1234
)
As you can see its quite nested but I am trying to get to the individual statistics array. I hope that helps?
$statistics[$data] = $data['statistics'];
There is absolutely no way this line is correct.
The right hand side uses $data as if it were an array, indexing into it. The left hand side uses $data as a key into an array. Since the only valid types for keys are strings and integers, $data cannot satisfy the requirements of both expressions at the same time -- it cannot be an array and a string or integer.
It's obvious from the error message that $data is in fact an array, so using it as $staticstics[$data] is wrong. What do you want $statistics to be?
Ive found myself drilling down through xml feeds quite allot, they are almost identical feeds apart from a couple of array names.
Ideally id want to make a sort of function that i can call, but i have no idea how to do it with this sort of data
//DRILLING DOWN TO THE PRICE ATTRIBUTE FOR EACH FEED & MAKING IT A WORKING VAR
$wh_odds = $wh_xml->response->will->class->type->market->participant;
$wh_odds_attrib = $wh_odds->attributes();
$wh_odds_attrib['odds'];
$lad_odds = $lad_xml->response->lad->class->type->market->participant;
$lad_odds_attrib = $lad_odds->attributes();
$lad_odds_attrib['odds'];
as you can see they are essentially the very similar, but im not quite sure how i can streamline the process of setting a working var without writing 3 lines each time.
The function you're probably looking for is called SimpleXMLElement::xpath().
XPath is a language of it's own designed to pick stuff out of XML files. In your case, the xpath expression to get the odds attribute of all these elements is:
response/*/class/type/market/participant/#odds
You can also replace the * with the concrete element name or allow multiple names there and what not.
$odds = $lad_xml->xpath('response/*/class/type/market/participant/#odds');
Different to your code, this has all attribute elements inside the array (you have the attribute's parent element inside the variable). An example outcome (considering two of such elements) would be:
Array
(
[0] => SimpleXMLElement Object
(
[#attributes] => Array
(
[odds] => a
)
)
[1] => SimpleXMLElement Object
(
[#attributes] => Array
(
[odds] => a
)
)
)
You can turn that into strings as well easily:
$odds_strings = array_map('strval', $odds);
print_r($odds_strings);
Array
(
[0] => a
[1] => a
)
Xpath is especially useful if you say, you want to get all participant element's odds attributes:
//participant/#odds
You don't need to explicitly specify each of the parent element names.
I hope this is helpful.
You can do it like this:
function getAttrib ($xmlObj, $attrName) {
$wh_odds = $xmlObj->response->$attrName->class->type->market->participant;
$wh_odds_attrib = $wh_odds->attributes();
return $wh_odds_attrib['odds'];
}
getAttrib ($wh_xml, "will");
hope that helps.
I have a situation where I have already obtained and manipulated SQL data into an array and into a tree. I am trying to avoid recursion at all costs because it has bitten me in the behind in the past.
I have an array of elements with Parent_IDs, and I'd like to be able to obtain a list of all of their children and subchildren. It shouldnt be nearly as complex as going the other way (from an array to a nested tree) which I got without issue using references, but for some reason I'm brain-frozen...
Any help appreciated.
I have the data in two possible formats because I have manipulated it already. Whichver is best for input can be used. this is a structure (print_r) of the two arrays I have:
Array
(
[202735] => Array
(
[ID] => 202735
[text] => aaafdf
[Parent] =>
)
[202737] => Array
(
[ID] => 202737
[text] => Filho 2
[Parent] => 202735
)
[202733] => Array
(
[ID] => 202733
[text] => Neto 1
[Parent] => 202731
)
[202739] => Array
(
[ID] => 202739
[text] => Neto 2
[Parent] => 202737
)
)
or
Array
(
[0] => Array
(
[ID] => 202735
[text] => aaafdf
[Parent] =>
[children] => Array
(
[0] => Array
(
[ID] => 202737
[text] => Filho 2
[Parent] => 202735
[children] => Array
(
[0] => Array
(
[ID] => 202739
[text] => Neto 2
[Parent] => 202737
)
)
)
)
)
[1] => Array
(
[ID] => 202733
[text] => Neto 1
[Parent] => 202731
)
)
Desired output in the format:
(first level parent => all children and grandchildren)
array(202731=>array(202735));
array(202735=>array(202737,202739));
or similar... Ideally i'll wrap this in a function like ListChildren($InitialParent), and return all children from such... invoking ListChildren(0) or (null) would list all elements and all subs...
(additional array elements can be ignored for the purpose of this exercise)
OBS: some data in the array is missing... namely the category above 202735, which would be 202731, but thats just because i limited the data I copied in... basically I can have either a flat array with parent ids, or a "tree" array with nested children as the source.
I ended up with this. Thanks for the help, in the end I reverted to a recursive code. I'll monitor it for performance although I suppose just within the PHP layer it wont be an issue. I had a bad experience when I went in for maintenance on a project that had DB calls in a recursive function... as the usage grew the recursvie function usage grew exponentially and the DB calls went to 100s of thousands...
anyways:
function __GetChildrenRec($Lista, $Categoria){
// Return false if $initialParent doesn't exist
if ($Categoria == 0) $Categoria = "";
if (!isset($Lista[$Categoria])) return FALSE;
// Loop data and assign children by reference
foreach ($Lista as $CategAtual) {
if ($CategAtual[Parent] == $Categoria) {
$Filhos[] = $CategAtual[ID];
$Filhos = array_merge((array)$Filhos,(array)self::__GetChildrenRec($Lista, $CategAtual[ID]));
}
}
// Return the data
return is_array($Filhos) ? $Filhos : array();
}
There really is no reason to avoid recursion unless it is causing you a genuine performance problem. Recursion is how most developers looking at problems of this nature would probably attempt to solve them, and using alternative approaches can hurt maintainability because your code is doing something that someone who needs to maintain your code at a later date doesn't expect.
Generally speaking, unrolling recursion means managing a stack. Recursion is a way of getting the language you're using to manage the stack for you, if you don't use recursion, then you'll need to manipulate a stack yourself inside your function. The simplest way of doing this is with array_push and array_pop, functions built into PHP that let you use an array as a stack.
The stack-based approach is rather complex compared to simply using recursion though, and if recursion is giving you problems then manually maintaining a stack certainly will. There are benefits to be sure, but honestly I'd suggest that you try to figure out recursion instead, as it really is easier to deal with, and while it isn't as performant as managing a stack yourself, the performance loss in probably not going to be a bottleneck in your code as the bottlenecks in PHP scripts tend to be where PHP interfaces with the outside world (databases, files, network connections, etc).
Using the first array format:
function list_children ($array, $initialParent) {
// Return false if $initialParent doesn't exist
if (!isset($array[$initialParent])) return FALSE;
// Loop data and assign children by reference
foreach ($array as &$item) {
if (isset($array[$item['parent']])) {
if (!isset($array[$item['parent']]['children'])) $array[$item['parent']]['children'] = array();
$array[$item['parent']]['children'][] = &$item;
}
}
// Return the data
return (isset($array[$initialParent]['children'])) ? $array[$initialParent]['children'] : array();
}
What this does is basically create the second array from the first, but it does it by reference - so the initial parent can still be found by it's ID, and returned. Returns the array of children, an empty array() if there are no children, or FALSE if the $initialParent doesn't exist.
Somebody must have come up with a solution for this by now. We are using PHP 5.2. (Don't ask me why.) I wrote a PHP class to display a recordset as an HTML table/datagrid, and I wish to expand it so that we can sort the datagrid by whichever column the user selects. In the below example data, we may need to sort the recordset array by Name, Shirt, Assign, or Age fields. I will take care of the display part, I just need help with sorting the data array.
As usual, I query a database to get a result, iterate throught he result, and put the records into an assciateiave array. So, we end up with an array of arrays. (See below.) I need to be able to sort by any column in the dataset. However, I will not know the column names at design time, nor will I know if the colums will be string or numeric values.
I have seen a ton of solutions to this, but I have not seen a GOOD and GENERIC solution Can somebody please suggest a way that I can sort the recordset array that is GENERIC, and will work on any recordset? Again, I will not know the fields names or datatypes at design time. The array presented below is ONLY an example.
UPDATE: Yes, I would love to have the database do the sorting, but that is just not going to happen. The queries that we are running are very complex. (I am not really querying a table of Star Trek characters.) They include joins, limits, and complex WHERE clauses. Writing a function to pick apart the SQL statement to add an ORDER BY is really not an option. Besides, sometimes we already have the array that is a result of the query, rather than the ability to run a new query.
Array
(
[0] => Array
(
[name] => Kirk
[shrit] => Gold
[assign] => Bridge
)
[1] => Array
(
[name] => Spock
[shrit] => Blue
[assign] => Bridge
)
[2] => Array
(
[name] => Uhura
[shrit] => Red
[assign] => Bridge
)
[3] => Array
(
[name] => Scotty
[shrit] => Red
[assign] => Engineering
)
[4] => Array
(
[name] => McCoy
[shrit] => Blue
[assign] => Sick Bay
)
)
Well, first off, you should be sorting in the database (it's more efficient). If not
In php 5.3+:
function getSortCommand($field, $sortfunc) {
return function($var1, $var2) use ($field, $sortfunc) {
return $sortfunc($var1[$field], $var2[$field]);
}
}
in php <= 5.2, you'll need to create a pure function:
function getSortCommand($field, $sortfunc) {
return create_function('$var1, $var2', 'return '.$sortfunc.'($var1["'.$field.'"], $var2["'.$field.'"]);');
}
Usage for both:
$array = usort($array, getSortCommand('name', 'strnatcasecmp'));
To adjust the field, you just change the $field parameter. To change the sort function, just change the $sortfunc parameter...
I suggest you do it in the database. Otherwise, you can use this:
function sortby(&$arr, $column) {
usort($arr,
function ($a, $b) use ($column) {
return strcmp($a[$column], $b[$column]);
}
);
}
You should definitely do the sorting when querying the database.
You do this by adding a link like ?orderby=name&dir=asc to the <TH>s
Then when you query the database you check for the existence of $_GET['orderby']. If it exists you add an order by clause.
A clientside option is http://tablesorter.com/docs/