PHP merge (single depth) recursive based on first index (date) - php

I have searched alot, but can't find the exact similair question. The built-in function in PHP don't do what I'm looking for. Note: array_merge_recursively(...) came close, but didn't keep the indexes.
I have for example to arrays in PHP (json visualization):
[
["1-6-13", 10],
["30-6-13", 13],
["20-9-13", 28]
]
and:
[
["18-2-13", 7],
["30-6-13", 9]
]
And I want to merge those two, based on their date. However not every date matches the other one, it is important that the value of array 1 will be on index 1, and the value of array 2 must be appended to index 2. The values which don't exists must be for instance null.
So the desired result would look like:
[
["18-2-13", null, 7],
["1-6-13", 10, null],
["30-6-13", 13, 9],
["20-9-13", 28, null]
]
I hope someone can help me out!

You can first get a collection of all dates like this:
$dates = array_merge(array_map('reset', $data1), array_map('reset', $data2));
This collection includes duplicates, but we don't really care (we could remove them with array_unique if that was a problem).
Side note: if running PHP >= 5.5 it would be preferable to do array_column($arr, 0) instead of array_map('reset', $arr).
Then project that into an array of arrays, using the dates as keys:
$result = array_fill_keys($dates, array(null, null, null));
And fill it up with data:
foreach($data1 as $pair) {
list($date, $num) = $pair;
$result[$date][0] = $date;
$result[$date][1] = $num;
}
foreach($data2 as $pair) {
list($date, $num) = $pair;
$result[$date][0] = $date;
$result[$date][2] = $num;
}
Of course the above can easily be generalized for an arbitrary number of arrays with a loop.
Finally, if you want the $result to be numerically indexed then lose the dates that were used as keys:
$result = array_values($result);
See it in action.

You could do this. The benefit is that you only loop through the arrays once:
$dates = array();
foreach( $array1 as $val ) {
$dates[$val[0]] = array( $val[0], $val[1], null );
}
foreach( $array2 as $val) {
$dates[$val[0]][0] = $val[0];
$dates[$val[0]][1] = isset( $dates[$val[0]][1] ) ? $dates[$val[0]][1] : null;
$dates[$val[0]][2] = $val[1];
}

Related

How can the elements of a PHP array with matching characteristics be processed into a series of aggregates?

