php array_multisort(): Array sizes are inconsistent - php

How can I use the php array_multisort to sort arrays like this? I can't find any examples with this type of arrays. I have tried different avenues but I keep getting the error array_multisort(): Array sizes are inconsistent.
$array= Array (
"Arnold" => Array ( "index" => 2, "games_played" => 1, "score" => 5 ),
"Chris" => Array ( "index" => 1, "games_played" => 1, "score" => 5 ),
"Mike" => Array ( "index" => 0, "games_played" => 2, "score" => 5 )
);

I think you're taking it the wrong way. array_multisort is not what would be a "sort by" in other languages (i.e: sort array elements by some properties), instead it sorts the first array, and reverberate that order to all following arrays. And in case of equality it checks the corresponding values of the second arrays, etc...
If you want to order your example by score (desc), then by game played, then by index (and then by name, but this should never happen since indexes are uniques) you should do:
$array= Array (
"Arnold" => Array ( "index" => 2, "games_played" => 1, "score" => 5 ),
"Chris" => Array ( "index" => 1, "games_played" => 1, "score" => 5 ),
"Mike" => Array ( "index" => 0, "games_played" => 2, "score" => 5 )
);
$names = [];
$indexes = [];
$games_played = [];
$scores = [];
foreach ($array as $name => $player) {
$names[] = $name;
$indexes[] = $player['index'];
$games_played[] = $player['games_played'];
$scores[] = $player['score'];
}
array_multisort(
$scores, SORT_DESC,
$games_played,
$indexes,
$names,
$array /* This line will sort the initial array as well */
);

Related

How to group an array of associative arrays and declare custom keys?

