How to determine max length of a certain array column? - php

The array looks like this:
array(
array(5, true, 'Foo'),
array(8, true, 'Bar'),
array(8, true, 'FooBar'),
)
Can I determine the longest string length of the 3rd column, without having to iterate over the array?
In my example the longest string would be "FooBar" - 6 chars.
If the inner array had only the string element, I could do max(array_map('strlen', $arr)), but it has 3 items...

Add array_map('array_pop', $arr) to the mix:
<?php
$arr = array(
array(5, true, 'Foo'),
array(8, true, 'Bar'),
array(8, true, 'FooBarss')
);
print_r(max(array_map('strlen', array_map('array_pop', $arr))));
?>
http://codepad.org/tRzHoy7Z
Gives 8 (after I added the two ss to check). array_pop() takes the last array element off and returns it, use array_shift() to get the first.

First I'm pretty sure that the max function iterates over the whole array. But if you're fine with using it then you can define your own comparison function and pass it.
function cmp($a, $b) {
if (strlen($a[2]) == strlen($b[2])))
return 0;
return (strlen($a[2]) < strlen($b[2])) ? -1 : 1;
}
max(array_map('cmp', $arr))

Simply sorting the array of arrays then picking the first positioned row involves extra/unnecessary algorithmic sorting (specifically for non-longest rows) and also may give misleading results if there is a tie for longest.
I recommend accommodating the possibility of multiple longest strings and only traversing the input array one time.
Code: (Demo)
$array = [
[5, true, 'FooBar'],
[8, true, 'Bar'],
[18, true, 'IsTied'],
];
$result = [];
$longest = 0;
foreach ($array as $row) {
$len = strlen($row[2]);
if ($len >= $longest) {
$longest = $len;
$result[$len][] = $row;
}
}
var_export($result[$longest] ?? []);
Output:
array (
0 =>
array (
0 => 5,
1 => true,
2 => 'FooBar',
),
1 =>
array (
0 => 18,
1 => true,
2 => 'IsTied',
),
)
That said, if you just want to see the longest length and don't care where it came from, then you can use this:
var_export(max(array_map('strlen', array_column($array, 2))));

Related

how to fix array_udiff while having strings inside array [duplicate]

