Is there any way to maintain a sorted array of objects?
For example, if I have an object with properties ID, Date, Name and a collection of these objects:
$col = array();
public function addNewObject($id, $date, $name)
{
$col[] = new Object($id, $date, $name);
//but instead of appending, it should place it by Name desc
}
If I call something like getObjects, it would return the items in the collection by Name desc.
I think there were some answers for getting objects back in a sorted order, but for efficiency, I would think it be better to sort at insert as the "sort by" variable in my case will never change.
UPDATE:
So based on the comments, I should resort the whole array each time something is added but that seems a bit memory intensive...
Since the array would always be in sorted order to start out with I can identify the location where I want to insert by traversing the array (would this be efficient, is there a better way?). Once I find that how could I "insert" a new object into the array?
I do not imagine that the array will be very large but I would like to implement this the most efficient way possible.
If you're not keen on resorting the array after you add (although I'd recommend it; realistically this wont be a performance issue and it keeps the code readable.
However, if you definitely don't want to do this then you can, as you said, traverse the array and find out where to insert:
$col = array();
public function addNewObject($id, $date, $name){
//Find the index to insert at
$index = 0;
foreach($col as $i => $item){
if($item->name > $name){
//This item is after the item we want to insert.
//Use the previous index and stop traversing
break;
}
$index = $i;
}
$col = array_splice($col, $index, 0, new Object($id, $date, $name));
}
Using array_splice to insert at an arbritary position thanks to https://stackoverflow.com/a/3797526/505722
this is a good example of a function that sorts an array from whatever key you want it sorted by
http://www.php.net/manual/en/function.sort.php#99419
in your example you should run it like :
array_sort($col, 'Name', SORT_DESC));
take in mind that every time you add a new item to the array the whole array is sorted each time
Related
If you have any array $p that you populated in a loop like so:
$p[] = array( "id"=>$id, "Name"=>$name);
What's the fastest way to search for John in the Name key, and if found, return the $p index? Is there a way other than looping through $p?
I have up to 5000 names to find in $p, and $p can also potentially contain 5000 rows. Currently I loop through $p looking for each name, and if found, parse it (and add it to another array), splice the row out of $p, and break 1, ready to start searching for the next of the 5000 names.
I was wondering if there if a faster way to get the index rather than looping through $p eg an isset type way?
Thanks for taking a look guys.
Okay so as I see this problem, you have unique ids, but the names may not be unique.
You could initialize the array as:
array($id=>$name);
And your searches can be like:
array_search($name,$arr);
This will work very well as native method of finding a needle in a haystack will have a better implementation than your own implementation.
e.g.
$id = 2;
$name= 'Sunny';
$arr = array($id=>$name);
echo array_search($name,$arr);
Echoes 2
The major advantage in this method would be code readability.
If you know that you are going to need to perform many of these types of search within the same request then you can create an index array from them. This will loop through the array once per index you need to create.
$piName = array();
foreach ($p as $k=>$v)
{
$piName[$v['Name']] = $k;
}
If you only need to perform one or two searches per page then consider moving the array into an external database, and creating the index there.
$index = 0;
$search_for = 'John';
$result = array_reduce($p, function($r, $v) use (&$index, $search_for) {
if($v['Name'] == $search_for) {
$r[] = $index;
}
++$index;
return $r;
});
$result will contain all the indices of elements in $p where the element with key Name had the value John. (This of course only works for an array that is indexed numerically beginning with 0 and has no “holes” in the index.)
Edit: Possibly even easier to just use array_filter, but that will not return the indices only, but all array element where Name equals John – but indices will be preserved:
$result2 = array_filter($p, function($elem) {
return $elem["Name"] == "John" ? true : false;
});
var_dump($result2);
What suits your needs better, resp. which one is maybe faster, is for you to figure out.
I've done a fair bit of googling and couldn't find anything that works, I'm just getting nothing back, this is probably something simple but there's a lot of variations that don't seem to match what I'm doing.
To give you an overall idea what I'm at, I'm accessing an API and getting back info as an object. There are comments and attachments, these are in separate arrays.
What i want to do is display the comments and attachments all together in the order of the date and time not separately.
I figured the best way is to create a loop through the comments array, then create a loop through the attachment array, then join both and sort by the date (epoch) and then loop through the whole merged loop echoing what i want. That should provide some context, right now i just want to create the multidimensional array for comments and i can figure out the rest.
$comments_holder = array();
//total number of comments in the array
$comment_total = $issue_json->fields->comment->total -1;
$i=1;
while ($i <= $comment_total)
{
//this is the date,time and timezone info for each comment
$raw_date = $issue_json->fields->comment->comments[$i]->updated;
$comments_holder[$i] = array();
//convert_sql_time just converts from 2012-11-04T16:33:00.936+600 into epoch time so i can sort the results later based on date
$comments_holder[$i]['comments_date'] = convert_sql_time($raw_date);
$comments_holder[$i]['comments_displayName'] = $issue_json->fields->comment->comments[$i]->author->displayName;
$comments_holder[$i]['comments_body'] = $issue_json->fields->comment->comments[$i]->body;
}
if everything is okay with data, this code will be enough for building such array:
$comments = $issue_json->fields->comment->comments;
$result = array();
foreach ($comments as $comment) {
$result[] = array(
'comments_date' => convert_sql_time($comment->updated),
'comments_displayName' => $comment->author->displayName,
'comments_body' => $comment->body,
);
}
print_r($result);
if comment->comments is an array, there is no need to keep it's count separately;
foreach is enough for iterating through the array and there is no need to keep separate variable for calculating array index;
[] notation will automatically increase array index and assigning array directly will do the trick(i.e. will result to multi dim array)
foreach ( $this->parent->get_sections(null, $this->parent->author) as $section)
{
//...
}
I'm trying to do is force the loop to output each $section in the order I want. Each $section's name can be retrieved by $section->name. Let's say that I want to output $section "Section 2" first and then "Section 1" (and not in the order of the foreach). How can I force it do that? I presume the proper way would be a for loop with an if checking section names each time.
The proper way would be sorting the results when you call parent->get_sections(). How you would do this is entirely up to the implementation of that class and method. Changing this foreach to for for the sake of sorting seems like a code smell to me.
For the sake of answering the question as technical as possible.
$sections = $this->parent->get_sections(null, $this->parent->author);
$num_sections = count($sections);
for ($i = 0; $i < $num_sections; $i++) {
// what you do here is up to you $sections[$i]
}
Especially if you are not aware of the specific number of sections, you could use usort() to do a dynamic custom sort on the get_sections()-returned array or object and then utilize the existing code. (This is a little more elegant, imo, than doing the same in a for/foreach loop).
Not knowing the structure of your code, I would do something like.
// Get Org Sections
$sections = $this->parent->get_sections(null, $this->parent->author);
// Loop thru sections to get an array of names
foreach ( $sections as $key=>$section)
{
$sorted_sections[$section->name] = $key;
}
// Sort Array
//ksort — Sort an array by key
//krsort — Sort an array by key in reverse order
krsort($sorted_sections);
foreach ( $sorted_sections as $section)
{
// Orig Code
}
$section = $this->parent->get_sections(null, $this->parent->author);
echo $section[2]->name;
echo $section[1]->name;//just output the indexes the way you want
if you want it sorted, in say descending order, you can sort it that way and then use a for loop to display.
I have a working script, but I'm sure that my method of managing arrays could be better. I've searched for a solution and haven't found one, but I'm sure that I should be using the functionality of associative arrays to do things more efficiently.
I have two arrays, one from a CSV file and one from a DB. I've created the CSV array as numeric and the DB array as associative (although I'm aware that the difference is blurry in PHP).
I'm trying to find a record in the DB array where the value in one field matches a value in the CSV array. Both arrays are multi-dimensional.
Within each record in each array there is a reference number. It appears once in the CSV array and may appear in the DB array. If it does, I need to take action.
I'm currently doing this (simplified):
$CSVarray:
('reference01', 'blue', 'small' ),
('reference02', 'red', 'large' ),
('reference03', 'pink', 'medium' )
$Dbarray:
(0 => array(ref=>'reference01',name=>"tom",type=>"mouse"),
(1 => array(ref=>'reference02',name=>"jerry",type=>"cat"),
(2 => array(ref=>'reference03',name=>"butch",type=>"dog"),
foreach ($CSVarray as $CSVrecord) {
foreach ($Dbarray as $DBrecord) {
if ($CSVarray[$numerickey] == $DBrecord['key'] {
do something with the various values in the $DBrecord
}
}
}
This is horrible, as the arrays are each thousands of lines.
I don't just want to know if matching values exist, I want to retrieve data from the matching record, so functions like 'array_search ' don't do what I want and array_walk doesn't seem any better than my current approach.
What I really need is something like this (gibberish code):
foreach ($CSVarray as $CSVrecord) {
WHERE $Dbarray['key']['key'] == $CSVrecord[$numerickey] {
do something with the other values in $Dbarray['key']
}
}
I'm looking for a way to match the values using the keys (either numeric or associative) rather than walking the arrays. Can anyone offer any help please?
use a hash map - take one array and map each key of the record it belongs to, to that record. Then take the second array and simply iterate over it, checking for each record key if the hashmap has anything set for it.
Regarding your example:
foreach ($DBarray as $DBrecord){
$Hash[$record[$key]] = $DBrecord;
}
foreach ($CSVarray as $record){
if (isset($Hash[$record[$CSVkey]])){
$DBrecord = $Hash[$record[$CSVkey]];
//do stuff with $DBrecord and $CSVrecord
}
}
this solution works at O(n) while yours at O(n^2)...
You can use foreach loops like this too:
foreach ($record as $key => $value) {
switch($key)
{
case 'asd':
// do something
break;
default:
// Default
break;
}
}
A switch may be what you are looking for also :)
Load CSV into the db, and use db (not db array) if possible for retrieval. Index the referenceid field.
I have 2 sets of arrays:
$dates1 = array('9/12','9/13','9/14','9/15','9/16','9/17');
$data1 = array('5','3','7','7','22','18');
// for this dataset, the value on 9/12 is 5
$dates2 = array('9/14','9/15');
$data2 = array('12','1');
As you can see the 2nd dataset has fewer dates, so I need to "autofill" the reset of the array to match the largest dataset.
$dates2 = array('9/12','9/13','9/14','9/15','9/16','9/17');
$data2 = array('','','12','1','','');
There will be more than 2 datasets, so I would have to find the largest dataset, and run a function for each smaller dataset to properly format it.
The function I'd create is the problem for me. Not even sure where to start at this point. Also, I can format the date and data arrays differently (multidimensional arrays?) if for some reason that is better.
You can do this in a pretty straightforward manner using some array functions. Try something like this:
//make an empty array matching your maximum-sized data set
$empty = array_fill_keys($dates1,'');
//for each array you wish to pad, do this:
//make key/value array
$new = array_combine($dates2,$data2);
//merge, overwriting empty keys with data values
$new = array_merge($empty,$new);
//if you want just the data values again
$data2 = array_values($new);
print_r($data2);
It would be pretty easy to turn that into a function or put it into a for loop to operate on your array sets. Turning them into associative arrays of key/value pairs would make them easier to work with too I would think.
If datas are related will be painful to scatter them on several array.
The best solution would be model an object with obvious property names
and use it with related accessor.
From your question I haven't a lot of hint of what data are and then I have to guess a bit:
I pretend you need to keep a daily log on access on a website with downloads. Instead of using dates/data1/data2 array I would model a data structure similar to this:
$log = array(
array('date'=>'2011-09-12','accessCount'=>7,'downloadCount'=>3),
array('date'=>'2011-09-13','accessCount'=>9), /* better downloadsCount=>0 though */
array('date'=>'2011-09-15','accessCount'=>7,'downloadCount'=>3)
...
)
Using this data structure I would model a dayCollection class with methods add,remove,get,set, search with all methods returning a day instance (yes, the remove too) and according signature. The day Class would have the standard getter/setter for every property (you can resolve to magic methods).
Depending on the amount of data you have to manipulate you can opt to maintain into the collection just the object data (serialize on store/unserialize on retrieve) or the whole object.
It is difficult to show you some code as the question is lacking of details on your data model.
If you still want to pad your array than this code would be a good start:
$temp = array($dates, $data1, $data2);
$max = max(array_map('count',$temp));
$result = array_map( function($x) use($max) {
return array_pad($x,$max,0);
}, $temp);
in $result you have your padded arrays. if you want to substitute your arrays do a simple
list($dates, $data1, $data2) = array_map(....
You should use hashmaps instead of arrays to associate each date to a data.
Then, find the largest one, cycle through its keys with a foreach, and test the existence of the same key in the small one.
If it doesn't exist, create it with an empty value.
EDIT with code (for completeness, other answers seem definitely better):
$dates_data1 = array('9/12'=>'5', '9/13'=>'3', '9/14'=>'7' /* continued */);
$dates_data2 = array('9/14'=>'12', '9/15'=>'1');
#cycle through each key (date) of the longest array
foreach($dates_data1 as $key => $value){
#check if the key exists in the smallest and add '' value if it does not
if(!isset( $date_data2[$key] )){ $date_data2[$key]=''; }
}