Can someone help me with converting a php array in a grouped format? I am trying to group them by id. I would like to have the following array converted:
$Arr1=Array
(
0 => Array
(
"id" => "4123",
"test_number" => "1",
"sat_total" => "1050"
),
1 => Array
(
"id" => "4123",
"test_number" => "2",
"sat_total" => "1130"
),
2 => Array
(
"id" => "4123",
"test_number" => "3",
"sat_total" => "1120"
),
3 => Array
(
"id" => "5555",
"test_number" => "1",
"sat_total" => "1130"
),
4 => Array
(
"id" => "5555",
"test_number" => "2",
"sat_total" => "1160"
)
);
into this:
$Arr2=Array
(
0 => Array
(
"id" => "4123",
"Score1" => "1050",
"Score2" => "1130",
"Score3" => "1120"
),
1 => Array
(
"id" => "5555",
"Score1" => "1130",
"Score2" => "1160"
)
);
I have tried a little bit, but can't seem to find how to make it work.
You only need to iterate your rows of data, determine if each row is the first occurring id value or not, then either declare the initial values, or add a variably keyed element to the group. When the loop finishes, call array_values() to reindex the array (remove the temporary keys).
Code: (Demo)
$Arr1=[
["id" => "4123", "test_number" => "1", "sat_total" => "1050"],
["id" => "4123", "test_number" => "2", "sat_total" => "1130"],
["id" => "4123", "test_number" => "3", "sat_total" => "1120"],
["id" => "5555", "test_number" => "1", "sat_total" => "1130"],
["id" => "5555", "test_number" => "2", "sat_total" => "1160"]
];
foreach ($Arr1 as $set) {
if (!isset($result[$set['id']])) {
$result[$set['id']] = ['id' => $set['id'], 'Score1' => $set['sat_total']];
} else {
$result[$set['id']]['Score' . sizeof($result[$set['id']])] = $set['sat_total'];
}
}
var_export(array_values($result));
Output:
array (
0 =>
array (
'id' => '4123',
'Score1' => '1050',
'Score2' => '1130',
'Score3' => '1120',
),
1 =>
array (
'id' => '5555',
'Score1' => '1130',
'Score2' => '1160',
),
)
This method will find the scores matching the $id.
It uses three array_intersects to match all the values correct.
This method will only loop the number of unique IDs, in your case two times.
Plus the times to create the score keys.
I do agree with what ggorlen says about the keys. That will also create a more efficient code.
$ids = array_column($Arr1, "id");
$sat = array_column($Arr1, "sat_total");
foreach(array_unique($ids) as $id){
$new[$id] = ["id" => $id];
$tmp = array_values(array_intersect_key($sat,array_intersect_key($Arr1, array_intersect($ids, [$id]))));
for($i=1;$i<=count($tmp);$i++) $new[$id]["Score" . $i] = $tmp[$i-1];
}
var_dump($new);
https://3v4l.org/ag3To
The output is an associative array with id as key.
You can use array_values if you want to make it indexed.
Just to show how much more efficient the code can be with one score array.
This is what it would look like:
$ids = array_column($Arr1, "id");
$sat = array_column($Arr1, "sat_total");
foreach(array_unique($ids) as $id){
$new[] = ["id" => $id, "scores" => array_values(array_intersect_key($sat,array_intersect_key($Arr1, array_intersect($ids, [$id]))))];
}
var_dump($new);
https://3v4l.org/mdA0W
$arr2 = [];
$i = 0;
$length = count($arr1);
do {
$builder = $arr1[$i];
// grab the first item from the original array
$builder = [
// set its initial properties
'id' => $arr1[$i]['id'],
'Score1' => $arr1[$i]['sat_total'],
];
// initialise the subsequent score number
$testNumber = 2;
// prepare to look ahead in the original array for a matching id
while (($i + 1) < $length) { // only look ahead if it is possible to
if ($arr1[$i + 1]['id'] == $builder['id']) {
// did you find a matching id? if so, let's set the subsequent score
$builder["Score$testNumber"] = $arr1[$i + 1]['sat_total'];
$testNumber++; // increase the next score number
$i++; // increase the original array index
} else {
// no luck? let's go forwards and set the next record
break;
}
}
$arr2[] = $builder; // set the built record into the new array
$i++; // move the pointer forwards
} while ($i < $length); // as long as there are items ahead
Not often you get to use a do-while. But it works :)
Feed it your original array $arr1 and $arr2 will be set.
It works by looking forward for matching ids. This solution assumes your original array is ordered by id! So unless you trust the input - don't use this solution!
Otherwise this is a simple, fast, and fairly readable solution to what looks to me like a school exercise?
If you want something that is safe, the other solutions here are suitable.
I'm not sure this structure is ideal--it seems like your keys "Score1", "Score2" etc would be best as an array like scores => [1050, 1130, ...] and it feels like the ids should be keys in the result array. But in any case, this gives your requested output:
$res = [];
foreach ($arr as $e) {
if (!array_key_exists($e['id'], $res)) {
$res[$e['id']] = [];
}
$res[$e['id']]["Score".(count($res[$e['id']])+1)] = $e['sat_total'];
}
$count = 0;
foreach ($res as $k => $v) {
$res[$k]['id'] = $k;
$res[$count++] = $res[$k];
unset($res[$k]);
}
print_r($res);
Output
Array
(
[0] => Array
(
[Score1] => 1050
[Score2] => 1130
[Score3] => 1120
[id] => 4123
)
[1] => Array
(
[Score1] => 1130
[Score2] => 1160
[id] => 5555
)
)
Note that I did two passes which is a little verbose, but taking the time to key in the data ids into the array in the first pass should improve a linear search through the array for each element into O(1) hashing, so I think it's worth the extra loop block.

Merge second array into first array where row value in first array matches row key in second array

