Custom sorting array of arrays using two conditions - php

I have the following array:
$arr = [
[
'user_id' => 1,
'product_id' => 1
],
[
'user_id' => 1,
'product_id' => 2
],
[
'user_id' => 1,
'product_id' => 3
],
[
'user_id' => 2,
'product_id' => 1
],
[
'user_id' => 2,
'product_id' => 2
],
[
'user_id' => 3,
'product_id' => 1
]
];
And I want to sort it so it looks like this:
$arr = [
[
'user_id' => 1,
'product_id' => 1
],
[
'user_id' => 2,
'product_id' => 1
],
[
'user_id' => 3,
'product_id' => 1
],
[
'user_id' => 1,
'product_id' => 2
],
[
'user_id' => 2,
'product_id' => 2
],
[
'user_id' => 1,
'product_id' => 3
]
];
So basically I need to order by product_id and user_id in such a way that it selects the lower number product_id from each user before proceeding to the next.
I tried to use usort but I couldn't get it to work.
usort($campaigns, function($a, $b){
if($a['product_id'] == $b['product_id']){
return 0;
}
if($a['product_id'] < $b['product_id']){
if($a['user_id'] == $b['user_id']){
return 1;
}
if($a['user_id'] < $a['user_id']){
return 0;
}
return -1;
}else{
if($a['user_id'] == $a['user_id']){
return -1;
}
if($a['user_id'] < $a['user_id']){
return 0;
}
return 1;
}
});
I also tried array_multisort but all I could get it to do is to order using the same order that I already retrieve from the database.

Assumption is your values is integers:
usort($campaigns, function($a, $b){
if($a['product_id'] == $b['product_id']){
return $a['user_id'] - $b['user_id'];
} else {
return $a['product_id'] - $b['product_id'];
}
});
Also you can use database ordering with ORDER BY product_id, user_id clause.

Solution using array_multisort function with "array of columns"(few sorting dimensions):
$userIds = $productIds = [];
foreach ($arr as $k => $v) {
$userIds[$k] = $v['user_id'];
$productIds[$k] = $v['product_id'];
}
array_multisort($productIds, SORT_ASC, $userIds, SORT_ASC, $arr);
print_r($arr);
The output:
Array
(
[0] => Array
(
[user_id] => 1
[product_id] => 1
)
[1] => Array
(
[user_id] => 2
[product_id] => 1
)
[2] => Array
(
[user_id] => 3
[product_id] => 1
)
[3] => Array
(
[user_id] => 1
[product_id] => 2
)
[4] => Array
(
[user_id] => 2
[product_id] => 2
)
[5] => Array
(
[user_id] => 1
[product_id] => 3
)
)

$arrTags = [
[
'user_id' => 1,
'product_id' => 1
],
[
'user_id' => 1,
'product_id' => 2
],
[
'user_id' => 1,
'product_id' => 3
],
[
'user_id' => 2,
'product_id' => 1
],
[
'user_id' => 2,
'product_id' => 2
],
[
'user_id' => 3,
'product_id' => 1
]
];
foreach($arrTags as $key => $row){
$userArray[$key] = $row['user_id'];
$productArray[$key] = $row['product_id'];
}
array_multisort($productArray, SORT_ASC, $userArray, SORT_ASC, $arrTags);
print_r($arrTags);
Output
Array
(
[0] => Array
(
[user_id] => 1
[product_id] => 1
)
[1] => Array
(
[user_id] => 2
[product_id] => 1
)
[2] => Array
(
[user_id] => 3
[product_id] => 1
)
[3] => Array
(
[user_id] => 1
[product_id] => 2
)
[4] => Array
(
[user_id] => 2
[product_id] => 2
)
[5] => Array
(
[user_id] => 1
[product_id] => 3
)
)
You can also check in online editor. Click Here

Related

function to make Tree like Array in to Array

