Merging PHP Arrays - php

I have multiple associative arrays that I want to merge if all values except startdate are the same. If two arrays are indeed the same, I want to merge them and create a new element enddate so that startdate and enddate show the date range. All dates within the range must be represented in the original arrays, i.e. if a date is missing, the dates on either side of it must not be merged.
Array('color'=>'red','size'=>'large','shape'=>'circle','startdate'=>'2011-08-17')
Array('color'=>'red','size'=>'large','shape'=>'circle','startdate'=>'2011-08-18')
Array('color'=>'red','size'=>'large','shape'=>'square','startdate'=>'2011-08-20')
should become:
Array('color'=>'red','size'=>'large','shape'=>'circle','startdate'=>'2011-08-17','enddate'=>'2011-08-18')
Array('color'=>'red','size'=>'large','shape'=>'square','startdate'=>'2011-08-20')
So far I have tried looping through each array and creating a multidimensional array:
foreach($arrays as $id => $array){
$mergearray[$array['red']][$array['large']][$array['circle']] = $id;
};
in order to check whether another array has the same values. I'm trying to use those arrays to reconstruct arrays in the original structure.

Avoid the day/ date functions it's a waste of cpu time for no added benefit (answer from Dave).
Avoid the massive array function use (answer from adlawson), same waste of time, just ordering arrays and processing the smart way will be much faster.
But mostly, what are you trying to do, why are those arrays like that and ... is there an SQL behind all that ?
Because if there is, there's much simpler solutions than attempting to merge badly-formed arrays (with all due respect, the transformation you seek implies that something went wrong at some point.. a start_time becoming an end_time denotes an attempt at keeping a full item history, which has no place in PHP itself imho).
I'll give you the right PHP code if it really is what you need but I have quite a doubt it's the correct way to go forward for your application.

Something like this should do the trick:
$newArray = array();
$newLine = false;
$prev_day = false;
foreach ($array as $line) {
$this_day = strtotime($line['startdate']);
if (($newLine) && ($prev_day == strtotime('-1 day', $this_day))) {
$newLine['enddate'] = $line['startdate'];
$newArray[] = $newLine;
$newLine = false;
} elseif (!$newLine) {
$newLine = $line;
}
if ($newLine) {
$newLine['enddate'] = $line['startdate'];
}
$prev_day = $this_day;
}
$newArray[] = $newLine;

I can't see the actual implementation of such a merge, but this should work. It may look long winded, but it takes care of a few things as it goes.
/**
* #example array_merge_allbutstartdate($array1, $array2, $array3, $array4, $arr...)
* #param array $array1
* #param array $array2, $arr...
* #return array
*/
function array_merge_allbutstartdate(array $array1, array $array2)
{
$out = array();
$date = array();
$startKey = 'startdate';
$endKey = 'enddate';
foreach (func_get_args() as $item) {
if (!is_array($item)) {
trigger_error('All arguments should be an array.', E_USER_ERROR);
}
$temp = null;
if (isset($item[$startKey])) {
$temp = $item[$startKey];
unset($item[$startKey]);
}
if (!in_array($item, $out)) {
$i = count($out);
$out[] = $item;
} else {
$i = array_search($item, $out);
}
if (null !== $temp) {
$date[$i][] = $temp;
}
}
foreach ($date as $j => $row) {
array_map('strtotime', $row);
$start = array_search(min($row), $row);
$end = array_search(max($row), $row);
// Add start date
$out[$j][$startKey] = $date[$j][$start];
// Only add end date if it is not equal to start date
if ($date[$j][$start] !== $date[$j][$end]) {
$out[$j][$endKey] = $date[$j][$end];
}
}
return $out;
}

Given that you have an array of arrays already, what you're actually trying to do is DELETE consecutive entries (other than the first entry for each time span).
Your algorithm would be:
$expected_start_time= 0; // initial val
foreach($all_entries as $k => &$v) {
$start_time = strtotime($v['startdate']);
if($start_time != $expected_start_time) {
$range_start =& $v; // this is a range beginning. Put end date in here
} else {
$range_start['enddate'] = $v['startdate'];
unset($all_entries[$k]);
}
$expected_date = strtotime('+1 day', $start_time);
}
This is basically a more minimal, and in-place version of Dave Child's answer.