I would like to merge the associative elements from my second array into my first array where the second array's subarray key matches a row's epg_channel_id value.
First array:
[
[
'num' => 1,
'name' => 'name 1',
'epg_channel_id' => 'ch111',
'added' => '1505435915',
],
[
'num' => 2,
'name' => 'name 2',
'epg_channel_id' => 'ch222',
'added' => '1505435915',
],
[
'num' => 3,
'name' => 'name 3',
'epg_channel_id' => 'ch333',
'added' => '1505435915',
],
[
'num' => 4,
'name' => 'name 4',
'epg_channel_id' => 'ch444',
'added' => '1505435915'
]
]
And the second array:
[
['ch000' => 'Um9jayBJbiBSaW8='],
['ch111' => 'Um9jayBJbiBSaW8='],
['ch222' => 'Um9jayBJbiBSaW8='],
['ch333' => 'Um9jayBJbiBSaW8='],
['ch444' => 'Um9jayBJbiBSaW8=']
]
Desired output (for one row):
Array
(
[0] => Array
(
[num] => 1
[name] => name 1
[epg_channel_id] => ch111
[added] => 1505435915
[ch111] => Um9jayBJbiBSaW8=
)
...
)
I tried array_recursive, array merge and not works.
If the corresponding indexes in both arrays are guaranteed to have the same channel id, this will work quite efficiently. For example, if $array1[0] is guaranteed to have the same channel id as $array2[0] this solution will work nicely:
$combined = [];
foreach($array1 as $key=>$val){
$combined[$key] = $val + $array2[$key];
}
However, if the corresponding indexes are not guaranteed to have the same channel ids, this solution will not work, and you'll need to use one of the other posted answers.
One last note if you do use this method is that if the arrays are different sizes, you will want the largest one to be $array1. So, just do a comparison to see which has the most elements.
You have to loop over two arrays to get desired result: as you have to match epg_channel_id of first array to second arrays inner key
$arr1 = Array
(
0 => Array
(
"num" => 1,
"name" => "name 1",
"epg_channel_id" => "ch111",
"added" => "1505435915",
),
1 => Array
(
"num" => 2,
"name" => "name 2",
"epg_channel_id" => "ch222",
"added" => "1505435915",
),
2 => Array
(
"num" => 3,
"name" => "name 3",
"epg_channel_id" => "ch333",
"added" => "1505435915",
),
3 => Array
(
"num" => 4,
"name" => "name 4",
"epg_channel_id" => "ch444",
"added" => "1505435915",
),
);
$arr2 = Array
(
0 => Array
(
"ch000" => "Um9jayBJbiBSaW8="
),
1 => Array
(
"ch111" => "Um9jayBJbiBSaW8="
),
2 => Array
(
"ch222" => "Um9jayBJbiBSaW8="
),
3 => Array
(
"ch333" => "Um9jayBJbiBSaW8="
),
4 => Array
(
"ch444" => "Um9jayBJbiBSaW8="
),
);
$new_array = array();
foreach($arr1 as $key=>$value)
{
foreach($arr2 as $key1=>$value1)
{
foreach($value1 as $key2=>$value2)
{
if($key2 == $value['epg_channel_id'])
{
$value[$key2]=$value2;
}
}
}
$new_array[$key]=$value;
}
print_r($new_array);
DEMO
You can key exists or not using array_key_exists in second array then add it to new array
Working Demo: https://eval.in/863359
$array = Array
(
Array
(
'num' => 1,
'name' => 'name 1',
'epg_channel_id' => 'ch111',
'added' => '1505435915',
),
Array
(
'num' => 2,
'name' => 'name 2',
'epg_channel_id' => 'ch222',
'added' => '1505435915',
),
Array
(
'num' => 3,
'name' => 'name 3',
'epg_channel_id' => 'ch333',
'added' => '1505435915',
),
Array
(
'num' => 4,
'name' => 'name 4',
'epg_channel_id' => 'ch444',
'added' => '1505435915'
)
);
$array2 = Array
(
Array
(
'ch000' => 'Um9jayBJbiBSaW8='
),
Array
(
'ch111' => 'Um9jayBJbiBSaW8='
),
Array
(
'ch222' => 'Um9jayBJbiBSaW8='
),
Array
(
'ch333' => 'Um9jayBJbiBSaW8='
),
Array
(
'ch444' => 'Um9jayBJbiBSaW8='
)
);
$newArray =[];
foreach ($array as $key => $value) {
foreach ($array2 as $key2 => $value2) {
if (array_key_exists($value['epg_channel_id'], $value2)) {
$value[$value['epg_channel_id']] = $value2[$value['epg_channel_id']];
}
}
$newArray[] = $value;
}
echo "<pre>";
print_r($newArray);
array_merge_recursive works well for associated array that have keys are string. Numeric keys will be appended. From php.net
If the input arrays have the same string keys, then the values for these keys are merged together into an array, and this is done recursively, so that if one of the values is an array itself, the function will merge it with a corresponding entry in another array too. If, however, the arrays have the same numeric key, the later value will not overwrite the original value, but will be appended.
You have to convert your array to string keys, or using one loop to merge child arrays one by one.
Try this . I hope it will solve your problems. I have tested it.
foreach ($array1 as $key => $value){
// echo $key;
foreach ($array2 as $i =>$item){
foreach ($item as $j=>$subitem){
if($value['epg_channel_id'] == $j){
$array1[$key][$j] = $subitem;
}
}
}
}
print_r($array1);
try to read the value of 'epg_channel_id' from array1
and insert it to array1 itself from getting 'ch111' from array2
$ch_name = $array1[$i]['epg_channel_id'];
$id = $array1[$i]['num'];
$ch_value = $array2[$id]['$ch_name'];
$array1[$i]["$ch_name"] = $ch_value;
try to put in foreach for every array
$new_arr = [];
foreach($arr1 as $val){
foreach($arr2 as $val2){
if(array_key_exists($val['epg_channel_id'], $val2)){
$val[$val['epg_channel_id']] = $val2[$val['epg_channel_id']];
break;
}
}
$new_arr[] = $val;
}
print_r($new_arr);
The bad news is that your second array is not suitably structured to serve as a lookup array. The good news is that the step to flatten the structure into a simple associative array is quite easy.
Once the lookup is declared, just use a loop and modify-by-reference as you use array union syntax to append the desired key-value pairs.
I do not recommend any answers that are using nested loops -- they will not perform efficiently.
Code: (Demo)
$lookup = array_merge(...$array2);
foreach ($array as &$row) {
$row += [$row['epg_channel_id'] => $lookup[$row['epg_channel_id']]];
}
var_export($array);