I have $tree like array which made by function from the $array I need a function to make it $tree to $array back
so I need a function to make it back.
lets call it $list this time.
I will share $tree , $array and the function in below
tree like array ;
Array
(
[0] => Array
(
[id] => 1
[name] => id1
[children] => Array
(
[0] => Array
(
[id] => 2
[parent_id] => 1
[name] => id2
[children] => Array
(
[0] => Array
(
[id] => 5
[parent_id] => 2
[name] => id5
)
)
)
[1] => Array
(
[id] => 3
[parent_id] => 1
[name] => id3
[children] => Array
(
[0] => Array
(
[id] => 6
[parent_id] => 3
[name] => id6
)
[1] => Array
(
[id] => 8
[parent_id] => 3
[name] => id8
)
)
)
)
)
[1] => Array
(
[id] => 4
[name] => id4
[children] => Array
(
[0] => Array
(
[id] => 9
[parent_id] => 4
[name] => id9
[children] => Array
(
[0] => Array
(
[id] => 10
[parent_id] => 9
[name] => id10
)
)
)
)
)
[2] => Array
(
[id] => 7
[name] => id7
[children] => Array
(
)
)
)
Which made by a function from this array
$array = [
['id'=> 1, 'parent_id' => 0, 'name' => 'id1'],
['id' => 2, 'parent_id' => 1, 'name'=> 'id2'],
['id' => 3, 'parent_id' => 1, 'name'=> 'id3'],
['id' => 4, 'parent_id' => 0, 'name'=> 'id4'],
['id' => 5,'parent_id' => 2, 'name'=> 'id5'],
['id' => 6, 'parent_id' => 3, 'name'=> 'id6'],
['id' => 7, 'parent_id' => 0, 'name'=> 'id7'],
['id' => 8, 'parent_id' => 3, 'name'=> 'id8'],
['id' => 9, 'parent_id' => 4, 'name'=> 'id9'],
['id' => 10, 'parent_id' => 9, 'name'=> 'id10'],
];
function(making $array in to $tree)
$tree = [];
function buildTree (array $infos, int $parent_Id = null): array
{
$branch = [];
foreach ($infos as $info)
if($info['parent_id'] === $parent_Id){
$children = buildTree($infos , $info['id']);
if ($children){
$info['children'] = $children;
}
$branch[] = $info;
}
return $branch;
}
foreach ($array as $info){
if($info['parent_id']=== 0){
$tree[] = [
'id' => $info['id'],
'name' => $info['name'],
'children' => buildTree($array , $info['id']),
];
}
}
print_r($tree);
any explanation would be appreciated.
Result of this code can be found here: http://sandbox.onlinephpfunctions.com/code/38a091db5ace63900fa0bf69ddde17412118513c
function flatMergeArray(array $array, int $parentId = 0, array &$result = []): array
{
$subResult = [];
foreach ($array as $key => $sub) {
$parentId = $array['parent_id'] ?? 0;
if (is_array($sub)) {
flatMergeArray($sub, $parentId, $result);
} else {
$subResult[$key] = $sub;
}
}
if (!empty($subResult)) {
if (!isset($subResult['parent_id'])) {
$subResult['parent_id'] = 0;
}
$result[] = $subResult;
}
return $result;
}
function flatTree(array $tree): array
{
$array = flatMergeArray($tree);
usort($array, static function (array $node1, array $node2) {
return ($node1['id'] < $node2['id']) ? -1 : 1;
});
return array_values($array);
}
$tree = [
[
"id" => 1,
"name" => "id1",
"children" => [
[
"id" => 2,
"parent_id" => 1,
"name" => "id2",
"children" => [
[
"id" => 5,
"parent_id" => 2,
"name" => "id5"
]
]
],
[
"id" => 3,
"parent_id" => 1,
"name" => "id3",
"children" => [
[
"id" => 6,
"parent_id" => 3,
"name" => "id6"
],
[
"id" => 8,
"parent_id" => 3,
"name" => "id8"
]
]
]
]
],
[
"id" => 4,
"name" => "id4",
"children" => [
[
"id" => 9,
"parent_id" => 4,
"name" => "id9",
"children" => [
[
"id" => 10,
"parent_id" => 9,
"name" => "id10"
]
]
]
]
],
[
"id" => 7,
"name" => "id7",
"children" => [
]
]
];
$array = flatTree($tree);
print_r($array);

How to simplify this function and make it universal for n-level depth?