Essentially I want to perform a kind of grouping against a PHP array, and then squash the elements in each group into a single aggregate value.
Specifically, I have a PHP array with the following form:
[
[date, int],
[date, int],
[date, int],
...
[date, int]
]
and I want to process this so that I end up with an array of arrays each containing a unique date and the sum of the ints that went with that date in the original array, e.g.:
[
['2017-01-01', 1],
['2017-01-01', 1],
['2018-01-01', 1],
['2019-01-01', 1],
['2019-01-01', -1],
['2020-01-01', -1],
['2020-01-01', 1],
['2020-01-01', -1]
]
should result in this:
[
['2017-01-01', 2],
['2018-01-01', 1],
['2019-01-01', 0],
['2020-01-01', -1]
]
The original array is in date order.
What's an efficient way of achieving this?
I would do it like this, assuming your input is in $arr:
foreach ($arr as list($date, $num)) {
$result[$date] = isset($result[$date]) ? $result[$date]+$num : $num;
}
Now $result will be almost as you asked for. It will in fact produce key/value pairs, where the key is the date, and the value the number. If you really prefer the nested array format, then:
foreach ($arr as list($date, $num)) {
if (!isset($result[$date])) $result[$date] = [$date, 0];
$result[$date][1] += $num;
}
This will still have the dates as keys, but they will have the pairs as values.
If you want a sequential array instead of dates as keys, then add:
$result = array_values($result);
Perhaps something like this (assuming your array of arrays above is called $source):
$destination = array();
foreach ( $source as $date_value ) {
if ( isset( $destination[ $date_value[0] )) {
$destination[ $date_value[0] ] += $date_value[1];
} else {
$destination[ $date_value[0] ] = $date_value[1];
}
}
This will create an array with the dates as keys and the sums as the values of those keys. Not specifically what you were asking for. To format it otherwise:
$new_destination = array();
foreach ( $destination as $key => value ) {
$new_destination[] = array( $key, $value );
}
Now $new_destination should be an array that matches your desired output. This can be done when creating the first array, but will get very messy. So even though it creates two loops, it prevents the need for nested loops, and is likely more efficient than doing it in one block.
Note that I haven't tested this, so it may contain syntax errors or need to be modified slightly to get what you need, but it should put you on the right track.

maximum value in php array for each key value

I want to find out the maximum value of the each key in the array
$arr1= array(
0=>array(1,2,3),
1=>array(2,4,6),
2=>array(25,4,5));
}
I want to find the maximum value for each value for the corresponding key
The final output shuld be
$max_array = array(25,4,6);
with the below code i get error max(): When only one parameter is given, it must be an array in
foreach ($res_arr as $k=>$subArray) {
foreach ($subArray as $id=>$value) {
$spanmaxArray[$id] = max($value);
}
}
First, rotate array to makes columns as lines
$arr1= array(
0=>array(1,2,3),
1=>array(2,4,6),
2=>array(25,4,5)
);
$arr2 = array();
foreach($arr1 as $subArray) {
foreach($subArray as $k=>$v) {
$arr2[$k][] = $v;
}
}
Then, use array_map as #HamZa did
$max_array = array_map('max', $arr2);
print_r($max_array);
foreach ($res_arr as $k=>$subArray){
$max = 0;
foreach ($subArray as $id=>$value){
if ($value>$max){
$max = $value;
}
}
$spanmaxArray[$k] = $max;
}
Try this.
Definitely the shortest version:
$max_array = array_map('max', $arr1);
array_map() takes each element array of $arr1 and applies max() to it.
Edited after seeing that max in each column position is desired:
$sorted = array();
for ($i = 0; $i <=2; $i++) {
$sorted[$i] = array_column($input, $i);
}
This loops over the array and applies array_column() to each (requires PHP 5.5).
So:
$sorted = array();
for ($i = 0; $i <=2; $i++) {
$sorted[$i] = array_column($arr1, $i);
}
$max_array = array_map('max', $sorted);
working version
You only need one loop, because the max function takes an array as a parameter. But the answer you are giving is wrong, unless I've misunderstood the problem.
I think what you're after is:
$max_array = array_map('max', $arr1);
All of the earlier answers are working too hard.
Use array_map() as the iterating function.
Unpack your multidimensional array with the "spread/splat operator" (...); in other words, convert your single array containing three subarrays into three separate arrays.
Call max() on the three elements as they are synchronously iterated by array_map().
Code: (Demo)
$arr1 = [
[1, 2, 3],
[2, 4, 6],
[25, 4, 5],
];
var_export(array_map('max', ...$arr1));
The above is a more versatile and concise version of the following one-liner which has the same effect on the sample array:
var_export(array_map('max', $arr1[0], $arr1[1], $arr1[2]));
Output:
array (
0 => 25,
1 => 4,
2 => 6,
)
*A note for researchers: if processing a multidimensional array that has associative first level keys, you will need to index the first level so that the splat operator doesn't choke on the keys.
...array_values($array)

Convert a numeric array of indexes to an ordered array of elements from an associative array of objects indexed by those indexes