php merge multidimensional arrays into each other

I have two multidimensional arrays in php and want to merge them.
First one :
array1 = (
0 => array (
0 => array(
id => 1,
name => "test"
)
)
1 => array(...)
)
Second one :
array2 = (
0 => array (
0 => array(
id => 200,
name => "test"
),
1 => array(
id => 201,
name => "test"
)
)
1 => array(...)
)
And the merged array must be like this :
lastArray = (
0 => array (
0 => array(
id =>1,
name => "test"
),
1 => array(
id => 200,
name => "test"
),
2 => array (
id => 201,
name => "test"
)
)
1 => array(...)
)
How should I do this with a proper foreach loop?
According to the lastArray that you've presented - you need to merge the inner arrays only at 0 position/index(and you didn't show how should look a merge result at 1 index).Use the following approach:
$array1[0] = array_merge($array1[0], $array2[0]);
// now, $array1 is your $lastArray
Method 1:
Why not use array_merge or array-merge-recursive for this? This will merge the both (or more) arrays.
$array = array_merge($array1, $array2);
$array = array_merge-recursive($array1, $array2);
Method 2:
Loop through one array.
(You may check if the key is equal to the others array key).
Maybe something like this:
foreach($array_1 as $key=>$value) $array_2[$key] = $value;
Reminder from splash58: Don't forget to use usort in both methods to sort the array by values after sort by id

Method to convert an associative array into an indexed array

I have an associative array with indexed sub-arrays each containing associative arrays which contain content and an index. So like this (in PHP):
$assoc_arr =
array("second" => array(
array("position" => 4,
"content" => "Valiant"),
array("position" => 5,
"content" => "Hail")
),
"first" => array(
array("position" => 0,
"content" => "Hail"),
array("position" => 3,
"content" => "Victors"),
array("position" => 2,
"content" => "the"),
array("position" => 1,
"content" => "to")
)
);
I want to put all of those into an indexed array where their index is their "position" in the associative array. So final array should be:
Array ( [0] => Hail [1] => to [2] => the [3] => Victors [4] => Valiant [5] => Hail )
Currently, I'm merging all of the arrays inside the highest level array, then sorting it by each of those sub-array's positions, then creating an indexed array by pushing the content in order onto a new array. As so:
$pos_arr = array_merge($assoc_arr["second"], $assoc_arr["first"]);
usort($pos_arr, function($a, $b) {
return $a["position"] >= $b["position"] ? 1 : -1;
});
$indexed_arr = array();
foreach ($pos_arr as $elem) {
array_push($indexed_arr, $elem["content"]);
}
It seems like there must be a better way to do this! Can anyone think of one?
The data is coming from a poorly structured XML document which I can't change.
$idxar=array();
foreach ($assoc_arr as $subar)
foreach ($subar as $item)
$idxar[$item['position']+0]=$item['content'];
will work, if your input data is flawless.
Edit
If you need the keys not only to be numerically correct, but also in the correct order, you must suffix this with ksort($idxar)
here is another solution:
<?php
$assoc_arr =
array("second" => array(
array("position" => 4,
"content" => "Valiant"),
array("position" => 5,
"content" => "Hail")
),
"first" => array(
array("position" => 0,
"content" => "Hail"),
array("position" => 3,
"content" => "Victors"),
array("position" => 2,
"content" => "the"),
array("position" => 1,
"content" => "to")
)
);
$order = array('first','second'); // this helps you create your own sort. E.g. position1, position2, etc
$arr = array();
foreach($order as $ord) {
if(isset($assoc_arr[$ord])) {
$arr = array_merge($arr, $assoc_arr[$ord]);
}
}
$finalArray = array();
foreach($arr as $a) {
$finalArray[$a['position']] = $a['content'];
}
ksort($finalArray);
print_r($finalArray);
and a fiddle that works for you here

