sort php array by x then y - php

I want to sort an array by 'hits', but I also want to look for a particular ID and set that as the first iteration, then continue the 'hits' sort.
For example, I have a multidimensional array:
$myarray = array(
array(
"id"=>10,
"hits"=>80
),
array(
"id"=>14,
"hits"=>50
),
array(
"id"=>15,
"hits"=>700
),
array(
"id"=>18,
"hits"=>200
)
);
I want to test whether the id is something particular, i.e. if the id==18 then put it first, then sort by hits. How would I do this, using usort and a custom function?
I think I'm looking for something similar to:
function customsort($a,$b){
if($a["id"]==18){ //or b==18?
return -1;
} else {
return $a["hits"]>$b["hits"];
}
}
usort($myarray,"customsort");
The outcome I would like is for the order to be :
array(
"id"=>18,
"hits"=>200
),
array(
"id"=>14,
"hits"=>50
),
array(
"id"=>10,
"hits"=>80
),
array(
"id"=>15,
"hits"=>700
)
(or if they were labelled ABCD then I need it to be DBAC)

The only thing in your code that might make this NOT work is the return $a["hits"]>$b["hits"];. Your function should return 1/-1 only (not true/false), so change that line to: return $a["hits"]>$b["hits"]?1:-1; and it should work as expected.
Sure enough, it works: http://codepad.org/ItyIa7fB

Related

Searching through arrays and Creating a custom array in PHP

I having an issue merging arrays. I have 3 arrays that show particular data. I need a way to search through all three arrays and if the record matches the date I need to store it in a new custom array.
I have 3 arrays that need to merge them using the date
The arrays are as follows.
// ARRAY 1
$transactions = array(
array(
"date"=>"2021-03-01",
"trans_count"=>100,
"trans_amount"=>5300
),
array(
"date"=>"2021-03-02",
"trans_count"=>95,
"trans_amount"=>5035
),
array(
"date"=>"2021-03-03",
"trans_count"=>105,
"trans_amount"=>5565
)
);
// ARRAY 2
$overdrafts = array(
array(
"date"=>"2021-03-01",
"od_amount"=>500
),
array(
"date"=>"2021-03-02",
"od_amount"=>1000
),
);
// ARRAY 3
$payouts= array(
array(
"date"=>"2021-03-02",
"amount"=>2300
)
);
I tried to write a function but I got stuck. The end goal is to collect all records from all 3 arrays and coming up with one array.
the expected result should look like this.
$merged_arr = array(
array(
"date"=>"2021-03-01",
"type"=>"transactions",
"trans_count"=>100,
"trans_amount"=>5300
),
array(
"date"=>"2021-03-01",
"type"=>"overdrafts",
"od_amount"=>500,
),
array(
"date"=>"2021-03-02",
"type"=>"transactions",
"trans_count"=>95,
"trans_amount"=>5035
),
array(
"date"=>"2021-03-02",
"type"=>"overdrafts",
"od_amount"=>1000
),
array(
"date"=>"2021-03-02",
"type"=>"payout",
"od_amount"=>2300
),
);
Not 100% sure of how to add the type for each of the different types automatically in a nice way, I'd suggest just adding the types directly on the initial arrays, or looping through each before merging them together.
As for the merging and sorting it's pretty easy and can be done with built-in functionality.
$merged_arr = array_merge($transactions, $overdrafts, $payouts);
usort($merged_arr, function ($left, $right) {
return new DateTime($left['date']) <=> new DateTime($right['date']);
});
// Demo merged array.
print "<pre>";
var_export($merged_arr);
The sort function will work with PHP 7 and up.

Merge/Append the differences between two 2d arrays to the intersection of the same two arrays to achieve a particular sort order