I have an array containing rows of associative data.
$array1 = array(
array('ITEM' => 1),
array('ITEM' => 2),
array('ITEM' => 3),
);
I have a second array, also containing rows of associative data, that I would like to filter using the first array.
$array2 = array(
array('ITEM' => 2),
array('ITEM' => 3),
array('ITEM' => 1),
array('ITEM' => 4),
);
This feels like a job for array_diff(), but how can I compare the rows exclusively on the deeper ITEM values?
How can I filter the second array and get the following result?
array(3 => array('ITEM' => 4))
You can define a custom comparison function using array_udiff().
function udiffCompare($a, $b)
{
return $a['ITEM'] - $b['ITEM'];
}
$arrdiff = array_udiff($arr2, $arr1, 'udiffCompare');
print_r($arrdiff);
Output:
Array
(
[3] => Array
(
[ITEM] => 4
)
)
This uses and preserves the arrays' existing structure, which I assume you want.
I would probably iterate through the original arrays and make them 1-dimensional... something like
foreach($array1 as $aV){
$aTmp1[] = $aV['ITEM'];
}
foreach($array2 as $aV){
$aTmp2[] = $aV['ITEM'];
}
$new_array = array_diff($aTmp1,$aTmp2);
Another fun approach with a json_encode trick (can be usefull if you need to "raw" compare some complex values in the first level array) :
// Compare all values by a json_encode
$diff = array_diff(array_map('json_encode', $array1), array_map('json_encode', $array2));
// Json decode the result
$diff = array_map('json_decode', $diff);
A couple of solutions using array_filter that are less performant than the array_udiff solution for large arrays, but which are a little more straightforward and more flexible:
$array1 = [
['ITEM' => 1],
['ITEM' => 2],
['ITEM' => 3],
];
$array2 = [
['ITEM' => 2],
['ITEM' => 3],
['ITEM' => 1],
['ITEM' => 4],
];
$arrayDiff = array_filter($array2, function ($element) use ($array1) {
return !in_array($element, $array1);
});
// OR
$arrayDiff = array_filter($array2, function ($array2Element) use ($array1) {
foreach ($array1 as $array1Element) {
if ($array1Element['ITEM'] == $array2Element['ITEM']) {
return false;
}
}
return true;
});
As always with array_filter, note that array_filter preserves the keys of the original array, so if you want $arrayDiff to be zero-indexed, do $arrayDiff = array_values($arrayDiff); after the array_filter call.
you can use below code to get difference
$a1 = Array(
[0] => Array(
[ITEM] => 1
)
[1] => Array(
[ITEM] => 2
)
[2] => Array(
[ITEM] => 3
)
);
$a2 = Array(
[0] => Array(
[ITEM] => 2
)
[1] => Array(
[ITEM] => 3
)
[2] => Array(
[ITEM] => 1
)
[3] => Array(
[ITEM] => 4
));
array_diff(array_column($a1, 'ITEM'), array_column($a2, 'ITEM'));
Having the same problem but my multidimensional array has various keys unlike your "ITEM" consistently in every array.
Solved it with: $result = array_diff_assoc($array2, $array1);
Reference: PHP: array_diff_assoc
Another solution
if( json_encode($array1) == json_encode($array2) ){
...
}
Trust that the maintainers of PHP have optimized array_udiff() to outperform all other techniques which could do the same.
With respect to your scenario, you are seeking a filtering array_diff() that evaluates data within the first level's "value" (the row of data). Within the custom function, the specific column must be isolated for comparison. For a list of all native array_diff() function variations, see this answer.
To use the first array to filter the second array (and output the retained data from the second array), you must write $array2 as the first parameter and $array1 as the second parameter.
array_diff() and array_intersect() functions that leverage (contain u in their function name) expect an integer as their return value. That value is used to preliminary sort the data before actually performing the evaluations -- this is a performance optimization. There may be scenarios where if you only return 0 or 1 (not a three-way comparison), then the results may be unexpected. To ensure a stable result, always provide a comparison function that can return a negative, a positive, and a zero integer.
When comparing integer values, subtraction ($a - $b) will give reliable return values. For greater utility when comparing float values or non-numeric data, you can use the spaceship operator when your PHP version makes it available.
Codes: (Demo)
PHP7.4+ (arrow functions)
var_export(
array_udiff($array2, $array1, fn($a, $b) => $a['ITEM'] <=> $b['ITEM'])
);
PHP7+ (spaceship operator)
var_export(
array_udiff(
$array2,
$array1,
function($a, $b) {
return $a['ITEM'] <=> $b['ITEM'];
}
)
);
PHP5.3+ (anonymous functions)
var_export(
array_udiff(
$array2,
$array1,
function($a, $b) {
return $a['ITEM'] === $b['ITEM']
? 0
: ($a['ITEM'] > $b['ITEM'] ? 1 : -1);
}
)
);
Output for all version above:
array (
3 =>
array (
'ITEM' => 4,
),
)
When working with object arrays, the technique is the same; only the syntax to access a property is different from accessing an array element ($a['ITEM'] would be $a->ITEM).
For scenarios where the element being isolated from one array does not exist in the other array, you will need to coalesce both $a and $b data to the opposite fallback column because the data from the first array and the second arrays will be represented in both arguments of the callback.
Code: (Demo)
$array1 = array(
array('ITEM' => 1),
array('ITEM' => 2),
array('ITEM' => 3),
);
$array2 = array(
array('ITEMID' => 2),
array('ITEMID' => 3),
array('ITEMID' => 1),
array('ITEMID' => 4),
);
// PHP7.4+ (arrow functions)
var_export(
array_udiff(
$array2,
$array1,
fn($a, $b) => ($a['ITEM'] ?? $a['ITEMID']) <=> ($b['ITEM'] ?? $b['ITEMID'])
)
);
Compares array1 against one or more other arrays and returns the values in array1 that are not present in any of the other arrays.
//Enter your code here, enjoy!
$array1 = array("a" => "green", "red", "blue");
$array2 = array("b" => "green", "yellow", "red");
$result = array_diff($array1, $array2);
print_r($result);