function group_and_sort($data)
{
$out = array();
while($data) {
// Shift off the first element
$buffer = array_shift($data);
$end_date = $buffer['startdate'];
// Try to group successive elements...
while($data) {
// Case 1: Does the next element differ by more than just date?
if(count(array_diff_assoc($buffer, $data[0])) > 1) {
break;
}
// Case 2: Does the next element have an unexpected date?
$expected_date = date('Y-m-d', strtotime('+1 day', strtotime($end_date)));
if($data[0]['startdate'] != $expected_date) {
break;
}
// Otherwise, push end_date forward and throw away the element
$end_date = $data[0]['startdate'];
array_shift($data);
}
// If the dates differ, record the range.
if($buffer['startdate'] != $end_date) {
$buffer['enddate'] = $end_date;
}
$out[] = $buffer;
}
return $out;
}
Assumes the elements are already sorted by date. If they're not, you could use:
function sort_startdate($a, $b)
{
return strcmp($a['startdate'], $b['startdate']);
}
usort($data, 'sort_startdate');
prior to passing it to group_and_sort($data).

Related

Stop cycle when a condition is true php

I have this code which receives as parameter a date in this format: 2021-10-12.
The function looks for that date entered as a parameter that matches another date stored in an array of x positions.
I need a loop that iterates or searches in all the positions of the array and when it finds the date it finishes the search and returns the one it found.
this is what my array looks like:
and the data that I want to bring is what is before /
It means: if the date 2021-10-31 stored in $registro_a_array matches $fechaAMostrar21 it will bring me the number 50 and stop iterating.
$getTransitos = function ($datos_transito, $fechaAMostrar21){
$datos_transito_string = strval($datos_transito);
$registro_a_array = explode(',', $datos_transito_string);
$size_array2 = count($registro_a_array);
$y = 0;
while ( $y <= $size_array2 ) {
$buscando = str_contains($registro_a_array[$y],$fechaAMostrar21);
if ($buscando === true) {
$tra_cantidad_ex = explode('/', $registro_a_array[$y]);
$tra = $tra_cantidad_ex[0];
$transito_cantidad = intval($tra);
}else{
$transito_cantidad = 0;
}
$y++;
}
return $transito_cantidad;
};
This code works fine when it doesn't find the date, but I can't stop it when it finds the date. i need help.
In theory what I want to achieve is that if the date coincides with the date that is stored in my array (in any position) it stops the search and shows the result.
You can probably rewrite the function like this:
$getTransitos = function ($datos_transito, $fechaAMostrar21) {
$datos_transito_string = strval($datos_transito);
$registro_a_array = explode(',', $datos_transito_string);
// Check all elements
foreach ($registro_a_array as $elemento) {
// Split element into cantidad, fecha and wtf
list ($cantidad, $fecha, $alio) = preg_split('#[/#]#', $elemento);
// Found fecha?
if ($fecha === $fechaAMostrar21) {
return intval($cantidad);
}
// If dates are ordered, in the array, and we passed the good one, we won't
// find our date. So just exit the loop.
// if ($fecha > $fechaAMostrar21) {
// break;
// }
// The above ALSO assumes that dates were in YMD format,
// so that lexicographical ordering ("<") is the same as
// date ordering. With European format dates, this would
// not be true.
}
// if we're here, we didn't find the date.
return 0;
};
Much simpler. Just grep for the date, explode and check for the first element:
$result = preg_grep("~/$fechaAMostrar21~", explode(",", $datos_transito_string));
return explode("/", reset($result))[0] ?? 0;
You can reduce the list by looking for each marker and accepting the first value's initial integer:
$foo = [
'50/2021-10-31#2',
'93/2021-11-14#2',
'300/2022-02-14#2',
];
$bar = '2021-11-14';
$getTransitos = fn(string $test, array $values): ?int => array_reduce(
$values,
fn($found, $value): ?int => $found ?? (
strpos($value, $test) ? sscanf($value, '%d/')[0] : null
),
null
);
var_dump($getTransitos($bar, $foo)); // int(93)
https://3v4l.org/hrr7F
Other ways of doing the check:
$getTransitos = function(string $test, array $values): ?int {
foreach ($values as $value) {
if (strpos($value, $test)) {
return sscanf($value, '%d/')[0];
}
}
return null;
};
https://3v4l.org/uccaO
$getTransitos = fn(string $test, array $values) => sscanf(array_values(array_filter(
$values,
fn($value) => strpos($value, $test)
))[0] ?? '', '%d/')[0] ?? null;
https://3v4l.org/fWGOY

Sorting PHP array without ksort