I seem to struggle with (for me) complex recursive functions. Could anyone point me in the right direction so that I can simplify this messy and repetitive code? Also, it's meant to work for n-level deep.
$hasProducts = array();
function filterOutEmpty($levelZero) {
global $hasProducts;
foreach ($levelZero as $levelZeroK => $levelZeroV) {
if (empty($levelZeroV['products']) && empty($levelZeroV['families'])) continue;
if (!empty($levelZeroV['products'])) {
$hasProducts[$levelZeroK] = $levelZeroV;
}
if (!empty($levelZeroV['families'])) {
foreach ($levelZeroV['families'] as $levelOneK => $levelOneV) {
if (empty($levelOneV['products']) && empty($levelOneV['families'])) continue;
if (!empty($levelOneV['products'])) {
$hasProducts[$levelZeroK]['families'][$levelOneK] = $levelOneV;
}
if (!empty($levelOneV['families'])) {
foreach ($levelOneV['families'] as $levelTwoK => $levelTwoV) {
if (empty($levelTwoV['products']) && empty($levelTwoV['families'])) continue;
if (!empty($levelTwoV['products'])) {
$hasProducts[$levelZeroK]['families'][$levelOneK]['families'][$levelTwoK] = $levelTwoV;
}
}
}
}
}
}
}
Sample input could be as follows:
$districts = [
1 => [
'id' => 1,
'families' => [
0 => [
'id' => 2
],
1 => [
'id' => 3,
'families' => [
0 => [
'id' => 2,
'products' => [
1 => 'Arnold'
],
],
1 => [
'id' => 2,
'products' => [],
],
]
],
]
],
2 => [
'id' => 1,
'families' => [
0 => [
'id' => 2,
'products' => [
1 => 'John Doe'
],
],
1 => [
'id' => 3,
'products' => [],
],
]
],
3 => [
'id' => 1,
'products' => [
1 => 'Hi',
2 => 'Hello',
]
],
4 => [
'id' => 1,
'families' => [
0 => [
'id' => 2
],
1 => [
'id' => 3
],
]
],
];
The desired output:
(
[1] => Array
(
[families] => Array
(
[1] => Array
(
[families] => Array
(
[0] => Array
(
[id] => 2
[products] => Array
(
[1] => Arnold
)
)
)
)
)
)
[2] => Array
(
[families] => Array
(
[0] => Array
(
[id] => 2
[products] => Array
(
[1] => John Doe
)
)
)
)
[3] => Array
(
[id] => 1
[products] => Array
(
[1] => Hi
[2] => Hello
)
)
)
Please, keep in mind, that the array can be nested x-levels deep, this is just as an example. With each level I would need to add another if check (!empty($level{SOME_LEVEL}V['families'])) { ... the code again for a particular level... }
This code uses a recursive function to parse each level, so the number of levels doesn't matter. Each time it enters the routine it gets passed in the current level and will return the representation of that level in the new format ( I think ).
The one part is that after each recursive call, it checks if the return value is empty and only adds it in if it contains something...
function filterOutEmpty($levelZero) {
$hasProducts = [];
foreach ($levelZero as $levelZeroK => $levelZeroV) {
if (!empty($levelZeroV['products'])) {
$hasProducts[$levelZeroK] = $levelZeroV;
}
else if ( is_array($levelZeroV)) {
$new = filterOutEmpty($levelZeroV);
if ( !empty($new) ) {
$hasProducts[$levelZeroK] = $new;
}
}
if (!empty($levelZeroV['families'])) {
$new = filterOutEmpty($levelZeroV['families']);
if ( !empty($new) ) {
$hasProducts[$levelZeroK]['families'] = $new;
}
}
}
return $hasProducts;
}
print_r(filterOutEmpty($districts));

PHP Sort by strnatcmp and by another specific keyword