Subtract row values in Array2 from specific row values in Array1

I would like to subtract the quantity of $array2 from the stocks of $array1.
$array1= ([product_id]=>4, [stocks]=>20)
$array2= ([product_id]=>4, [quantity]=>3)
So that would be:
$array1= ([0]=> 4, [1] => 20);
$array2= ([0]=> 4, [1] => 3);
And then the output should be:
$array1= ([0]=> 4, [1] => 17);
Your array structure looks slightly different with multiple records, the code works out like this in an ugly manner. I'm assuming you're talking about something like this:
$array1 = array(
0=>array('product_id'=>4, 'stocks'=>20),
1=>array('product_id'=>5, 'stocks'=>60));
$array2 = array(
0=>array('product_id'=>4, 'quantity'=>3)
1=>array('product_id'=>5, 'quantity'=>30));
...It's a multi-dimensional array (typical for records pulled from a database).
foreach($array1 as $key=>$value){
foreach($array2 as $key2=>$value2) {
if($value['product_id']==$value2['product_id']){
$value['stocks'] -= $value2['quantity'];
//optimization to avoid searching this again.
unset($array2[$key]);
}
}}
With what you have given the following will do what you are asking for:
if($array1['product_id'] == $array2['product_id']) {
$array1['stocks'] -= $array2['quantity'];
}
If you need to loop through a bigger array then what I have given is only part of the larger puzzle.
Jesse's answer wasn't tested and will not provide the desired output because the "stocks" array wasn't being modified -- a copy of the array was being modified in the loop -- so if you try to print the result to screen, there would be no change.
To modify by reference, use & just before the value variable in the first loop.
Also the unset() key must come from the inner loop to be accurate.
Additionally, if the "sales" "product_id"s are unique, then breaking the inner loop upon matching will improve performance. (This is how array_search() works.)
Code: (Demo)
$stocks = [
['product_id'=>2, 'stocks'=>50],
['product_id'=>3, 'stocks'=>100],
['product_id'=>4, 'stocks'=>20],
['product_id'=>5, 'stocks'=>60]
];
$sales = [
['product_id'=>4, 'quantity'=>3],
['product_id'=>5, 'quantity'=>30]
];
foreach ($stocks as &$row) { // modify by reference
foreach ($sales as $k => $row2) { // search for product_id match
if ($row['product_id'] == $row2['product_id']) {
$row['stocks'] -= $row2['quantity']; // subtract
unset($sales[$k]); // eliminate match from lookup array
break; // assuming $sales['product_id'] values are unique
}
}
}
var_export($stocks);
Output:
array (
0 =>
array (
'product_id' => 2,
'stocks' => 50,
),
1 =>
array (
'product_id' => 3,
'stocks' => 100,
),
2 =>
array (
'product_id' => 4,
'stocks' => 17,
),
3 =>
array (
'product_id' => 5,
'stocks' => 30,
),
)
Alternatively, you can converted the sales array into a flattened, product_id-keyed array to serve as a lookup.
Code: (Demo)
$keyed = array_column($sales, 'quantity', 'product_id');
var_export($keyed);
echo "\n---\n";
foreach ($stocks as &$row) { // modify by reference
if (isset($keyed[$row['product_id']])) { // search for product_id match
$row['stocks'] -= $keyed[$row['product_id']]; // subtract
}
}
var_export($stocks);
Output:
array (
4 => 3,
5 => 30,
)
---
array (
0 =>
array (
'product_id' => 2,
'stocks' => 50,
),
1 =>
array (
'product_id' => 3,
'stocks' => 100,
),
2 =>
array (
'product_id' => 4,
'stocks' => 17,
),
3 =>
array (
'product_id' => 5,
'stocks' => 30,
),
)

Categories