I have two arrays, the first one is the sorted version (sorted by the user) of the second one, but the second one can be shorter or longer than the first one. For example:
$books_sorted = array(
0 => array(
"title" => "In Search of Lost Time"
),
1 => array(
"title" => "Don Quixote"
),
2 => array(
"title" => "The Great Gatsby"
)
);
$books_available = array(
0 => array(
"title" => "Moby Dick"
),
1 => array(
"title" => "In Search of Lost Time"
),
2 => array(
"title" => "The Great Gatsby"
),
3 => array(
"title" => "War and Peace"
)
);
I need a result array that respects the order set by the user, but removes the missing books from the second array and adds them to the end of all the new books from the second array. Ex.
// "Don Quixote" is not available anymore -> needs to be removed
// "War and Peace" and "Moby Dick" are available -> need to be added both at the end
$books_third_array = array(
0 => array(
"title" => "In Search of Lost Time"
),
1 => array(
"title" => "The Great Gatsby"
),
2 => array(
"title" => "Moby Dick"
),
3 => array(
"title" => "War and Peace"
)
);
I only put the "title" key because there are others, but I don't think they're useful to this example.
You can find all elements in the first array that are in the second, then find all elements in the second that are not in the first - and combine the two. array_filter will help you there. You'll have something like this:
$sorted_titles = array_column($books_sorted, 'title');
$available_titles = array_column($books_available, 'title');
$third_array = array_merge(
array_filter($books_sorted, function($e) use ($available_titles) {
return in_array($e['title'], $available_titles);
}),
array_filter($books_available, function($e) use ($sorted_titles) {
return !in_array($e['title'], $sorted_titles);
})
);
Live demo: https://3v4l.org/fSpWm
Edit based on comments:
If you need to not just preserve other "fields" in your first array, but also copy non-existing keys from the second array into the first one, the code becomes somewhat more complicated. Something like this may do:
$sorted_titles = array_column($books_sorted, 'title');
$available_titles = array_reduce($books_available, function($result, $e) {
$result[$e['title']] = $e;
return $result;
});
$third_array = array_merge(
array_map(
function($e) use ($available_titles) {
return array_merge($available_titles[$e['title']], $e);
},
array_filter($books_sorted, function($e) use ($available_titles) {
return in_array($e['title'], array_keys($available_titles));
})
),
array_filter($books_available, function($e) use ($sorted_titles) {
return !in_array($e['title'], $sorted_titles);
})
);
Live demo: https://3v4l.org/VZGbB
Use usort() and define your own sorting-function, use the use keyword to pass in the array of sorted titles, and if its in the array, move it up - otherwise move it down.
$books_sorted_titles = array_column($books_sorted, 'title');
usort($books_available, function($k, $v) use ($books_sorted_titles) {
return in_array($v['title'], $books_sorted_titles) ? 1 : -1;
});
Live demo at https://3v4l.org/NsPtf
You should avoid making iterated calls of in_array() for performance reasons. When this logic is called for, use array_intersect().
To implement a functional-style approach, isolate the intersections between the arrays with the sorted array as the first parameter so that its order is preserved. Then get the diiferences between the arrays with the available array as the first parameter to return its qualifying elements. Then simply merge the arrays so that the sorted values come before the (un-chosen) available values.
Code: (Demo)
var_export(
array_merge(
array_uintersect(
$books_sorted,
$books_available,
fn($a, $b) => $a['title'] <=> $b['title']
),
array_udiff(
$books_available,
$books_sorted,
fn($a, $b) => $a['title'] <=> $b['title']
),
)
);
I don't know how this alternative snippet will compare in terms of performance, but you can determine the intersections and differences in one pass. This aproach however consumes/unsets elements from the available array. (Demo)
$available = array_column($books_available, 'title');
$result = [];
foreach ($books_sorted as $row) {
if (($index = array_search($row['title'], $available)) !== false) {
$result[] = $row;
unset($books_available[$index]);
}
}
array_push($result, ...$books_available);
var_export($result);

Rewrite 2d array, which contains only some keys

I am using Animate.css for Bootstrap carousel and I need to sort the animations.
I have made an array called $anims, which contains all the animations. I want to make a new variable which contains only entrance animations, for example. So this is my array.
$anims = array(
"Bouncing Entrances" => array(
"bounceIn",
"bounceInDown",
....
),
"Bouncing Exits" => array(
"bounceOut",
"bounceOutDown",
....
),
"Fading Entrances" => array(
"fadeIn",
"fadeInDown",
....
),
......
)
$enrtyAnims = ...
?>
After processing it should look like that:
$anims = array(
"Bouncing Entrances" => array(
"bounceIn",
"bounceInDown",
....
),
"Fading Entrances" => array(
"fadeIn",
....
)
)
But I don't have any idea how to do it with the keys. I want to be able to say I want new array with keys X and Y and it makes it.
You can simply use array_filter function with third flag parameter along with the flag ARRAY_FILTER_USE_KEY so your code looks like as
$result_array = array_filter($anims,function($k){
return (strpos($k,"Entrances") !== false);
},ARRAY_FILTER_USE_KEY);
Note: Flags as third parameter are introduced in PHP versions >= 5.6.0
Demo
Access the desired elements by key and create a new array using these items, like this:
$anims = array(
'Bouncing Entrances' => $anims['Bouncing Entrances'],
'Fading Entrances' => $anims['Fading Entrances'],
// ...
);
EDIT: forgot to preserve the keys.
If there's a certain pattern by which you want to filter the animations, you can use array_filter() function, like #Uchiha said.

Multi-staged sorting of array based on subarray values using comparison functions