I need to sort an multi dimensional array which looks like this down below (usort already applied).
I need to move '10000_M3' to the first key if an identifier '_M' was found (as natural sorting), but the order from 'first' sort should not be touched.
Extra: (Optional) If i have a description like '10000_0', it should be still in index 0
If the usort can be done in 1 step would be great
I take any solution (usort, foreach, ..)!!!
usort($anArray, function ($a, $b) {
return strnatcmp($a['description'], $b['description']);
});
$anArray = [
// ? => [
// 'description' => '10000_0'
// ]
0 => [
'description' => '10000_D2'
],
1 => [
'description' => '10000_D3'
],
2 => [
'description' => '10000_M3'
],
3 => [
'description' => '10000_M4'
]
]
Result (Natural -> at first position = '_0' -> $ident = '_M' after '_0' if its exists):
$result = [
0 => [
'description' => '10000_0'
]
1 => [
'description' => '10000_M3'
],
2 => [
'description' => '10000_M4'
]
3 => [
'description' => '10000_D2'
],
4 => [
'description' => '10000_D3'
],
]
You can modify your sort function to check for the special cases individually then fall back on a regular sorting method.
<?php
$inputArray = [
0 => [
'description' => '10000_D2'
],
1 => [
'description' => '10000_D3'
],
2 => [
'description' => '10000_M3'
],
3 => [
'description' => '10000_M4'
],
4 => [
'description' => '10000_0'
]
];
usort($inputArray, function ($a, $b) {
// _0 first then _M* then alphabetic
//assume only 1 value will be _0?
if (preg_match('/_0$/', $a['description']) === 1){
//"a" ends in _0
return -1;
}
if (preg_match('/_0$/', $b['description']) === 1){
//"b" ends in _0
return 1;
}
if (
preg_match('/_M\d*$/', $a['description']) === 1
&& preg_match('/_M\d*$/', $b['description']) === 1
){
//both have "M" so sort them normally
return strnatcmp($a['description'], $b['description']);
}
if (preg_match('/_M\d*$/', $a['description']) === 1){
//only "a" has _M
return -1;
}
if (preg_match('/_M\d*$/', $b['description']) === 1){
//only "b" has _M
return 1;
}
//neither side has _M or _0 so normal sorting
return strnatcmp($a['description'], $b['description']);
});
echo print_r($inputArray, true);
?>
Output:
Array
(
[0] => Array
(
[description] => 10000_0
)
[1] => Array
(
[description] => 10000_M3
)
[2] => Array
(
[description] => 10000_M4
)
[3] => Array
(
[description] => 10000_D2
)
[4] => Array
(
[description] => 10000_D3
)
)
I'm assuming you will only have a single _0 value. If you could have multiple _0 values then you need to modify the above code to behave like the 3 _M "if" statements.
You can try this :
$anArray = [
0 => [
'description' => '10000_D2'
],
1 => [
'description' => '10000_D3'
],
2 => [
'description' => '10000_M3'
],
3 => [
'description' => '10000_M4'
]
, 4 => [
'description' => '10000_0'
]
, 5 => [
'description' => '10000_15'
]
, 6 => [
'description' => '10000_789'
]
];
usort($anArray, function ($a, $b) {
$tmpa=explode('_',$a['description']);
$tmpb=explode('_',$b['description']);
if(ctype_digit($tmpa[1])&&!ctype_digit($tmpb[1]))
return -1;
if(!ctype_digit($tmpa[1])&&ctype_digit($tmpb[1]))
return 1;
if($tmpa[1][0]==='M'&&$tmpb[1][0]!=='M')
return -1;
if($tmpa[1][0]!=='M'&&$tmpb[1][0]==='M')
return 1;
return strnatcmp($a['description'], $b['description']);
});
print_r($anArray);
and the output is:
Array
(
[0] => Array
(
[description] => 10000_0
)
[1] => Array
(
[description] => 10000_15
)
[2] => Array
(
[description] => 10000_789
)
[3] => Array
(
[description] => 10000_M3
)
[4] => Array
(
[description] => 10000_M4
)
[5] => Array
(
[description] => 10000_D2
)
[6] => Array
(
[description] => 10000_D3
)
)

PHP - (Almost) Flatten multidimensional array recursively

