I want to convert this:
$arr = [
[
'type' => 'fruit',
'name' => 'apple',
'cost' => 1
],
[
'type' => 'fruit',
'name' => 'orange',
'cost' => 2
],
[
'type' => 'vegetable',
'name' => 'carrot',
'cost' => 2.5
],
[
'type' => 'vegetable',
'name' => 'avocado',
'cost' => 3.5
]
];
Into this:
$arr = [
[
'type' => 'fruit',
'apple' => '1',
'orange' => 2
],
[
'type' => 'vegetable',
'carrot' => 2.5,
'avocado' => 3.5
]
];
As you can see, I'm needing to group each type in a single array and pivoting fruit name and cost.
Here's a method for obtaining that exact array structure in the output. This was a touch trickier than I thought it would be:
//first build groups by type
$groups = array();
foreach($arr as $key => $array){
//$type is not necessary, it's just for clarity below
$type = $array['type'];
if( !isset($groups[$type]) ){
$groups[$type] = array();
$groups[$type]['type'] = $array['type'];
}
$groups[$type][$array['name']] = $array['cost'];
}
//then combine the groups into a master array
$out = array();
foreach($groups as $g){
$out[] = $g;
}
echo '<pre>'. print_r($out, true).'</pre>';
For something like this simply looping through your array as Rizier suggests should do the trick. If you're grouping by type, it would probably be easiest to use the type as the array key, so the format would be slightly different than you've requested. You'd be able access your resulting array like this:
$fruitItems = $newArr['fruit'];
Here's sample code:
$size = count($itemArray);
$newArr = []; //The array you want to create
for ($i = 0; $i < $size; $i++)
{
$item = $itemArray[$i];
$type = $item['type'];
$name = $item['name'];
$cost = $item['cost']
//If your new array doesn't have this type yet, make a sub-array
//with the type as key
if (!isset($newArr[$type]))
$newArr[$type] = [];
$newArr[$type][$name] = $cost; //In your type sub-array, add the new item
}
If you absolutely have to create an array with that structure, you can tweak the above code a little bit to search through your array and find the sub-array with the correct type. However, that seems a little over complicated.
In case someone needs a little more generic approch and has to handle multiple values in the same column I enlarged #larsAnders solution a bit.
Usage (to get the exact required output):
$pivot_fruits = arr_pivot($arr, 'type', 'name', 'cost', 'sum', false, false);
Function:
function arr_pivot(
$arr,
$arr_key_to_pivot_for_rows,
$arr_key_to_pivot_for_columns,
$arr_key_to_pivot_for_values,
$grouping_method = "sum",
$add_missing_columns=true,
$sort_columns=true){
if(!is_array($arr)) return false;
$pivot_row = $arr_key_to_pivot_for_rows;
$pivot_col = $arr_key_to_pivot_for_columns;
$pivot_val = $arr_key_to_pivot_for_values;
//first build groups by $col_name_to_pivot_for_rows
$row_groups = [];
$columns=[];
foreach ($arr as $row_key => $row) {
$group_label = $row[$pivot_row];
$col_label = $row[$pivot_col];
$value = $row[$pivot_val];
if( !isset($row_groups[$group_label]) ){
$row_groups[$group_label]=[];
$row_groups[$group_label][$pivot_row] = $group_label;
}
if(!isset($columns[$col_label])) $columns[$col_label]=[];
$row_groups[$group_label][$col_label][]=$value;
}
//then combine the groups into a return array
$arr_pivoted = [];
foreach($row_groups as $row_group){
//all columns except of the first one are pivoted columns. split into the row name and the columns itself
$row_group_columns = $row_group;
$row_group_row = array_shift($row_group_columns);
//ensure that all groups have all columns
if($add_missing_columns) $row_group_columns = array_merge($columns,$row_group_columns);
if($sort_columns) ksort($row_group_columns);
$row_group_columns_grouped=[];
//apply grouping function to columns
foreach ($row_group_columns as $column_name => $row_group_column){
$acount=count($row_group_column);
switch ($grouping_method){
default:
case 'sum':
$row_group_columns_grouped[$column_name] = array_sum($row_group_column);
break;
case 'avg':
$row_group_columns_grouped[$column_name] = $acount == 0 ? 0 : array_sum($row_group_column) / count($row_group_column);
break;
case 'count':
$row_group_columns_grouped[$column_name] = count($row_group_column);
break;
case 'max':
$row_group_columns_grouped[$column_name] = $acount == 0 ? 0 : max($row_group_column);
break;
case 'min':
$row_group_columns_grouped[$column_name] = $acount == 0 ? 0 : min($row_group_column);
break;
case 'concat':
case 'implode':
$row_group_columns_grouped[$column_name] = implode(',',$row_group_column);
break;
}
}
$arr_pivoted[] = array_merge([$pivot_row=>$row_group_row],$row_group_columns_grouped);
}
return $arr_pivoted;
}
PHPDoc:
/**
* Turns 2 dimensional array in a pivoted version
*
* #param array $arr 2 dimensional to pivot
* #param string $arr_key_to_pivot_for_rows input array key to use as rows
* #param string $arr_key_to_pivot_for_columns input array key to use as columns
* #param string $arr_key_to_pivot_for_values input array key to use as values
* #param string $grouping_method method to use on values out of sum|count|avg|min|max|concat. If values are not numeric use count or concat
* #param bool $add_missing_columns if true all occurring columns in any row are added if missing
* #param bool $sort_columns if true all columns will be sorted by column name (=key)
* #return array|bool false if input is not an array otherwise the pivoted result as 2 dimensional array
*/
If someone feels the urge to improve coding style, input validation and/or variable naming I would be very glad.
This task is as simple as grouping related data using temporary first level keys.
$array = [
['type' => 'fruit', 'name' => 'apple', 'cost' => 1],
['type' => 'fruit', 'name' => 'orange', 'cost' => 2],
['type' => 'vegetable', 'name' => 'carrot', 'cost' => 2.5],
['type' => 'vegetable', 'name' => 'avocado', 'cost' => 3.5]
];
$result = [];
foreach ($array as $row) {
$result[$row['type']]['type'] = $row['type'];
$result[$row['type']][$row['name']] = $row['cost'];
}
var_export(array_values($result));
Contrary to what is demonstrated by other answers on this page, parent elements do not need to be instantiated/declared before assigning a child element. (The same cannot be said for when assigning child properties in an object.) There is also no need for conditions or multiple loops for this basic task.
Output:
array (
0 =>
array (
'type' => 'fruit',
'apple' => 1,
'orange' => 2,
),
1 =>
array (
'type' => 'vegetable',
'carrot' => 2.5,
'avocado' => 3.5,
),
)
You can also abuse a body-less foreach() loop's destructuring syntax to perform the pivot. (Demo)
$array = [
['type' => 'fruit', 'name' => 'apple', 'cost' => 1],
['type' => 'fruit', 'name' => 'orange', 'cost' => 2],
['type' => 'vegetable', 'name' => 'carrot', 'cost' => 2.5],
['type' => 'vegetable', 'name' => 'avocado', 'cost' => 3.5]
];
$result = [];
foreach ($array as ['type' => $type, 'type' => $result[$type]['type'], 'name' => $name, 'cost' => $result[$type][$name]]);
var_export(array_values($result));
This provides the same result as above. Notice how the type key is written twice while destructuring -- it looks invalid, but it actually works.
Related
I have an array like so:
$array = [
['record' => 1, 'sponsor' => 2, 'email' => 'some#email.com'],
['record' => 2, 'sponsor' => 2, 'email' => 'some1#email.com'],
['record' => 3, 'sponsor' => 2, 'email' => 'some2#email.com'],
['record' => 4, 'sponsor' => 2, 'email' => 'some3#email.com'],
];
Each row has a unique record and email and the sponsor key is related to the record. So, instead of an integer, I am trying to replace the value of the sponsor key with the corresponding email based on the record, something like this:
$array = [
['record' => 1, 'sponsor' => 'some1#email.com', 'email' => 'some#email.com'],
['record' => 2, 'sponsor' => 'some1#email.com', 'email' => 'some1#email.com'],
['record' => 3, 'sponsor' => 'some1#email.com', 'email' => 'some2#email.com'],
['record' => 4, 'sponsor' => 'some1#email.com', 'email' => 'some3#email.com'],
];
I have tried using a foreach loop but it doesn't give me the expected result:
$yes = [];
foreach ($array as $key => $arr) {
if ( ! empty($arr['sponsor']) ) {
if ( $arr['sponsor'] == $array[$key]['record'] ) {
$yes[] = ['sponsor' => $array[$key]['email']];
}
}
$yes[] = [
'record' => $array[$key]['record'],
'email' => $array[$key]['email'],
'sponsor' => $array[$key]['sponsor'],
];
}
print_r($yes);
As record is unique, I recommend rebuilding $arr using this value as the key. Then you loop again to replace your value for $sponsor –
$indexedArray = [];
foreach ($array as $v) {
$indexedArray[$v['record']] = $v;
}
foreach ($indexedArray as $record => $v) {
if (isset($indexedArray[$v['sponsor']]) && isset($indexedArray[$v['sponsor']]['email'])) {
$indexedArray[$record]['sponsor'] = $indexedArray[$v['sponsor']]['email'];
}
}
$yes = array_values($indexedArray);
Create a lookup array with record values as keys and email values as values, then overwrite the sponsor value in each row based on the corresponding lookup value.
Code: (Demo)
$lookup = array_column($array, 'email', 'record');
var_export(
array_map(fn($row) => array_replace($row, ['sponsor' => $lookup[$row['sponsor']]]), $array)
);
Or with a classic loop: (Demo)
$lookup = array_column($array, 'email', 'record');
foreach ($array as &$row) {
$row['sponsor'] = $lookup[$row['sponsor']];
}
var_export($array);
If there is a chance that a given sponsor value might not be found in the lookup, then use $lookup[$row['sponsor']] ?? 'some fallback' when you try to access the lookup array. This way you will not generate warnings/errors. This is a more concise way of checking isset().
p.s. Bear in mind that using the array union operator won't work properly in this case.
I have a a number of values/IDs that need to be translated to a single ID, what is the recommended method using PHP?
For example, I want IDs 38332, 84371, 37939, 1275 to all translate to ID 1234 and IDs222, 47391, 798 to all translate to ID 1235, etc. .
I'm thinking PHP has something built-in to handle this efficiently?
I'm thinking PHP has something built-in to handle this efficiently?
You can use the standard array as a map, quickly translating one ID to another:
$table[38332]; # int(1234)
depending on how you store your overall translation table, you can create a function that returns the translation from its input:
$table = $translation('I want IDs 38332, 84371, 37939, 1275 to all translate to ID 1234');
$result = $table[1275] ?? null; # int(1234)
Example:
$parseId = static fn(string $i) => (int)trim($i);
$translation = static fn(string $buffer): array
=> preg_match_all('~((?:\d+,\s*)+\d+)\s+to all translate to ID\s*(\d+)~', $buffer, $_, PREG_SET_ORDER)
? array_reduce($_, static fn (array $carry, array $item): array => [
$ids = array_map($parseId, explode(',', $item[1])),
$carry += array_fill_keys($ids, $parseId($item[2])),
$carry,][2], []) : [];
This is pretty easy to accomplish with PHP, here's one way you could do it:
Using this method, you populate the $map array, using the id you want to replace with as the key, and the value being an array of the keys you want to be replaced. It then calculates a simple key => value array based on this to make comparison a lot quicker.
Instead of creating a copy of the data, you could use foreach ($data as &$record)
$data = [
[
'id' => 1,
'foreign_id' => 38332,
'text' => 'a'
],
[
'id' => 2,
'foreign_id' => 84371,
'text' => 'b'
],
[
'id' => 3,
'foreign_id' => 37939,
'text' => 'c'
],
[
'id' => 4,
'foreign_id' => 1275,
'text' => 'd'
],
[
'id' => 5,
'foreign_id' => 222,
'text' => 'e'
],
[
'id' => 5,
'foreign_id' => 47391,
'text' => 'f'
],
[
'id' => 5,
'foreign_id' => 798,
'text' => 'g'
]
];
$map = [
123 => [
38332,
84371,
37939,
1275
],
1235 => [
222,
47391,
798
]
];
// Calculate a map to speed things up later
$map_calc = [];
foreach ($map as $destination_id => $ids) {
foreach ($ids as $id) {
$map_calc[$id] = $destination_id;
}
}
$new_data = [];
foreach ($data as $record) {
if (isset($map_calc[$record['foreign_id']]))
$record['foreign_id'] = $map_calc[$record['foreign_id']];
$new_data[] = $record;
}
var_dump($new_data);
I need to add new elemets to my array when a new category value is encountered. When a category value is encountered after the first time, its value1 and value2 values should be added to the first encounter's respective values.
Also, in the result array, I no longer wish to keep the category column. The category-grouping rows should use the category value as its name value.
Sample input:
$datas = [
[
'category' => 'Solution',
'name' => 'Name1',
'value1' => 20,
'value2' => 21
],
[
'category' => 'Solution',
'name' => 'Name2',
'value1' => 30,
'value2' => 31
],
[
'category' => 'Solution1',
'name' => 'Name3',
'value1' => 40,
'value2' => 41
]
];
Desired result:
[
['name' => 'Solution', 'value1' => 50, 'value2' => 52],
['name' => 'Name1', 'value1' => 20, 'value2' => 21],
['name' => 'Name2', 'value1' => 30, 'value2' => 31],
['name' => 'Solution1', 'value1' => 40, 'value2' => 41],
['name' => 'Name3', 'value1' => 40, 'value2' => 41]
]
I tried like this:
private function groupByProductSuperCategory($datas)
{
$return = [];
foreach ($datas as $data) {
$return[$data['category']][$data['name']] = array_sum(array_column('category', $data);
}
return $return;
}
The idea is to calculate first all sum values for by category, and after that just put values from name like another array. Have you an idea of how to do that?
From the posted array... To end in the desired array, there is some tiny fixes to do first. But I assumed it was due to typos while copying here...
So here is the array I started with:
$result = [
0 => [
"category" => 'Solution',
"name" => 'Name1',
"value1" => 20,
"value2" => 21
],
1 => [
"category" => 'Solution',
"name" => 'Name2',
"value1" => 30,
"value2" => 31
],
2 => [
"category" => 'Solution1',
"name" => 'Name3',
"value1" => 40,
"value2" => 41
]
];
Now, that re-organization of the data is a bit more complex than it looks... You need to perform several loops to:
Find distinct "category" names
Perform the summations for each
Add the sum item and the single items
So here is the code I ended with:
function groupByProductSuperCategory($datas){
$category = [];
$return = [];
// Find distinct categories
foreach ($datas as $data) {
if(!in_array($data["category"],$category)){
array_push($category,$data["category"]);
}
}
// For each distinct category, add the sum item and the single items
foreach ($category as $cat) {
// Get the sums
if(!in_array($cat,$return)){
$sum1 = 0;
$sum2 = 0;
foreach ($datas as $data) {
if($data["category"] == $cat){
$sum1 += $data["value1"];
$sum2 += $data["value2"];
}
}
}
// Push the sums in the return array
array_push($return,[
"name" => $cat,
"value1" => $sum1,
"value2" => $sum2,
]);
// Push the single elements
foreach ($datas as $data) {
if($cat == $data["category"]){
array_push($return,[
"name" => $data["name"],
"value1" => $data["value1"],
"value2" => $data["value2"],
]);
}
}
}
return $return;
}
Here is a PHPFiddle to try it out... Hit [F9] to run.
It is much more direct, efficient, and readable to implement a single loop and push reference variables into the result array to allow summing based on shared categories without keeping track of the actual indexes of the category rows.
Code: (Demo)
$result = [];
foreach ($array as $row) {
if (!isset($ref[$row['category']])) {
$ref[$row['category']] = [
'name' => $row['category'],
'value1' => 0,
'value2' => 0
];
$result[] = &$ref[$row['category']];
}
$ref[$row['category']]['value1'] += $row['value1'];
$ref[$row['category']]['value2'] += $row['value2'];
unset($row['category']);
$result[] = $row;
}
var_export($result);
I want to know that is there a way to insert certain elements of an array into a new array. I mean I have an array containing 10 objects. Each object has 3 or four fields for example id, name , age , username. now I want to insert the id's of all the objects into the new array with a single call.Is there anyway to do that.
$array = [
[0] => [
id =>
name =>
],
[1] = > [
id =>
name =>
]
]
and so on now I want to insert all the id's of all the object into a new array with a single call. Is there a way to do that?
Use array_map() function.
Here is your solution:-
$ids = array_map( function( $arr ){
return $arr["id"];
}, $arr );
echo '<pre>'; print_r($ids);
A basic foreach loop will do the job just fine
$firstArray = array(
array(
'id' => 1,
'name' => 'abc'
),
array(
'id' => 2,
'name' => 'def'
),
array(
'id' => 3,
'name' => 'gh'
)
);
$onlyIds = array();
$onlyKeys = array();
//To get the array value 'id'
foreach($firstArray as $value){
$onlyIds[] = $value['id'];
}
//To get the array keys
foreach($firstArray as $key => $value){
$onlyKeys[] = $key;
}
You could use array_walk which could be considered a "single call"
$array = array(0 => array('id', 'name', 'age'), 1 => array('id', 'name', 'age'));
array_walk($array, function($item, $key) {
// $key is 0 or 1
// $item is either id, name, age
});
You can use array_column.
$arr = [ ['id' => 1, 'username' => 'a'], ['id' => 2, 'username' => 'b'] ];
$ids = array_column($arr, 'id')
$ids == [1, 2]
I'm having real problems trying to figure this one out.
I have a PHP array which looks like this:
$info = array();
$info[0] = array(
'car' => 'Audi',
'previous_car' => 'BMW'
);
$info[1] = array(
'car' => 'Audi',
'previous_car' => 'Seat'
);
$info[2] = array(
'car' => 'Audi',
'previous_carg' => 'BMW'
);
$info[3] = array(
'car' => 'BMW',
'previous_car' => 'BMW'
);
$info[4] = array(
'car' => 'Ford',
'previous_car' => 'Seat'
);
I need to do some sorting on this, so the result looks like this:
Array (
car [
'Audi' => 3,
'BMW' => 1,
'Ford' => 1
],
previous_car [
'BMW' => 3,
'Seat' => 2
]
);
I need to count distinct occurrences of a value in the same key, but the search is made upon couple of arrays. I was trying to use array_value_count(), but I doesn't work well on multidimensional arrays.
I am trying to avoid the looping, since it can be overkill if the array is large.
I will be very grateful for all the help.
If you're running PHP 5.5, you can use:
$newArray = array(
'car' => array_count_values(array_column($info, 'car')),
'previous_car' => array_count_values(array_column($info, 'previous_car'))
);
var_dump($newArray);
For versions of PHP prior to 5.5
$newArray = array(
'car' => array_count_values(
array_map(
function($value) {
return $value['car'];
},
$info
)
),
'previous_car' => array_count_values(
array_map(
function($value) {
return $value['previous_car'];
},
$info
)
)
);
var_dump($newArray);
In a more object orientated way you can solve it as follows
$values = new ArrayObject();
$iterator = new RecursiveArrayIterator($info);
iterator_apply($iterator, 'countDistinct', array($iterator, $values));
function countDistinct($iterator, $values) {
while ( $iterator -> valid() ) {
if ( $iterator -> hasChildren() ) {
countDistinct($iterator -> getChildren(), $values);
} else {
if (!$values->offsetExists($iterator->key())) {
$values->offsetSet($iterator->key(), new ArrayObject());
}
if (!$values->offsetGet($iterator->key())->offsetExists($iterator->current())) {
$values->offsetGet($iterator->key())
->offsetSet($iterator->current(), 1);
} else {
$values->offsetGet($iterator->key())
->offsetSet($iterator->current(),
$values->offsetGet($iterator->key())->offsetGet($iterator->current()) + 1);
}
}
$iterator -> next();
}
}
Sure, with this example you do not avoid the loop. But with the ArrayObject and the RecursiveArrayIterator you will have some memory and performance advantages.
The result of this will exactly match your expected result, which you can easyliy iterate with the getIterator() function of the ArrayObject.
You can write a function that will sort your data but for now check this out:
http://www.php.net/manual/en/function.array-multisort.php
Here is what might help you:
$returnArray = array('car' => NULL, 'previous_car' => NULL);
foreach($info as $newInfo) {
$returnArray['car'][] = $newInfo['car'];
$returnArray['previous_car'][] = $newInfo['previous_car'];
}
$ret['car'] = array_count_values($returnArray['car']);
$ret['previous_car'] = array_count_values($returnArray['previous_car']);
var_dump($ret);
This returns:
array (size=2)
'car' =>
array (size=3)
'Audi' => int 3
'BMW' => int 1
'Ford' => int 1
'previous_car' =>
array (size=2)
'BMW' => int 3
'Seat' => int 2