array_slice for associative array

I have an array with indexes as timestamps.
I tried array_slice to get all values between a time range but it doesn't seem to work.
$data = array_slice(["1549440811" => 1, "1549448226" => 2, "1551108588" => 3 ], 0, 1549460338);
I should get the $data as ["1549440811" => 1, "1549448226" => 2] but it doesn't work that way.
To get the right data I have to use
$data = array_slice(["1549440811" => 1, "1549448226" => 2, "1551108588" => 3 ], 0, 2);
But the problem is the records can have random timestamps and no. of records. So I am unable to figure out the offset which is 2 in this case.
I know the code below with a few changes might work for small range but not for my timestamps as $myrange would have a lot of data.
$myrange = range(0,1549460338);
$output = array_intersect(["1549440811" => 1, "1549448226" => 2, "1551108588" => 3 ] , $myrange );
I am avoiding looping through the array as the array has a lot of data. Also I have a lot of timestamps to check. This code is a simplified logic of a bigger code with records from database indexed with timestamps.
Is there any other way I could get the desired data?
Simple for-loop should do:
$arr = ["1549440811" => 1, "1549448226" => 2, "1551108588" => 3 ];
$range = "1549460338";
foreach($arr as $k => $v) {
if ($range > $k)
break;
$newArr[$k] = $v;
}
You can also use array_filter (doc):
$filtered = array_filter( $arr,
function ($key) use ($range) {return $range > $key;},
ARRAY_FILTER_USE_KEY
);
Example: 3v4l
Edit:
Fastest way (consider your array is sorted) is to extract the keys with $keys = array_keys($arr); and then search for the $range using binary search (O(log(n))) -> then use array_slice with that index.

Average each associative pair found in a 2d array

