Create new array based on another array - php

I have array with the next structure:
$collection = array (
array('category' => 'buildings',
'id' => 9),
array('category' => 'buildings',
'id' => 8),
array('category' => 'trees',
'id' => 11),
array('category' => 'trees',
'id' => 12),
array('category' => 'trees',
'id' => 11),
)
I need to create next collection:
array ('buildings' => array (9, 8),
'trees' => array (10, 12, 11),
)
So I use for it foreach () and array_push(). First of all if new collection hasn't current category. If not, I set empty array, push id to it. But if category exists in new collection I push id value to array ans add to collection. So my code is:
function getCategoriesAndTypes($collection)
{
$categoriesAndTypes = [];
$typesCollection = [];
foreach ($collection as $object) {
$category = $object['category'];
if (! array_key_exists($category, $categoriesAndTypes)) {
$typesCollection = [];
array_push($typesCollection, $object['type']);
$categoriesAndTypes[$category] = $typesCollection;
} else {
array_push($typesCollection, $object['type']);
$categoriesAndTypes[$category] = $typesCollection;
}
}
return $categoriesAndTypes;
}
But I think that more pretty sollutions exist! Can you help me to refactor my code?
Thanks!

I've refactored my code to next variant:
function getCategoriesAndTypesFromLibraryFolder($collection)
{
$categoriesAndTypes = [];
$typesCollection = [];
foreach ($collection as $object) {
if (! array_key_exists($object['category'], $categoriesAndTypes)) {
$typesCollection = [];
}
array_push($typesCollection, $object['type']);
$categoriesAndTypes[$object['category']] = $typesCollection;
}
return $categoriesAndTypes;
}
Have you idea to make it better?

The following method would be enough:
function getCategoriesAndTypesFromLibraryFolder($collection)
{
$categoriesAndTypes = [];
foreach ($collection as $item) {
$categoriesAndTypes[$item['category']][] = $item['id'];
}
return $categoriesAndTypes;
}
The result is:
array ( 'buildings' => array ( 0 => 9, 1 => 8, ),
'trees' => array ( 0 => 11, 1 => 12, 2 => 11, ), )

Related

Filter array with array_walk_recursive but deny specific values

I am trying to filter an array
$array = [
[
'id' => 1,
'some_key' => 'some_value',
'attribute' => [
'id' => 45,
'cat_id' => 1
],
'sub' => [
'id' => 17,
'some_key' => 'some_value',
'attribute' => [
'id' => 47,
'cat_id' => 17
],
],
],
[
'id' => 2,
'some_key' => 'some_value',
'sub' => [
'id' => 19,
'some_key' => 'some_value',
],
]
];
$childArray = [];
array_walk_recursive($array, static function($value, $key) use(&$childArray){
if($key === 'id') {
$childArray[] = $value;
}
});
This returns me an array of all array-fields having id as key.
[1,45,17,47,2,19]
But there is a small problem, some of the array have an key called attribute containing an idkey field that I dont want to have.
[1,17,2,19] //this is what I want
Is there a way to say "don't take the id inside attribute" ?
My current solution, I added a filter before my filter :D
/**
* #param array $array
* #param string $unwanted_key
*/
private function recursive_unset(&$array, $unwanted_key)
{
unset($array[$unwanted_key]);
foreach ($array as &$value) {
if (is_array($value)) {
$this->recursive_unset($value, $unwanted_key);
}
}
}
but this seems like this is not the best practice ^^
You can traverse recursively manually instead of array_walk_recursive and avoid all under attribute key.
<?php
$childArray = [];
function collectIDs($arr,&$childArray){
foreach($arr as $key => $value){
if($key === 'attribute') continue;
if(is_array($value)) collectIDs($value,$childArray);
else if($key === 'id') $childArray[] = $value;
}
}
collectIDs($array,$childArray);
print_r($childArray);
Demo: https://3v4l.org/V6uFf
Find a function that will flatten your array. The result should look like this (I have a class for this):
array (
0 =>
array (
'id' => 1,
'some_key' => "some_value",
'attribute.id' => 45,
'attribute.cat_id' => 1,
'sub.id' => 17,
'sub.some_key' => "some_value",
'sub.attribute.id' => 47,
'sub.attribute.cat_id' => 17,
),
1 =>
array (
'id' => 2,
'some_key' => "some_value",
'sub.id' => 19,
'sub.some_key' => "some_value",
),
)
So you have all keys available and can work with a modified array_walk.
$childArray = [];
array_walk_recursive($data, static function($value, $key) use(&$childArray){
$keys = array_reverse(explode(".",$key));
if($keys[0] === 'id' AND (!isset($keys[1]) OR $keys[1] != 'attribute')) {
$childArray[] = $value;
}
});
The RecursiveArrayIterator class is also very suitable when array values ​​are to be collected depending on keys and values ​​on different levels.
$result = [];
$it = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
foreach($it as $key => $value) {
$parentLevel = $it->getDepth()-1;
$parentKey = $it->getSubIterator($parentLevel)->key();
if($key === 'id' AND $parentKey !== 'attribute'){
$result[] = $value;
}
}
var_export($result);
//array ( 0 => 1, 1 => 17, 2 => 2, 3 => 19, )
To return the qualifying ids instead of creating a reference variable, merge recursive calls of the function as you iterate.
Code: (Demo)
function getIds($array) {
$ids = [];
foreach ($array as $key => $value) {
if ($key === 'id') {
$ids[] = $value;
} elseif ($key !== 'attribute' && is_array($value)) {
$ids = array_merge($ids, getIds($value));
}
}
return $ids;
}
var_export(getIds($array));
Output:
array (
0 => 1,
1 => 17,
2 => 2,
3 => 19,
)