I am trying to manually sort a PHP array without making use of ksort.
This is how my code looks at the moment:
function my_ksort(&$arg){
foreach($arg as $key1 => $value1){
foreach($arg as $key2 => $value2){
if($key1 > $key2){
$aux = $value2;
$arg[$key2] = $value1;
$arg[$key1] = $aux;
}
}
}
}
It doesn't sort, I can't figure out how to make it sort.
You could try this:
function my_ksort(&$arg)
{
$keys=array_keys($arg);
sort($keys);
foreach($keys as $key)
{
$val=$arg[$key];
unset($arg[$key]);
$arg[$key]=$val;
}
}
I'm sorting the keys separately and then deleting the elements one-by-one and appending them to the end, in ascending order.
I'm using another sorting function (sort()), but if you want to eliminate all available sorting functions from your emulation, sort() is much easier to emulate. In fact, #crypticous's algorithm does just that!
This function return array in ASC. Take in consideration that I'm using goto which is supported in (PHP 5 >= 5.3.0)
function ascending_array($array){
if (!is_array($array)){
$array = explode(",", $array);
}
$new = array();
$flag = true;
iter:
$array = array_values($array); // recount array values with new offsets
(isset($min["max"])) ? $min["value"] = $min["max"] : $min["value"] = $array[0];
$min["offset"] = 0;
for ($i=0;$i<count($array);$i++){
if ($array[$i] < $min["value"]){ // redefine min values each time if statement executed
$min["value"] = $array[$i];
$min["offset"] = $i;
}
if ($flag){ // execute only first time
if ($array[$i] > $min["value"]){ // define max value from array
$min["max"] = $array[$i];
}
$flag = false;
}
if ($i === (count($array)-1)){ // last array element
array_push($new,$min["value"]);
unset($array[$min["offset"]]);
}
}
if (count($array)!=0){
goto iter;
}
print_r($new);
}
$arr = array(50,25,98,45);
ascending_array($arr); // 25 45 50 98
PS. When I was studying php, I wrote this function and now remembered that I had it (that's why I really don't remember what I am doing in it, though fact is it's working properly and hopefully there are comments too), hope you'll enjoy :)
DEMO
I was checking some issue related to this post and i wanted to give my insight about it ! here's what i would have done to implement php's sort :
$array_res = array();
$array = array(50,25,98,45);
$i=0;
$temp = $array[0];
$key = array_search($temp, $array);
while ($i<count($array)-1){
$temp = $array[0];
for($n=0;$n<count($array) ;$n++)
{
if($array[$n]< $temp && $array[$n] != -1 )
{
$temp = $array[$n];
}
else{continue;}
}
//get the index for later deletion
$key = array_search($temp, $array);
array_push($array_res, $temp);
/// flag on those which were ordered
$array[$key] =-1;
$i++;
}
// lastly append the highest number
for($n=0;$n<count($array) ;$n++)
{
if ($array[$n] != -1)
array_push($array_res, $array[$n]);
}
// display the results
print_r($array_res);
This code will display : Array
(
[0] => 25
[1] => 45
[2] => 50
[3] => 98
)
Short and sweet
function custom_ksort($arg)
{
$keys = array_keys($arg);
sort($keys);
foreach($keys as $newV)
{
$newArr[$newV] = $arg[$newV];
}
return $newArr;
}
It looks like your issue is that you're changing "temporary" characters $key1 and $key2 but not the actual arrays. You have to change $arg, not just $key1 and $key2.
Try something like:
$arr = Array(3=>"a",7=>"b");
print_r( $arr );
foreach( $arr as $k=>$v ){
unset($arr[$k]);
$arr[$k+1] = $v;
}
print_r($arr);

PHP remove duplicate instances of dates in an array