Note: I should be clear my desire is to do this functionally, in one statement. I can do this easily with loops but that's not what I'm interested in.
I have two arrays: a numeric array of indexes, A, and an associative array, B, of objects O indexed by the elements of array A.
I want to produce an array of O in the order of the elements of A--in other words, map the indexes into real objects, based on the associative array B.
For example:
A = [ 3, 4, 2, 1 ];
B = [ 1=>"one", 2=>"two", 3=>"three", 4=>"four" ]
I want:
[ "three", "four", "two", "one" ]
Also, incidentally I'm also curious to learn what this concept is called. It's kind of like mapping, but specifically involves indexes into another array, as opposed to a function.
$A = array(3, 4, 2, 1);
$B = array(1=>"one", 2=>"two", 3=>"three", 4=>"four");
foreach($A as $i) $R[] = $B[$i];
var_dump($R);
I am just adding a little bit, if anyone is still interested in using "array_map".
<?php
$A = array( 3, 4, 2, 1);
$B = array( 1=>"one", 2=>"two", 3=>"three", 4=>"four" );
print_r($A);echo '<br/>';
print_r($B);echo '<br/>';
function matchAtoB($sourceA, $sourceB)
{
global $B;
return $B[$sourceA];
}
$O = array_map("matchAtoB", $A, $B);
print_r($O);
?>
So the function can only receive an element of each array at a time (not the whole array) and it will loop/repeat itself automatically until all elements in the array are processed.
Cheers,
You don't need a loop, you can access the elements right away:
$three = $b[$a[0]]; // "three"
$four = $b[$a[1]]; // "four"
$two = $b[$a[2]]; // "two"
$one = $b[$a[3]]; // "one"
You could see this as a 'lazy' or 'just in time' way of accomplishing the same goal, but without the ahead cost of indexing the hash map.
If you want the array explicitly, without the additional lookup, you will need a loop.
I'm not sure if this has a name but the combination of a 'datastore' or 'hash map' combined with an ordered array of keys is not an uncommon one.
$order = array(3, 4, 2, 1);
$input = array(1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four');
Using array_map() (PHP 5.3 >)
$output = array_map(function($v) use($input) {
return $input[$v];
}, $order);
However, this is essentially the same as doing the following:
$output = array();
foreach ($order as $o) {
$output[] = $input[$o];
}
I can't honestly see a shorter way of doing this.
NullUserException posted the answer in a comment:
array_map(function ($v) use ($b) { return $b[$v]; }, $a);

Filter array by its key using the values of another array and order results by second array [duplicate]

This question already has answers here:
How to filter an associative array comparing keys with values in an indexed array?
(12 answers)
Closed 4 months ago.
Here is data
$array = array(
'random' => 1,
'pewpew' => 2,
'temp' => 5,
'xoxo' => 3,
'qweqweqe' => 4,
);
$fields = array('random', 'xoxo', 'temp');
I need to get in result:
$result = array(
'random' => 1,
'xoxo' => 3,
'temp' => 5,
);
I mean keys presence/order from $fields apply to $array.
The question is:
Can I perform this transformation using only array_ functions? (I don't wanna use iteations)
If yes: can you link me function that I need?
(sorry for spelling mistakes)
upd.
PHP 5.2
$result=array_intersect_key($array ,array_flip($fields) );
// little trick required here...
$fields = array('random' => 0, 'xoxo' => 0, 'temp' => 0);
$result = array_intersect_key($array,$fields);
I am always interested in these types of question, where it's about efficient code (both in code-usage and speed). That said, I tried and benchmarked several different methods, and nothing was as efficient and as a simple foreach!
I tried all the solutions posted, and my own array_walk and basic foreach. I ran several test, both with the arrays and fields posted by Miraage, and some with much larger arrays. I also noted anything odd with the results, such as additional values if $fields had values not in $array.
I've ordered it by speed.
FOREACH: 0.01245 sec
$result = array();
foreach ($fields as $k)
{
if (isset($array[$k]))
$result[$k] = $array[$k];
}
ARRAY_DIFF_KEY: 0.01471 sec (unexpected results: additional values)
$result = array_diff_key($fields, $array);
FOREACH (function): 0.02449 sec
function array_filter_by_key($array, $fields)
{
$result = array();
foreach ($fields as $k)
{
if (isset($array[$k]))
$result[$k] = $array[$k];
}
return $result;
}
ARRAY_WALK (by reference): 0.09123 sec
function array_walk_filter_by_key($item, $key, $vars)
{
if (isset($vars[1][$item]))
$vars[0][$item] = $vars[1][$item];
}
$result = array();
array_walk($fields, 'array_walk_filter_by_key', array(&$result, &$array));
LIST/EACH: 0.12456 sec
$result = array();
reset($fields);
while (list($key, $value) = each($fields))
{
if (isset($array[$value]))
$result[$value] = $array[$value];
}
ARRAY_INTERSECT_KEY: 0.27264 sec (incorrect order)
$result = array_intersect_key($array, array_flip($fields));
ARRAY_REPLACE (array_intersect_key second): 0.29409 sec (unexpected results: additional values)
$result = array_replace(
array_fill_keys($fields, false),
array_intersect_key($array, array_flip($fields))
);
ARRAY_REPLACE (two array_intersect_key): 0.33311 sec
$flip = array_flip($fields);
$result = array_replace(
array_intersect_key($flip, $array),
array_intersect_key($array, $flip)
);
ARRAY_WALK (set null): 3.35929 sec (unexpected results: additional values)
function array_walk_filter_by_key_null(&$item, $key, $array)
{
if (isset($array[$key]))
$item = $array[$key];
else
$item = null;
}
$result = array_flip($fields);
array_walk($result, 'array_walk_filter_by_key_null', $array);
ARRAY_REPLACE (array_intersect_key first): 11.11044 sec
$flip = array_flip($fields);
$result = array_intersect_key(
array_replace($flip, $array),
array_intersect_key($flip, $array)
);
ARRAY_MERGE: 14.11296 sec (unexpected results: additional values)
$result = array_splice(
array_merge(array_flip($fields), $array),
0,
count($fields)
);
So there it is. Can't beat a DIY. Sometimes the perception is that the built-in functions are faster, but it's not always the case. Compilers are pretty good these days.
This code preserves order and works in PHP 5.2 as required
One line:
$result = array_merge( array_flip($fields),
array_intersect_key(
$array,
array_flip( $fields )
)
);
For performance:
$flip = array_flip($fields);
$result = array_merge( $flip
array_intersect_key(
$array,
$flip
)
);
I believe this works as you require.
$result = array_splice(array_merge(array_flip($fields) , $array) , 0 , count($fields));
Just to solve the puzzle:
$result = array_replace(
array_intersect_key(array_flip($fields), $array),
array_intersect_key($array, array_flip($fields))
);
The first array_intersect creates the list of fields in good order, the other one overcomes array_replace functionality to create the the keys that do not exist in the first array.
Meets your requirements. But I wouldn't use it in any production code, as this may be pretty heavy (I didn't benchmark though, so it's just a gut feeling). An array_walk solution seems lighter.
If you want to keep key order from $fields, you could try this: (if key not exists in $array, then the value for that key will be null.)
$result = array_flip($fields);
array_walk($result, function(&$item, $key, $array) {
$item = isset($array[$key]) ? $array[$key] : null;
}, $array);
var_dump($result);
I will consider that you can't change the input (neither $array or $fields).
This can be achieved if you have an array that uses as keys the values from $fields. After that you can merge the two (with $fields being the first parameter) and remove the extra elements.
Considering that you can't change $fields, I will create it:
$tmp = array_combine($fields, range(1, count($fields)));
$result = array_merge($tmp, $array);
$result = array_splice($result, 0, count($fields));
The full working sample (with some comments) can be found here: http://codepad.org/H0CDN7ok
My attempt:
array_replace(
array_fill_keys($fields, false),
array_intersect_key($array, # Keys in array, without order
array_flip($fields))));
It was easy to get the keys in the same order as $array. Then to get them in the proper order, I built an array with keys equal to $fields. Array_replace did the rest.
The solution is "stable" in that missing keys in $array will be replaced with FALSE and can thus be filtered out if need be.
array_flip walks the field array of size N once, array_intersect walks M time a N sized array, array_fill_keys costs N and the final array_replace is, I believe, N^2.
So total cost is M*N^5.
Walking the smallest array and picking the values from the large one is O(M^2*N^2), so for large values of N I suspect that the PHP solution might prove faster. This doesn't enter keys which are not in the data array.
$answer = array();
foreach($fields as $fld) // N-sized cycle
if (isset($array[$fld])) // Cost M
$answer[$fld] = // Assignment is N*1/2
$array[$fld]; // Getting value is another M
(some time and much puzzlement later)
I ran a check and I think I must be making some silly mistake, for the times I'm getting are totally nonsensical. Admittedly I'm using a very short $fields array, so I'd expect skewed results, but not this skewed. Unless $answer[$fld] is calculated with some REALLY clever hash trick, whereby the true cost of the interpreted solution is not O(M^2*N^2) but O(K*N^2) with K small.
If anyone wants to play with times or tell me what stupid mistake I might have made, here's the benchmark.
I was of two minds about posting this, because the other obvious explanation is that I made a ridiculous, silly mistake somewhere and I'm going to end up with egg on my face, but, oh, what the hell.
$array = array(
'random' => 1,
'pewpew' => 2,
'temp' => 5,
'xoxo' => 3,
'qweqweqe' => 4,
);
$fields = array('random', 'xoxo', 'temp');
// Let's not print anything just yet, maybe that's what screwing the timer?
$results = '';
$duh = 0;
for ($cycle = 0; $cycle < 10; $cycle++)
{
// Add some more elements to $array.
for ($i = 0; $i < 10000; $i++)
{
$k = uniqid();
$array[$k] = 42;
}
$start = explode(' ', microtime());
// WTF? Do more cycles to average the timing.
for ($j = 0; $j < 10; $j++)
{
// 0 or 1 to switch
if (1)
{
// INTERPRETED ANSWER
$answer = array();
foreach($fields as $fld) // N-sized cycle
if (isset($array[$fld])) // Cost M
$answer[$fld] = // Assignment is N*1/2
$array[$fld]; // Getting value is another M
} else {
// FUNCTION ANSWER
$answer = array_replace(
array_fill_keys($fields, false),
// array_combine($fields, $fields),
array_intersect_key($array, # Keys in array, without order
array_flip($fields)));
}
// USE $answer so to avoid premature optimization?
// You can't be that clever.
$duh += strlen(serialize($answer));
}
$stop = explode(' ', microtime());
// An error in timing? Check via a stupid roundabout.
$int = $stop[1]-$start[1]+1;
$int += ($stop[0]-$start[0]);
$int -= 1;
$elapsed = number_format($int * 1000000, 2);
$results .= "".(5000*$cycle)." = $elapsed us.\n";
}
// I need to get in result:
$wanted = array(
'random' => 1,
'xoxo' => 3,
'temp' => 5,
);
// DID we get the right answer?
print "Wanted:\n"; print_r($wanted);
print "Gotten:\n"; print_r($answer);
print "Results: $results\n$duh -- count of array is " . count($array);
// And yet I have always the same realtime, name of a dog, how can that be?
// I must be doing something REALLY REALLY wrong somewhere.
Easy Way:
$array = array(
'random' => 1,
'pewpew' => 2,
'temp' => 5,
'xoxo' => 3,
'qweqweqe' => 4,
);
$fields = array('random', 'xoxo', 'temp');
$output = array();
foreach ($fields as $value)
if(isset($array[$value]))
$output[$value]=$array[$value];
This is a solution that also handles the case when some $fields are not present as keys in $array:
$flip = array_flip($fields);
$result = array_intersect_key(array_replace($flip, $array), array_intersect_key($flip, $array));
If all $fields are known to be present as keys in $array there is this simpler solution:
$flip = array_flip($fields);
$result = array_intersect_key(array_replace($flip, $array), $flip);
which can be written as a one-liner like this:
$result = array_intersect_key(array_replace($flip=array_flip($fields), $array), $flip);
If some $fields are not keys of $array, but $array contains counts, so that it makes sense to return a 0 count for missing keys, we can replace flip() with array_fill_keys($fields, 0):
$result = array_intersect_key(array_replace($fill=array_fill_keys($fields, 0), $array), $fill);
to which we can apply an array_filter() to filter out the 0s again, if needed. By replacing 0 with false or null we can flag and handle the absence of a key in $array when the values are not counts.
The sad thing is that these solutions, like all others on this page, have to work through all keys of the $array, while any explicit loop would be on $fields. For the time being, it seems that when count($array) is much bigger than count($fields) there is no array-function-based solution as fast as an explicit loop (since they would explicitly construct the result in callback functions, I consider array_walk() and array_reduce() to be explicit loops here).
The problem is that none of the available array_ functions breaks the association between keys and values, and since we would like to loop on $fields, or rather a flipped array thereof, also to conserve its sort order, while keeping the values of $array, we are out of luck.
try:
$result=array();
reset($fields);
while(list($key,$value)=each($fields))
{
if(isset($array[$value])) $result[$value]=$array[$value];
}
This will work and preserve order for you:
$fields = array_flip($fields);
array_merge($fields,array_intersect_key($array, $fields));
$fields = array_keys($fields);
note, you could just call array_flip twice, but the above seemed a bit "cleaner".
The PHP function is called array_diff_key.
Sample code:
$array = array(
'random' => 1,
'pewpew' => 2,
'temp' => 5,
'xoxo' => 3,
'qweqweqe' => 4
);
$fields = array('random', 'xoxo', 'temp');
$result = array_diff_key($fields, $array);
This will produce the desired result.
Demo: http://shaquin.tk/experiments/array1.php
EDIT: If $fields can contain a value that is not a key in $array, then use this code:
$result = array_diff_key($fields, $array);
$result = array_intersect_key(array_flip($fields), $array);
$result = array_flip(array_diff(array_keys($result), $array));
$result = array_replace($result, $array);
$result = array_flip(array_intersect(array_flip($result), $fields));
It may be possible to optimize this a bit, but it works!
Note: I cannot link to an example, since my (hosted) site doesn't have >= PHP 5.3, however, I can link to a similar one: http://shaquin.tk/experiments/array2.php.

Filter array to retain rows with smallest element count and unique first and last elements

I want to remove rows from my array so that my result is an array that contains rows with unique first and last elements. If two (or more) rows have the same first and last value, I want to preserve the row with the lowest element count.
Say I have the following array:
$var = [
[1, 2, 3],
[1, 3],
[1, 2, 4, 3],
[1, 3, 4]
];
What I want is to remove all arrays from $var that have the first and last element the same as another array from $var but have more elements.
Because the first three rows all start with 1 and end with 3, only the second row containing [1, 3] should be kept.
The fourth row ([1, 3, 4]) uniquely starts with 1 and ends with 4, so it should also be kept.
The output should be:
[
[1, 3],
[1, 3, 4]
]
I am looking for the most efficient way of doing this, both in terms of memory and time. $var may have up to 100 arrays, and each individual array may have up to 10 elements in it. I thought of using some kind of comparison between all two elements (for(i=0;...) for(j=i+1;...) complexCompareFunction();), but I believe this isn't very efficient.
use current and end
$all = array();
foreach ($var as $idx=>$arr):
$first = current($arr);
$last = end($arr);
$size = count($arr);
$key = $first.'.'.$last;
if (isset($all[$key])):
if ($size > $all[$key]):
unset($var[$idx]);
else:
$all[$key] = $size;
endif;
else:
$all[$key] = $size;
endif;
endforeach;
ops ... you can iterate (again) at the end to ensure the already reduced sized array can be further removed
In general, yes, you are too worried about efficiency (as you wondered in another comment). Though PHP is not the most blisteringly-fast language, I would suggest building the most straightforward solution, and only worry about optimizing it or streamlining it if there is a noticeable issue with the end result.
Here is what I would do, off the top of my head. It is based off of ajreal's answer but hopefully will be easier to follow, and catch some edge cases which that answer missed:
// Assume $var is the array specified in your question
function removeRedundantRoutes( $var ){
// This line sorts $var by the length of each route
usort( $var, function( $x, $y ){ return count( $x ) - count( $y ); } );
// Create an empty array to store the result in
$results = array();
// Check each member of $var
foreach( $var as $route ){
$first = $route[0];
$last = $route[ count( $route ) - 1 ];
if( !array_key_exists( "$first-$last", $results ) ){
// If we have not seen a route with this pair of endpoints already,
// it must be the shortest such route, so place it in the results array
$results[ "$first-$last" ] = $route;
}
}
// Strictly speaking this call to array_values is unnecessary, but
// it would eliminate the unusual indexes from the result array
return array_values( $results );
}
Here is how I would group by a temporary key (formed by creating a delimited string from the first and last value in a given row) and conditionally push qualifying data into a result array. When the loop finishes, extract the second column from the result array to produce an indexed array containing only the smallest of qualifying rows. No pre-sorting required.
Code: (Demo)
$result = [];
foreach ($array as $row) {
$cache = [count($row), $row];
array_splice($row, 1, -1);
$key = implode('-', $row);
if (!isset($result[$key]) || $cache[0] < $result[$key][0]) {
$result[$key] = $cache;
}
}
var_export(array_column($result, 1));
Alternative Code: (Demo)
$result = [];
foreach ($array as $row) {
$count = count($row);
$key = $row[0] . '-' . $row[array_key_last($row)]; // or array_pop($row)
if (!isset($result[$key]) || $count < $result[$key][0]) {
$result[$key] = [$count, $row];
}
}
var_export(array_column($result, 1));
Output:
array (
0 =>
array (
0 => 1,
1 => 3,
),
1 =>
array (
0 => 1,
1 => 3,
2 => 4,
),
)

Categories