I am trying to change the following array to an almost flat array. So id 4 would be in the first level of the array, as would id 6 and 5, but still have their own index so I can tell which page is which. But with the same order as they have now. I presume that the solution would be some sort of recursive PHP function but I haven't a clue how to do this.
Array
(
[0] => Array
(
[id] => 2
[identifier] => External URL
[parent] => 0
[sortOrder] => 1
[depth] => 0
)
[1] => Array
(
[id] => 3
[identifier] => First Team
[parent] => 0
[sortOrder] => 2
[depth] => 0
[children] => Array
(
[0] => Array
(
[id] => 4
[identifier] => League tables
[parent] => 3
[sortOrder] => 0
[depth] => 1
[children] => Array
(
[0] => Array
(
[id] => 6
[identifier] => British and Irish Cup Tables
[parent] => 4
[sortOrder] => 24
[depth] => 2
)
[1] => Array
(
[id] => 5
[identifier] => Greene King IPA Championship
[parent] => 4
[sortOrder] => 25
[depth] => 2
)
)
)
)
)
[2] => Array
(
[id] => 1
[identifier] => Home
[parent] => 0
[sortOrder] => 25
[depth] => 0
)
)
<?php
$data = [
[
'id' => 1,
'name' => 'one',
'children' =>
[
[
'id' => 2,
'name' => 'two',
'children' =>
[
[
'id' => 4,
'name' => 'four'
]
]
],
[
'id' => 3,
'name' => 'three',
'children' =>
[
[
'id' => 5,
'name' => 'five'
]
]
]
]
],
[
'id' => 6,
'name' => 'six'
]
];
$stanley = [];
$flatten = function(array $data) use (&$flatten, &$stanley) {
foreach($data as $k => $v) {
if(isset($v['children'])) {
$flatten($v['children']);
unset($v['children']);
}
$stanley[] = $v;
}
};
$flatten($data);
var_export($stanley);
Output:
array (
0 =>
array (
'id' => 4,
'name' => 'four',
),
1 =>
array (
'id' => 2,
'name' => 'two',
),
2 =>
array (
'id' => 5,
'name' => 'five',
),
3 =>
array (
'id' => 3,
'name' => 'three',
),
4 =>
array (
'id' => 1,
'name' => 'one',
),
5 =>
array (
'id' => 6,
'name' => 'six',
),
)
I have found the solution! I built a recursive PHP function which utilised the depth index to track which level each of the items are while still keeping the array flat (ish).
function dropdownNavigationTree($array) {
$response = [];
foreach($array as $page) {
if (!is_array($page['children'])) {
$response[$page['id']] = ($page['depth'] > 0 ? str_repeat("-", $page['depth']).' ' : FALSE).$page['identifier'];
} else {
$response[$page['id']] = ($page['depth'] > 0 ? str_repeat("-", $page['depth']).' ' : FALSE).$page['identifier'];
$children = dropdownNavigationTree($page['children']);
$response = $response + $children;
}
}
return $response;
}

Have the following array merged