Consider this collection below:
$collection = [
[1 => 10.0, 2 => 20.0, 3 => 50.0, 4 => 80.0, 5 => 100.0],
[3 => 20.0, 5 => 20.0, 6 => 100.0, 7 => 10.0],
[1 => 30.0, 3 => 30.0, 5 => 10.0, 8 => 10.0]
];
Consider this theorical output based on the intersection of the Arrays contained into $collection, considering their array keys with respective values based on the average of the single values:
$output = Array ( 3 => 33.3333, 5 => 43.3333 );
Can this problem be resolved with a native PHP function like array_intersect_* in an elegant way?
If not, can you suggest me an elegant solution that doesn't necessarily need an outer ugly foreach?
Keep in mind that the number of arrays that need to be intersected is not fixed. It can be 2 input arrays as it can be 1000 input arrays.
Keys will be integers at all times, and Values will be floats or integers at all times.
In other words:
$collection = [
$arr1 = [ ... ];
$arr2 = [ ... ];
$arr3 = [ ... ];
...
$arrn = [ ... ];
];
$output = [ intersected and weighted array based (on comparison) on keys from $arr1 to $arrn, and (on values) from the value averages ];
Count the input array once.
$n = count($collection);
Compute the intersection of all the sub-arrays by key.
$intersection = array_intersect_key(...$collection);
// PHP5: $intersection = call_user_func_array('array_intersect_key', $input);
Build your result by averaging the column from the input array for each key from the intersection.
$output = [];
foreach ($intersection as $key => $value) {
$output[$key] = array_sum(array_column($collection, $key)) / $n;
}
If you really want to completely avoid foreach you can use array_map instead.
$output = array_map(function($key) use ($collection, $n) {
return array_sum(array_column($collection, $key)) / $n;
}, array_keys($intersection));
But in my opinion, this just adds unnecessary complexity.
Note: The values in $intersection will be single values from the first sub-array, but they don't really matter; they're disregarded when generating the output. If it bothers you to have a useless $value variable in the foreach, then you can do foreach (array_keys($intersection) as $key) instead, but I opted for avoiding an unnecessary function call.
Can this problem be resolved with a native PHP function like array_intersect_* in an elegant way?
Well, elegance is in the eye of the developer. If functional-style programming with no new globally-scoped variables equals elegance, then I have something tasty for you. Can a native array_intersect_*() call be leveraged in this task? You bet!
There's a big lack in PHP native functions on intersects - #Maurizio
I disagree. PHP has a broad suite of powerful, optimized, native array_intersect*() and array_diff*() functions. I believe that too few developers are well-acquainted with them all. I've even build a comprehensive demonstration of the different array_diff*() functions (which can be easily inverted to array_intersect*() for educational purposes).
Now, onto your task. First, the code, then the explanation.
Code: (Demo)
var_export(
array_reduce(
array_keys(
array_intersect_ukey(
...array_merge($collection, [fn($a, $b) => $a <=> $b])
)
),
fn($result, $k) => $result + [$k => array_sum(array_column($collection, $k)) / count($collection)],
[]
)
);
The first subtask is to isolate the keys which are present in every row. array_intersect_ukey() is very likely the best qualified tool. The easy part is the custom function -- just write the two parameters with the spaceship in between. The hard part is setting up the variable number of leading input parameters followed by the closure. For this, temporarily merge the closure as an array element onto the collection variable, then spread the parameters into the the native function.
The payload produced by #1 is an array consisting of the associative elements from the first row where the keys were represented in all rows ([3 => 50.0, 5 => 100.0]). To prepare the data for the next step, the keys must be converted to values -- array_keys() is ideal because the float value are of no further use.
Although there is an equal number of elements going into and returning in the final "averaging step", the final result must be a flat associative array -- so array_map() will not suffice. Instead, array_reduce() is better suited. With the collection variable accessible thanks to PHP7.4's arrow function syntax, array_column() can isolate the full column of data then the averaging result pushed as an associative element into the result array.
I guess it could be done like this:
<?php
$intersecting_arrays = Array (
0 => Array ( 'one' => 10, 'two' => 20, 'three' => 50, 'four' => 80, 'five' => 100 ),
1 => Array ( 'three' => 20, 'five' => 20, 'six' => 100, 'seven' => 10 ),
2 => Array ( 'one' => 30, 'three' => 30, 'five' => 10, 'eight' => 10 )
);
$temp = $intersecting_arrays[0];
for($i = 1; $i < count($intersecting_arrays); $i++) {
$temp = array_intersect_key($temp, $intersecting_arrays[$i]);
}
$result = Array();
foreach(array_keys($temp) as $key => $val) {
$value = 0;
foreach($intersecting_arrays as $val1) {
$value+= $val1[$val];
}
$result[$key] = $value / count($intersecting_arrays);
}
print_r($temp);
print_r($result);
https://3v4l.org/j8o75
In this manner it doesn't depend on how much arrays you have.
Here you get the intersection of keys in all arrays and then count an average using collected keys.
Ok, with an unknown number of input arrays, I would definitively go with two nested foreach loops to combine them first - getting an unknown number into array_merge_recursive or similar is going to be difficult.
$input = [
0 => [ 'one' => 10, 'two' => 20, 'three' => 50, 'four' => 80, 'five' => 100],
1 => [ 'three' => 20, 'five' => 20, 'six' => 100, 'seven' => 10],
2 => [ 'one' => 30, 'three' => 30, 'five' => 10, 'eight' => 10]
];
$combined = [];
foreach($input as $array) {
foreach($array as $key => $value) {
$combined[$key][] = $value;
}
}
$averages = array_map(function($item) {
return array_sum($item)/count($item);
}, $combined);
var_dump($averages);
https://3v4l.org/hmtj5
Note that this solution doesn't need to check for array vs single integer in the array_map callback, because unlike array_merge_recursive, $combined[$key][] inside the loops sees to it that even the keys with just one value will have that value in an array.
EDIT:
but keep in mind that not all the keys are going to be taken into account
Ah, ok, so you want averages only for those keys that occurred more than once. That can easily be fixed by filtering the combined array before using array_map on it:
$combined = array_filter($combined, function($v, $k) {
return count($v) != 1;
}, ARRAY_FILTER_USE_BOTH );
Integrated into above solution: https://3v4l.org/dn5ro
EDIT #2
[Andreas' comment] I think "one" should not be in output since it is not in all three arrays.
Ah, I see ... couldn't tell that was the actually desired result even from the example :-) Then my filtering has to be modified a little bit again, and take the number of input arrays into account:
$combined = array_filter($combined, function($v, $k) use($input) {
return count($v) == count($input);
}, ARRAY_FILTER_USE_BOTH );
https://3v4l.org/9H086
You can merge the arrays to one and use array_sum and count() to get the average.
$arr1 = Array ( 'one' => 10, 'two' => 20, 'three' => 50, 'four' => 80, 'five' => 100 );
$arr2 = Array ( 'three' => 20, 'five' => 20, 'six' => 100, 'seven' => 10 );
$arr3 = Array ( 'one' => 30, 'three' => 30, 'five' => 10, 'eight' => 10 );
$array = array_merge_recursive($arr1,$arr2,$arr3);
$key= "two";
If(is_array($array[$key])){
$avg = array_sum($array[$key])/count($array[$key]);
}Else{
$avg = $array[$key];
}
Echo $avg;
https://3v4l.org/pa3PH
Edit to follow $collection array.
Try this then. Use array column to grab the correct key and use array_sum and count to get the average.
$collection = array(
Array ( 'one' => 10, 'two' => 20, 'three' => 50, 'four' => 80, 'five' => 100 ),
Array ( 'three' => 20, 'five' => 20, 'six' => 100, 'seven' => 10 ),
Array ( 'one' => 30, 'three' => 30, 'five' => 10, 'eight' => 10 ));
$key= "three";
$array = array_column($collection, $key);
If(count($array) != 1){
$avg = array_sum($array)/count($array);
}Else{
$avg = $array[0];
}
Echo $avg;
https://3v4l.org/QPsiS
Final edit.
Here I loop through the first subarray and use array column to find all the matching keys.
If the count of keys is the same as the count of collection the key exsists in all subarrays and should be "saved".
$collection = array(
Array ( 'one' => 10, 'two' => 20, 'three' => 50, 'four' => 80, 'five' => 100 ),
Array ( 'three' => 20, 'five' => 20, 'six' => 100, 'seven' => 10 ),
Array ( 'one' => 30, 'three' => 30, 'five' => 10, 'eight' => 10 ));
Foreach($collection[0] as $key => $val){
$array = array_column($collection, $key);
If(count($array) == count($collection)){
$avg[$key] = array_sum($array)/count($array);
}
}
Var_dump($avg);
https://3v4l.org/LfktH

Php associative array sort and get key with highest length [duplicate]

This question already has answers here:
Read the longest string from an array in PHP 5.3
(3 answers)
Closed 2 years ago.
Hey i have an associative array which has keys as String and values as Int.
So from that associative array i need to get the key which has the highest value and if multiple keys have the same value then i need the key with the highest length.
So whats the most efficient way of doing this?
example
array = (
'abc' => 10,
'def' => 8,
'fff' => 3,
'abcr' => 10,
'adsfefs' => 10
)
So for this i should get output as adsfefs
You can use array_keys and pass a second parameter to filter the returned keys to only include the max ones. You can then find the longest key by using array_reduce and a function that checks the lengths of the strings and throws out the shortest one, like so:
$array = array(
'abc' => 10,
'def' => 8,
'fff' => 3,
'abcr' => 10,
'adsfefs' => 10
);
$keys = array_keys($array, max($array));
$longestKey = array_reduce($keys, function ($a, $b) { return strlen($a) > strlen($b) ? $a : $b; });
var_dump($longestKey);
Be aware that if there are two or more strings that are the same length, it will return the last one.
Use arsort($data); to perform the initial sort of the value data, then use array_values to identify the largest value. With the largest value find which keys have this value and then iterate through and find the longest key value.
$data = array('bob' => 3, 'rob' => 4, 'nigel' => 6,'john' => 6, 'tony' => 6 );
// Sort array in descending value order
arsort($data);
// Get the first value (will be the largest value)
$maxValue = array_values($data)[0];
// Get an array of all keys with this value
$keys = array_keys($data, $maxValue);
// Find longest key
$maxKey = '';
foreach ($keys as $key) {
if (strlen($key) > strlen($maxKey)) {
$maxKey = $key;
}
}
// Output
print "Largest Value = $maxValue\n";
print "Largest Key with value of $maxValue is $maxKey\n";
Use arsort(array) to reverse sort them by value
foreach resulting array as long as value is not changing. Store each key in other array in format
[key] => strlen(key);
So arsort(other_array) and take first element. It should be the one with longest keys (but be aware that if there was many keys that has the same length you should add some other conditions to choose between them).
<?php
$sid = array(
'abc' => 10,
'def' => 8,
'fff' => 3,
'abcr' => 10,
'adsfefs' => 10
);
arsort($sid);
$prev_val = null;
$keys = Array();
foreach ($sid as $k=>$v) {
if ($k < $prev_val) break;
$keys[$k] = strlen($k);
$prev_val = $k;
}
arsort($keys);
echo "Longest key with highest value is: " . array_keys($keys)[0] . "\n";
?>

Merge two 2d arrays grouping on one column value and summing another column value within each group

Basically I need to take two arrays, merge them with unique values and sum one of columns. It makes more sense when written out below:
$a = [
['ID' => 1, 'Count' => 2],
];
$b = [
['ID' => 1, 'Count' => 4],
['ID' => 2, 'Count' => 3]
];
and I need the final product to be:
$a_plus_b = [
['ID' => 1, 'Count' => 6],
['ID' => 2, 'Count' => 3]
];
I have been playing with different variations of array_merge() and array_unique(), but I can't find an efficient way to do what I need. I know I can always do nested loops, but I was hoping for something easier. Any ideas?
This should do the trick
Note: This solution requires PHP >= 5.3. There is a PHP < 5.3 solution below.
$input = array($a, $b);
// add as many result arrays to $input as you want; e.g.,
// $input = array($a, $b, $c, $d);
$output = array_count_values(
call_user_func_array(
'array_merge',
array_map(
function($arr) {
return array_fill(0, $arr['Count'], $arr['ID']);
},
call_user_func_array(
'array_merge',
$input
)
)
)
);
print_r($output);
Output
Array
(
[1] => 6
[2] => 3
)
Note the array keys above are ID values. The array values are Count values.
If you're running PHP < 5.2 you won't be able to use the inline closure with array_fill. You have to define it as a separate function.
$input = array($a, $b);
function _fill($arr) {
return array_fill(0, $arr['Count'], $arr['ID']);
}
$output = array_count_values(
call_user_func_array(
'array_merge',
array_map(
'_fill',
call_user_func_array(
'array_merge',
$input
)
)
)
);
print_r($output);
From here, converting the output to your desired format is a trivial task.
Please don't over-engineer such a basic task. Iterate both array with a single loop and assign temporary keys using ID values. If encountering a respective ID key more than once, just add the new Count value to the stored value.
Code: (Demo)
$result = [];
foreach (array_merge($a, $b) as $row) {
if (!isset($result[$row['ID']])) {
$result[$row['ID']] = $row;
} else {
$result[$row['ID']]['Count'] += $row['Count'];
}
}
var_export(array_values($result));
Output:
array (
0 =>
array (
'ID' => 1,
'Count' => 6,
),
1 =>
array (
'ID' => 2,
'Count' => 3,
),
)
Functional programming can be used as well to achieve the same result -- array_reduce() is ideal since the number of elements in the output will be equal to or less than the number of elements in the input data.
Code: (Demo)
var_export(
array_values(
array_reduce(
array_merge($a, $b),
function ($result, $row) {
if (!isset($result[$row['ID']])) {
$result[$row['ID']] = $row;
} else {
$result[$row['ID']]['Count'] += $row['Count'];
}
return $result;
},
[]
)
)
);
If the ID values in the first array are guaranteed to be unique, you can avoid the array_merge() call by porting the $a array to the result array and assigning temporary keys using the ID values. (Demo)
$result = array_column($a, null, 'ID');
foreach ($b as $row) {
if (!isset($result[$row['ID']])) {
$result[$row['ID']] = $row;
} else {
$result[$row['ID']]['Count'] += $row['Count'];
}
}
var_export(array_values($result));

Categories