convert normal array in nested array with php

I have a hierarchical database table like below
ID Name Subcategory ParentID
1 ABC 0
2 DEF QFE 0
3 QFE XYZ 2
4 XYZ MNJ 3
From Thant I have got PHP array like below
$array_name = array(
array('ID' => '1', 'Name' => 'ABC', 'Subcategory' => '', 'ParentID' => '0'),
array('ID' => '2', 'Name' => 'DEF', 'Subcategory' => 'QFE', 'ParentID' => '0'),
array('ID' => '3', 'Name' => 'QFE', 'Subcategory' => 'XYZ', 'ParentID' => '2'),
array('ID' => '4', 'Name' => 'XYZ', 'Subcategory' => 'MNJ', 'ParentID' => '3')
);
but I want array like below
$array_name = array(
array('ID' => '1', 'Name' => 'ABC', 'Subcategory' => '', 'ParentID' => '0'),
array('ID' => '2', 'Name' => 'DEF', 'Subcategory' => array('ID' => '3', 'Name' => 'QFE', 'Subcategory' => array('ID' => '4', 'Name' => 'XYZ', 'Subcategory' => 'MNJ', 'ParentID' => '3'), 'ParentID' => '2'), 'ParentID' => '0'),
);
I want a function which checks is that row have some Subcategory or not and if a row has Subcategory then get that subcategory row as an array and make one array with all category and Subcategory
for that, I have tried to make a function which is given below
function find_subcategory($ID,$con){
$table_name ="SELECT * FROM `table_name` WHERE `parent_id` = '$ID'";
$table_name_result = mysqli_query($con,$table_name);
$category_array = array();
if(mysqli_num_rows($table_name_result)) {
while($row = mysqli_fetch_assoc($table_name_result)) {
$Subcategory= $row['Subcategory'];
$ID = $row['ID'];
if ($Subcategory== '') {
$find_subcategory = find_subcategory($ID,$con);
$row['Subcategory'] = $find_subcategory;
$category_array[] = $row;
}else{
$category_array[] = $row;
}
}
}
return json_encode(array('tbl_category'=>$category_array));
}
but this function is not working to get all the subcategories of one category.
can anybody help me with this
Rather than create a recursive routine, which executes the SQL for each level, this instead reads all of the categories in and them assembles them into the hierarchy.
Note that it reads them in reverse order so that when it assembles them, each subcategory is always read before the parent (More details in code comments)...
$table_name ="SELECT * FROM `category` ORDER BY parent_id DESC, id desc";
$table_name_result = mysqli_query($con,$table_name);
$categories = mysqli_fetch_all($table_name_result, MYSQLI_ASSOC);
$output= [];
foreach ( $categories as $category) {
// If there is a parent for this item
if ( !empty ($category['parent_id']) ) {
// Set basic details
$output[$category['parent_id']]['Subcategory'][$category['id']] = $category;
// If there is already some data (subcategories)
if ( isset($output[$category['id']]) ){
// Copy subcategories
$output[$category['parent_id']]['Subcategory'][$category['id']] +=
$output[$category['id']];
// Remove old node
unset ( $output[$category['id']] );
}
}
else {
// Add in category data (allow for existing data to be added
$output[$category['id']] = $category + ($output[$category['id']]??[]);
}
}
I successfully implemented and tested a recursive routine to solve this. However, for performance reasons I had to decouple the access to the database from the recursive call.
First, you fetch your query into an array as you already have and then recursively rearrange the elements so that the keys are nested in the proper order.
Pretty much of the explanations I'd like to put here are put as comments in the code.
$array_name = array(
array('ID' => '1', 'Name' => 'ABC', 'Subcategory' => '', 'ParentID' => '0'),
array('ID' => '2', 'Name' => 'DEF', 'Subcategory' => 'QFE', 'ParentID' => '0'),
array('ID' => '3', 'Name' => 'QFE', 'Subcategory' => 'XYZ', 'ParentID' => '2'),
array('ID' => '4', 'Name' => 'XYZ', 'Subcategory' => 'MNJ', 'ParentID' => '3'),
array('ID' => '5', 'Name' => 'XYY', 'Subcategory' => 'MNJ', 'ParentID' => '1')
);
// recreate nested array
function get_nested_array($arr) {
$new_arr = array(); // new array to collect each top level element with nesting
foreach ($arr as $key => $value) {
// only top level elements would appear here as they would not be nested
if($value['ParentID'] == '0') {
array_push($new_arr, get_nested_item($value, $arr));
}
}
return $new_arr;
}
// recursive function to perform nesting on each element
function get_nested_item($hay, $stack) {
foreach ($stack as $key => $value) {
if ($hay['ID'] == $value['ParentID']) {
$index = get_key($hay, $stack);
// reduce $stack size by removing the HAY from the STACK
// recursion terminates when $stack size is 0
$stack = $index >= 0 ? array_splice($stack, $index) : [];
// update subcategory of the found nesting
$hay['Subcategory'] = get_nested_item($value, $stack);
}
}
return $hay;
}
// get the position of $hay in a $stack using the ID
function get_key($hay, $stack) {
foreach ($stack as $key => $value) {
if($hay['ID'] == $value['ID']) return $key;
}
return -1;
}
// print array so that it understandable
function print_array($arr) {
foreach ($arr as $key => $value) {
print_r($value);
echo "<br>";
}
}
// Test case
// print array before nesting
print_array($array_name);
echo"<br>";
// print array after nesting
$new_array = get_nested_array($array_name);
print_array($new_array);