I've tried various permutations of array_unique, and have searched other generic questions here on removing duplicate values from an array, but I can't quite set upon the answer I need. I have an array being passed of dates and values, and only want to view the DATE value once per date.
I'm using this for a Google chart and only want the date labels to show up once for each date. And I don't want to remove it entirely, because I want to be able to plot it on the chart.
So, an example array being passed:
["June 4",30],["June 4",35],["June 5",46],["June 5",38.33],["June 5",12]
And how I want it:
["June 4",30],["",35],["June 5",46],["",38.33],["",12]
Ideas?
Since you're using the data to feed into a google chart, I'm assuming that you know exactly what you need as far as output data. There's already some suggestions above for better ways to structure the data, but that probably won't work directly for a google chart.
How about this?
$data = [["June 4",30],["June 4",35],["June 5",46],["June 5",38.33],["June 5",12]];
$found = array();
foreach ($data as $i => $x) {
if (in_array($x[0], $found)) {
$data[$i][0] = '';
} else {
$found[] = $x[0];
}
}
print_r($data);
Basically, it's just building a list of dates that it's already seen. We loop through the data, and check if we've seen the date... if we have, we clear it from the data, otherwise we save it to the list so it'll be cleared next time.
Here's an alternate solution that only checks for duplicate dates that are consecutive, unlike the first solution that will remove all duplicates. This is probably closer to what you need for charting:
$data = [["June 4",30],["June 4",35],["June 5",46],["June 5",38.33],["June 5",12]];
$last = '';
foreach ($data as $i => $x) {
if ($x[0] == $last) {
$data[$i][0] = '';
} else {
$last = $x[0];
}
}
print_r($data);
In this case, we're just keeping track of the last date we've seen... and if our new date matches that, we clear it.
This is a possible solution for your problem, though I would recommend a re-construction as Patashu & Nikola R stated.
$untrimmed = [["June 4",30],["June 4",35],["June 5",46],["June 5",38.33],["June 5",12]];
$trimmed = stripDates($untrimmed);
function stripDates($dates) {
foreach( $dates as $key=>$date ) {
if ($key>0) {
if ($date[0] === $dates[$key-1][0]) {
$dates[$key][0] = "";
} else if($dates[$key-1][0] === "") {
for ($i = $key-1; $i > -1; $i--) {
if ($date[0] === $dates[$i][0]) $dates[$key][0] = "";
if ($dates[$key] != "") break;
}
}
}
}
return $dates;
}
// Note: This would require dates to be added chronically
//Output: ["June 4",30],["",35],["June 5",46],["",38.33],["",12]
I would recommend something like this:
$unconstructed = [["June 4",30],["June 4",35],["June 5",46],["June 5",38.33],["June 5",12]];
$constructed = constructAssoc($unconstructed);
function constructAssoc($dates) {
$constructed = array();
foreach( $dates as $index=>$date ) {
if (!array_key_exists($date[0], $constructed)) {
$constructed[$date[0]] = array("index"=>$index, "value"=>$date[1]);
} else {
array_push($constructed[$date[0], ["index"=>$index,"value"=>$date[1]]);
}
}
return $constructed;
}
//Output: ["June 4"=> [["index"=>0, "value"=>30], ["index"=>1, "value"=>35]], "June 5"=>[["index"=>2, "value"=>46], ["index"=>3, "value"=>38.33], ["index"=>4, "value"=>12]]]
Note: Added index in recommended solution if a more accurate re-construction is needed.

PHP combinations of array elements

I want to generate all possible combination of array elements to fill a placeholder, the placeholder size could vary.
Let say I have array $a = array(3, 2, 9, 7) and placeholder size is 6. I want to generate something like the following:
3,3,3,3,3,3
2,3,3,3,3,3
2,2,3,3,3,3
...........
...........
7,7,7,7,7,9
7,7,7,7,7,7
However (2,3,3,3,3,3) would be considered the same as (3,2,3,3,3,3) so the later one doesn't count.
Could anyone point me to the right direction? I know there is Math_Combinatorics pear package, but that one is only applicable to placeholder size <= count($a).
Edit
I am thinking that this one is similar to bits string combination though with different number base
I have no PHP source code for you but some sources that might help.
Some C code. Look at 2.1:
http://www.aconnect.de/friends/editions/computer/combinatoricode_g.html
Delphi code: combination without repetition of N elements without use for..to..do
Wiki article here
Well it took quit some time to figure this one out.
So i split the question into multiple parts
1.
I firsrt made an array with all the possible value options.
function create_all_array($placeholder, array $values)
{
if ($placeholder <= 0) {
return [];
}
$stack = [];
$values = array_unique($values);
foreach ($values as $value) {
$stack[] = [
'first' => $value,
'childs' => create_all_array($placeholder - 1, $values)
];
}
return $stack;
}
2.
Then I made a function to stransform this massive amount of data into string (no check for uniques).
function string($values, $prefix = '')
{
$stack = [];
foreach($values as $value) {
$sub_prefix = $prefix . $value['first'];
if (empty($value['childs'])) {
$stack[$sub_prefix] = (int)$sub_prefix;
} else {
$stack = array_merge($stack, string($value['childs'], $sub_prefix));
}
}
return $stack;
}
3.
Then the hard part came. Check for duplicates. This was harder than expected, but found some good anser to it and refactored it for my use.
function has_duplicate($string, $items)
{
$explode = str_split ($string);
foreach($items as $item) {
$item_explode = str_split($item);
sort($explode);
$string = implode('',$explode);
sort($item_explode);
$item = implode($item_explode);
if ($string == $item) {
return true;
}
}
return false;
}
4.
The last step was to combine the intel into a new funciton :P
function unique_string($placeholder, array $values)
{
$stack = string(create_all_array($placeholder, $values));
$check_stack = [];
foreach($stack as $key => $item) {
if (has_duplicate($item, $check_stack)) {
unset($stack[$key]);
}
$check_stack[] = $item;
}
return $stack;
}
Now you can use it simple as followed
unique_string(3 /* amount of dept */, [1,2,3] /* keys */);
Ps the code is based for PHP5.4+, to convert to lower you need to change the [] to array() but I love the new syntax so sorry :P

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);
}

Categories