I have the following array:
Array
(
[0] => Array
(
[Vendor_ID] => 1
[Quantity] => 55
)
[1] => Array
(
[Vendor_ID] => 1
[Quantity] => 55
)
[2] => Array
(
[Vendor_ID] => 1
[Quantity] => 55
)
[3] => Array
(
[Vendor_ID] => 3
[Quantity] =>
)
[4] => Array
(
[Vendor_ID] => 3
[Quantity] =>
)
[5] => Array
(
[Vendor_ID] => 3
[Quantity] =>
)
[6] => Array
(
[Vendor_ID] => 4
[Quantity] =>
)
[7] => Array
(
[Vendor_ID] => 4
[Quantity] =>
)
[8] => Array
(
[Vendor_ID] => 4
[Quantity] =>
)
)
Which is being created with the following code:
$Display_Arr = array();
$Tick = 0;
foreach ($_POST['product'] AS $_1){
if (!in_array($_1['vendor_id'], $Display_Arr)){
$Display_Arr[$Tick] = array(
"Vendor_ID" => $_1['vendor_id'],
"Quantity" => ""
);
$Display_Arr[$Tick]["Quantity"] .= $_1['quantity'];
}else{
$Display_Arr[$Tick]["Quantity"] .= $_1['quantity'];
}
++$Tick;
}
echo "<pre>";
print_r($Display_Arr);
echo "</pre>";
But I am not getting my desired output, which is:
Array
(
[0] => Array
(
[Vendor_ID] => 1
[Quantity] => 55,55,55
)
[1] => Array
(
[Vendor_ID] => 3
[Quantity] =>
)
[2] => Array
(
[Vendor_ID] => 4
[Quantity] =>
)
)
Where am I going wrong with this?
#mathielo
The current output is:
Array
(
[1] => Array
(
[Vendor_ID] => 1
[Quantity] => 55
)
[3] => Array
(
[Vendor_ID] => 3
[Quantity] =>
)
[4] => Array
(
[Vendor_ID] => 4
[Quantity] =>
)
)
Whereas, i'm trying to obtain:
[0] => Array
(
[Vendor_ID] => 1
[Quantity] => 55,55,55
)
If I got it right, what you need is this:
EDIT: Just made some tests and got it right this time:
$Display_Arr = array();
foreach ($_POST['product'] AS $_1){
if (!array_key_exists($_1['Vendor_ID'], $Display_Arr)){
$Display_Arr[$_1['Vendor_ID']] = array(
"Vendor_ID" => $_1['Vendor_ID'],
"Quantity" => $_1['Quantity']
);
}else{
if(!empty($_1['Quantity']))
$Display_Arr[$_1['Vendor_ID']]["Quantity"] .= ",{$_1['Quantity']}";
}
}
echo "<pre>";
print_r($Display_Arr);
echo "</pre>";
The main problem was in if (!in_array($_1['vendor_id'], $Display_Arr)). PHP's in_array() checks for given needle in the array values, and we were trying to match to the Vendor_ID value stored in the outer array keys. That was fixed using array_key_exists().
EDIT 2: I used this for test data:
$_POST['product'] = array(
0 => array(
'Vendor_ID' => 1,
'Quantity' => 55
),
1 => array(
'Vendor_ID' => 1,
'Quantity' => 55
),
2 => array(
'Vendor_ID' => 1,
'Quantity' => 55
)
,
3 => array(
'Vendor_ID' => 3,
'Quantity' => ''
),
4 => array(
'Vendor_ID' => 3,
'Quantity' => ''
),
5 => array(
'Vendor_ID' => 3,
'Quantity' => ''
),
6 => array(
'Vendor_ID' => 4,
'Quantity' => ''
),
7 => array(
'Vendor_ID' => 4,
'Quantity' => ''
),
8 => array(
'Vendor_ID' => 4,
'Quantity' => ''
)
);
You won't be needing $Tick anymore, as you could use Vendor_ID as keys for the outer array.
Looks like the easiest way to solve this would be to first aggregate the vendor quantities, and then build the final array with the keys that you are using.
I am assuming the input looks something like this:
$_POST['product'] = [
['vendor_id' => 1, 'quantity' => 55],
['vendor_id' => 1, 'quantity' => 55],
['vendor_id' => 1, 'quantity' => 55],
['vendor_id' => 3, 'quantity' => null],
['vendor_id' => 3, 'quantity' => null],
['vendor_id' => 3, 'quantity' => null],
['vendor_id' => 4, 'quantity' => null],
['vendor_id' => 4, 'quantity' => null],
['vendor_id' => 4, 'quantity' => null],
];
Aggregating the quantities:
$aggregates = [];
foreach ($_POST['product'] as $product) {
$id = $product['vendor_id'];
if ( ! isset($aggregates[$id])) {
$aggregates[$id] = [];
}
if ($product['quantity'] > 0) {
$aggregates[$id][] = $product['quantity'];
}
}
The aggregates array should now look like this:
$aggregates = [
1 => [
0 => 55,
1 => 55,
2 => 55,
],
3 => [], // Empty
4 => [], // Empty
];
As you can see the data is now neatly organized and ready to be put into any format you want. Using the keys that you use in your question it is as simple as:
$output = [];
foreach ($aggregates as $vid => $qty) {
$quantity = implode(',', $qty);
$output[] = ['Vendor_ID' => $vid, 'Quantity' => $quantity];
}
The output should now look like this:
$output = [
['Vendor_ID' => 1, 'Quantity' => '55,55,55'],
['Vendor_ID' => 3, 'Quantity' => ''],
['Vendor_ID' => 4, 'Quantity' => ''],
];
This will output exactly what you are looking for although the output of the last answer (Sverri M. Olsen) is more useful. Here you get the quantities as a string while with Sverri's method you get an array in first place.
$Display_Arr = array();
$vendors=array();
foreach ($_POST['product'] AS $_1){
if (!in_array($_1['vendor_id'],$vendors)){
$vendors[]=$_1['vendor_id'];
$Display_Arr[sizeof($vendors)-1] = array(
"Vendor_ID" => $_1['vendor_id'],
"Quantity" => $_1['quantity']
);
}
else{
$vendorKey=array_search($_1['vendor_id'],$vendors);
$Display_Arr[$vendorKey]["Quantity"] .=(!empty($Display_Arr[$vendorKey]["Quantity"])?',':null).$_1['quantity'];
}
}

Categories