Recursive function to build an array

Can anyone see why this isn't working?
private static function traverseTree($tree,$node)
{
$nodes = array();
$results = array();
$children = $tree->where('parent_id',$node->id)->where('type',UserGroup::DEPARTMENT_TYPE);
foreach($children as $child){
$nodes[] = $child->id;
$children = self::traverseTree($tree,$child);
$results = array_merge ($nodes,$children);
}
return $results;
}
I am stepping through it and I can see that the children are getting found but they are not included in the final results.
The final results just contain the top level.
Detail added:
$tree is a Laravel Collection, if I pipe this to an array I get:
array (
0 =>
array (
'id' => 21,
'name' => 'Top',
'type' => 1,
'parent_id' => 0,
),
1 =>
array (
'id' => 33,
'name' => 'UKDept',
'type' => 2,
'parent_id' => 21,
),
2 =>
array (
'id' => 36,
'name' => 'UKSubDept',
'parent_id' => 33,
),
3 =>
array (
'id' => 37,
'name' => 'USDept',
'type' => 2,
'parent_id' => 21,
),
)
$node:
array (
'id' => 21,
'name' => 'Top',
'type' => 1,
'parent_id' => 0,
),
The first call to populate $children gives:
array (
0 =>
array (
'id' => 33,
'name' => 'UKDept',
'type' => 2,
'parent_id' => 21,
),
1 =>
array (
'id' => 37,
'name' => 'USDept',
'type' => 2,
'parent_id' => 21,
),
)
Try taking the array outside the function.
$nodes = array();
$results = array();
private static function traverseTree($tree,$node)
{
$children = $tree->where('parent_id',$node->id)->where('type',UserGroup::DEPARTMENT_TYPE);
foreach($children as $child){
$nodes[] = $child->id;
$children = self::traverseTree($tree,$child);
$results = array_merge ($nodes,$children);
}
return $results;
}
It will not reinitialize them each time you call the function.Check if it works?
Looks like $child is an array but you are treating it like an object. Try changing:
$nodes[] = $child->id;
to
$nodes[] = $child['id'];
I have this working now, one stupid mistake I made was:
foreach($children as $child){
$nodes[] = $child->id;
$children = self::traverseTree($tree,$child);
$results = array_merge ($nodes,$children);
Using $children as a variable name twice!
This is the complete function:
private static function traverseTree($tree,$node)
{
$initialId = array();
$results = array();
$results[] = $node->id;
$children = $tree->where('parent_id',$node->id);
foreach($children as $node){
//if this is a department, then we need to go deeper
if($node->type == DEPARTMENT) {
$nodesChildren = self::traverseTree($tree, $node);
$results = array_merge ($results,$nodesChildren);
}else{
//just add this node to the results array
$results[] = $node->id;
}
}
return $results;
}

PHP Loop Multidimensional Associative Array [duplicate]

This question already has answers here:
Transposing multidimensional arrays in PHP
(12 answers)
Closed 3 months ago.
I have this output:
I don't have any idea how can I make my array look like this:
$array[
0 => [
'item_id' => 6,
'price' => "2311.00",
'qty' => 12,
'discount' => 0
],
1 => [
'item_id' => 7,
'price' => "1231.00",
'qty' => 1,
'discount' => 12
],
2 => [
'item_id' => 8,
'price' => "123896.00",
'qty' => 0,
'discount' => 24
]
]
I have started the loop but I don't really know how to get that kind of structure.
foreach( $array as $wishlist ){
foreach( $wishlist as $k => $v ){
}
}
You can iterate the outer and inner arrays to build your data like so, this allows you to add further keys to the array later - but does depend on your inner array keys being contiguous
$wishlist = [];
foreach ($array as $outerKey => $outerValue) {
foreach ($outerValue as $innerKey => $innerValue) {
$wishlist[$innerKey][$outerKey] = $innerValue;
}
}
Your loop should look like this:
foreach( $array as $item => $wishlist ){
foreach( $wishlist as $k => $v ){
$new_array[$k][$item] = $v;
}
}
You should have to use for loop.
for($i=0;$i<count(youarray['item_id']);$i++) {
$wishlist[$i]['item_id'] = youarray['item_id'][$i];
$wishlist[$i]['price'] = youarray['price'][$i];
$wishlist[$i]['qty'] = youarray['qty'][$i];
$wishlist[$i]['discount'] = youarray['discount'][$i];
}
or user foreach like this
foreach(youarray['item_id'] as $key=>$val) {
$wishlist[$key]['item_id'] = $val;
$wishlist[$key]['price'] = youarray['price'][$key];
$wishlist[$key]['qty'] = youarray['qty'][$key];
$wishlist[$key]['discount'] = youarray['discount'][$key];
}

PHP: merge multidimensional, associative arrays (LEFT JOIN simulation - keep duplicates, keep non-existent elements)

I got two associative, multidimensional arrays $arrayOffered and $arraySold. I would like to merge them under certain conditions:
if value of key 'item' from $arrayOffered exists in $arraySold, both elements should be included in array $result. If for 1 element from $arrayOffered there are 3 elements in $arraySold, I should get also 3 elements in $result.
otherwise, element from $arrayOffered should be added into $result.
One element from $arrayOffered can have >1 equivalents in $arraySold. They should be joined in the way shown below.
Input data:
$arrayOffered = array(
0 => array('item' => 'product_1', 'Category' => 'ABC'),
1 => array('item' => 'product_2', 'Category' => 'DEF')
);
$arraySold = array(
0 => array('item' => 'product_1', 'ItemsSold' => '2', 'ItemsReturned' => 1), //arrays in this array can contain up to 30 elements
1 => array('item' => 'product_1', 'ItemsSold' => '1')
);
Desired result:
$desiredResult = array(
0 => array('item' => 'product_1', 'Category' => 'ABC', 'ItemsSold' => '2', 'ItemsReturned' => 1),
1 => array('item' => 'product_1', 'Category' => 'ABC', 'ItemsSold' => '1'),
2 => array('item' => 'product_2', 'Category' => 'DEF')
);
I got stuck on something like:
$result = array();
foreach ($arrayOffered as $keyOffered => $offeredSubArr)
{
$item = $offeredSubArr['item'];
foreach($arraySold as $keySold => $soldSubArr)
{
if(isset($soldSubArr['item']) && $soldSubArr['item'] == $item)
{
$i = 0;
$test = array_merge($offeredSubArr, $soldSubArr);
$result[$i][] = $test;
$i++;
}
else
{
$result[$i][] = $offeredSubArr;
$i++;
}
}
}
Problem:
- output array isn't formatted the way I wanted
- I know I'm not going in the right direction. Can you please give me a hint?
This is an option, since you have this $arrayOffered as a kind of master file I suggest to build a hash with this array and use later on the foreach look for sold array.
$arrayOffered = array(
0 => array('item' => 'product_1', 'Category' => 'ABC'),
1 => array('item' => 'product_2', 'Category' => 'DEF')
);
$arraySold = array(
0 => array('item' => 'product_1', 'ItemsSold' => '2', 'ItemsReturned' => 1), //arrays in this array can contain up to 30 elements
1 => array('item' => 'product_1', 'ItemsSold' => '1')
);
//Build a hash to get the extra properties
$hashArray = array();
foreach ($arrayOffered as $offered) {
$hashArray[$offered['item']]=$offered;
}
$resultArray = array();
foreach ($arraySold as $sold) {
$hashItem = $hashArray[$sold['item']];
// you dont want this sold flag on your final result
unset($hashItem['sold']);
$resultArray[]=array_merge($hashItem,$sold);
$hashArray[$sold['item']]['sold']= true;
}
//Add all the missing hash items
foreach($hashArray as $hashItem){
if(!isset($hashItem['sold'])){
$resultArray[]=$hashItem;
}
}
print_r($resultArray);
Test sample
http://sandbox.onlinephpfunctions.com/code/f48ceb3deb328088209fbaef4f01d8d4430478db
$result = array();
foreach ($arrayOffered as $keyOffered => $offeredSubArr)
{
$item = $offeredSubArr['item'];
foreach($arraySold as $keySold => $soldSubArr)
{ $i = 0;
if(isset($soldSubArr['item']) && $soldSubArr['item'] == $item)
{
$test = array_merge($offeredSubArr, $soldSubArr);
$result[$i][] = $test;
}
else
{
$result[$i][] = $offeredSubArr;
}
$i++;
}
}
$result = $result[0];
echo '<pre>'; print_r($result); die();
Well i will try to follow your logic although there is simpler solutions.
First of all we will need to search in a multidimentional array thats why we will need the followed function from this so thread
function in_array_r($needle, $haystack, $strict = false) {
foreach ($haystack as $item) {
if (($strict ? $item === $needle : $item == $needle) || (is_array($item) && in_array_r($needle, $item, $strict))) {
return true;
}
}
return false;
}
Next after small changes:
$i you don't need to make it zero on every loop just once so place it outside
unnecessary [] ($result[$i][]) you don't need the empty brackets no reason to create an extra table in the $i row since what you add there, the $test is already table itself
Adding the last loop coz when sth is not in the second table it will be added in your new table in every loop and as far as i get you don't want that kind of duplicates
We have the following code:
$arrayOffered = array(
0 => array('item' => 'product_1', 'Category' => 'ABC'),
1 => array('item' => 'product_2', 'Category' => 'DEF')
);
$arraySold = array(
0 => array('item' => 'product_1', 'ItemsSold' => '2', 'ItemsReturned' => 1), //arrays in this array can contain up to 30 elements
1 => array('item' => 'product_1', 'ItemsSold' => '1')
);
$i = 0;
$result = array();
foreach ($arrayOffered as $keyOffered => $offeredSubArr)
{
$item = $offeredSubArr['item'];
foreach($arraySold as $keySold => $soldSubArr)
{
if(isset($soldSubArr['item']) && $soldSubArr['item'] == $item)
{
$test = array_merge($offeredSubArr, $soldSubArr);
$result[$i] = $test;
$i++;
}
}
}
foreach ($arrayOffered as $value)
{
if (!in_array_r($value['item'], $result))
{
$result[$i] = $value;
$i++;
}
}
print_r($result);
Which as far as i tested gives the wanted result.

Categories