I am in a tricky situation where i need to sort an array by values that lie within its subarray, but i need the result to be staged. In other words, sorting should be done by one OR more priorities.
The problem is that the entire sorting process is configurable by the user, so hardcoding anything is not an option. I need to stay flexible, but i limit the options by providing predefined sort functions.
Let's get into it:
In this example we will sort a list of print formats. We will use only two possible properties.
The user configures the sorting process in an INI file:
sort_priority="special_deal:desc,ratio:asc"
Description:
// special_deal -> This is a binary flag - if set to 1 the print format is a special deal and should therefore be presented first
// ratio -> This is the ratio of the given print format (i.e. 0.75 (that's a 3:4 format) or 1 (that's a 1:1 format))
In the code, the configuration is broken apart:
$toSort=array(<OUR ARRAY WE WANT TO SORT>);
$sortKeys=explode(',', 'special_deal:desc,ratio:asc');
// we then iterate through the defined keys
foreach($sortKeys as $sortKey){
// we put together the name of the predefined sort function
if(strstr($sortKey, ':')) {
list($skey,$sdir)=explode(':', $sortKey);
$methodName='sort_by_'.$skey.'_'.$sdir;
} else $methodName='sort_by_'.$sortKey.'_asc';
// so $methodName can (for instance) be: sort_by_special_deal_asc
// or: sort_by_ratio_desc
// if the sort function is available, we apply it
if(is_callable($methodName))
usort($toSort, $methodName);
}
And our sort functions look like this:
function sort_by_special_deal_asc($a, $b){
return ($a['specialDeal']!=$b['specialDeal']);
}
function sort_by_special_deal_desc($a, $b){
return ($a['specialDeal']==$b['specialDeal']);
}
function sort_by_ratio_asc($a, $b){
if($a==$b) return 0;
return $a['ratio']<$b['ratio'] ? -1 : 1;
}
function sort_by_ratio_desc($a, $b){
if($a==$b) return 0;
return $a['ratio']>$b['ratio'] ? -1 : 1;
}
On the the problem at hand ...
The above solution works fine, but only for the last applied sort function. So when we iterate through the sort functions that are to be applied, each call to usort() will cause a reordering of all the elements in the array. The problem is, we want the sorting to be staged (or stacked), so in this given example that would mean, practically:
1.) Sort all entries so that the ones that are a special deal come first
2.) Then sort all entries by their ratio
Here is an example on how the data can look like:
$formats=array(
array(
'format' => '30x40',
'ratio' => 0.75
),
array(
'format' => '60x90',
'ratio' => 0.667
),
array(
'format' => '50x50',
'ratio' => 1
),
array(
'format' => '60x80',
'ratio' => 0.75,
'specialDeal' => 1
)
);
And the desired result, given the above sorting feature, should be:
$formats=array(
array(
'format' => '60x80',
'ratio' => 0.75,
'specialDeal' => 1
),
array(
'format' => '60x90',
'ratio' => 0.667
),
array(
'format' => '30x40',
'ratio' => 0.75
),
array(
'format' => '50x50',
'ratio' => 1
),
);
I hope this explains the issue properly.
Can anybody point me in the right direction here? How can i achieve this, dynamically, at best by using usort() ?
Thank You!
EDIT: Please note - my comparison functions (see above) were faulty. There were two problems:
1.) Returning boolean was wrong - returning -1, 0 or 1 was the way to go.
2.) Comparison of $a and $b as complete arrays/objects was not correct - the right is to compare the specific values within those arrays (the ones the function is supposed to compare).
See the accepted answer and the according comments section for details.
Build an array like this by parsing the user's sort preferences:
$sortMethods = array('sort_by_ratio_desc', 'sort_by_special_deal_asc');
Then sort using a comparison like this:
usort($array, function ($a, $b) use ($sortMethods) {
foreach ($sortMethods as $method) {
$result = $method($a, $b);
if ($result != 0) {
break;
}
}
return $result;
});
Check out the comments for uasort in the php.net manual - http://php.net/manual/en/function.uasort.php
Specifically the one with dynamic callbacks posted by dholmes.

PHP5: Adding stuff into a multidimensional array given a location of any length?

// given following array:
$data = array(
0=>array(
"data"=>"object1",
"col"=>array(
0=>array(
"data"=>"object2",
"col"=>array(
0=>array(
"data"=>"object3",
),
1=>array(
"data"=>"object4",
),
)
),
1=>array(
"data"=>"object5",
"col"=>array()
),
)
)
);
// A new object can be added or existing one replaced given a position of any length
// array(0), array(1,0), array(1,0,0), etc...
$data = add($data, array(0,0,1), "new_object");
function add($data, $position, $object) {
// this has to be produced based the location:
$data[0]["col"][0]["col"][1]["data"] = "new_object";
return $data;
}
Is there a sane way to do it in PHP besides creating $data."[0]["col"][0]["col"][1]["data"]" ?
There must be a better data structure than that tangled array. 6 dimensions is too many in most cases. It seems like there's some repeated structure in there. Perhaps you could capitalize on that? There's no "sane" way, as you put it, to access an insane array.

Categories