Using the numbers from $ids, I want to pull the data from $nuts.
So for example:
$ids = [0,3,5]; // 0 calories, 3 sugar, 5 fat
$nuts = [
'calories' => 'cal',
'protein' => 'pro',
'carbohydrate' => 'car',
'sugar' => 'sug',
'fiber' => 'fib',
'fat' => 'fat',
];
$returnData = [
'calories' => 'cal',
'sugar' => 'sug',
'fat' => 'fat',
];
I could loop through each $ids number with a foreach(); but I'm curious to see if there is a better method than this?
$newNuts = array_values(array_flip($nuts));
foreach($ids as $i)
$returnData[$newNuts[$i]] = $nuts[$newNuts[$i]];
I did some work and realized, you don't need array_flip, array_values is fine.
$num_nuts = array_values ($nuts);
for ($z=0; $z<sizeof($ids); $z++) {
echo $num_nuts[$ids[$z]];
}
Just 1 more line of code, but I think it does the job. I think mine is going to be faster because the array_flip basically exchanges all keys with their associated values in an array, which is not what I am doing. It's actually one less pain.
I am simply converting the original array to a new one by index and simply looping upon it. Also, not the elegant way to use the power of PHP available to us, but works just fine. array_flip is O(n), but I think better not use it for larger data-sets.
How about a simple array_slice?
$result = array();
foreach ($ids as $i) {
$result += array_slice($nuts, $i, 1, true);
}
No need to create a copy of the array.
PHP's array is very flexible and useful. I counted over 30 array functions on the PHP.net array reference page. Some of them can solve my problem, but I'm looking for the best, most elegant way.
I have 2 arrays, called labor and cost, each containing a table:
labor = array(
0 => array('date' => date, 'labor'=> labor),
1 => array('date' => date, 'labor'=> labor),
...
);
cost = array(
0 => array('date' => date, 'cost'=> cost),
1 => array('date' => date, 'cost'=> cost),
...
);
My problem is that sometimes the number of dates don't match (i.e., there are days when you've incurred costs, even though you spent nothing on labor, or days when you had labor but no cost) - that means there are more lines in one array then the next - no way to know which has without using count().
What I'm interested in are only the days that had both labor and cost and I want to end up with an array:
laborcost = array(
0 => array('date' => date, 'labor'=> labor, 'cost' => cost),
1 => array('date' => date, 'labor'=> labor, 'cost' => cost),
...
)
I thought about using array_intersect() or one of the 'u' functions, but ended totally mixed up. Before giving up and writing my own array scanning function, I wanted to see if there are any ideas that will solve my issue with 1, possibly 2, lines of code.
There's is no intersect function accepting a user-defined comparison function that allows you to modify the arrays. The simplest way is just to do it yourself.
Here are a few examples:
O(2n + m)
// Remap dates as keys for faster lookups
$result = $nlabor = $ncost = array();
foreach ($labor as $l) $nlabor[$l['date']] = $l;
foreach ($cost as $c) $ncost[$c['date']] = $c;
// Compare
foreach ($nlabor as $date => $l) {
if (array_key_exists($date, $ncost)) {
$result[] = array_merge($l, $ncost[$date]);
}
}
~O(n * m)
// Just compare them all
$result = array();
foreach ($labor as $l) {
foreach ($cost as $c) {
if ($l['date'] == $c['date']) {
$result[] = array_merge($l, $c);
break;
}
}
}
Which way is the best depends on how many elements you have in each array. When used on smaller arrays ~O(n * m) is fine, while on bigger arrays O(2n + m) will be more efficient.
This should do that trick. Not quite as simple as a single function.
$merge = array();
for ($i = 0; $i < count($labor); $i++) {
array_push($merge, array_merge($labor[$i],$cost[$i]));
}
I have an array of records from a database (although the database is irrelevant to this question -- it eventually becomes an array of "rows", each row is an array with string keys corresponding to the field name). For example:
$items = array(
1 => array('id' => 1, 'name' => 'John', 'created' => '2011-08-14 8:47:39'),
2 => array('id' => 2, 'name' => 'Mike', 'created' => '2011-08-30 16:00:12'),
3 => array('id' => 5, 'name' => 'Jane', 'created' => '2011-09-12 2:30:00'),
4 => array('id' => 7, 'name' => 'Mary', 'created' => '2011-09-14 1:18:40'),
5 => array('id' => 16, 'name' => 'Steve', 'created' => '2011-09-14 3:10:30'),
//etc...
);
What I want to do is shuffle this array, but somehow give more "weight" to items with a more recent "created" timestamp. The randomness does not have to be perfect, and the exact weight does not really matter to me. In other words, if there's some fast and simple technique that kinda-sorta seems random to humans but isn't mathematically random, I'm okay with that. Also, if this is not easy to do with an "infinite continuum" of timestamps, it would be fine with me to assign each record to a day or a week, and just do the weighting based on which day or week they're in.
A relatively fast/efficient technique is preferable since this randomization will occur on every page load of a certain page in my website (but if it's not possible to do efficiently, I'm okay with running it periodically and caching the result).
You can use eg. this comparison function:
function cmp($a, $b){
$share_of_a = $a['id'];
$share_of_b = $b['id'];
return rand(0, ($share_of_a+$share_of_b)) > $share_of_a ? 1 : -1;
}
and then use it like this:
usort($items, 'cmp');
It compares two elements of an array based on their IDs (it is easier and they are assigned based on the date of creation - newer elements have bigger IDs). The comparison is done randomly, with different chances of success for each element, giving more chances to the newer elements. The bigger the ID (the newer the element), the more chances it has to appear at the beginning.
For example element with id=16 has 16x more chances than element id=1 to appear earlier on the resulting list.
What about splitting it up into chunks by date, randomizing each chunk, and then putting them back together as one list?
//$array is your array
$mother=array();
foreach($array as $k->$v) $mother[rand(0,count($array))][$k]=$v;
ksort($mother);
$child=array();
foreach($mother as $ak->$av)
foreach($av as $k->$v) $child[$k]=$v;
$array=$child;
or you can use shuffle()
After being partially inspired by the response from #Tadeck , I came up with a solution. It's kind of long-winded, if anyone could simplify it that would be great. But it seems to work just fine:
//Determine lowest and highest timestamps
$first_item = array_slice($items, 0, 1);
$first_item = $first_item[0];
$min_ts = strtotime($first_item['created']);
$max_ts = strtotime($first_item['created']);
foreach ($items as $item) {
$ts = strtotime($item['created']);
if ($ts < $min_ts) {
$min_ts = $ts;
}
if ($ts > $max_ts) {
$max_ts = $ts;
}
}
//bring down the min/max to more reasonable numbers
$min_rand = 0;
$max_rand = $max_ts - $min_ts;
//Create an array of weighted random numbers for each item's timestamp
$weighted_randoms = array();
foreach ($items as $key => $item) {
$random_value = mt_rand($min_rand, $max_rand); //use mt_rand for a higher max value (plain old rand() maxes out at 32,767)
$ts = strtotime($item['created']);
$ts = $ts - $min_ts; //bring this down just like we did with $min_rand and $max_rand
$random_value = $random_value + $ts;
$weighted_randoms[$key] = $random_value;
}
//Sort by our weighted random value (the array value), with highest first.
arsort($weighted_randoms, SORT_NUMERIC);
$randomized_items = array();
foreach ($weighted_randomsas $item_key => $val) {
$randomized_items[$item_key] = $items[$item_key];
}
print_r($randomized_items);
I'm looking for a way to sort an array, based on the information in each row, based on the information in certain cells, that I'll call columns.
Each row has columns that must be sorted based on the priority of: timetime, lapcount & timestamp.
Each column cotains this information: split1, split2, split3, laptime, lapcount, timestamp.
laptime if in hundredths of a second. (1:23.45 or 1 Minute, 23 Seconds & 45 Hundredths is 8345.) Lapcount is a simple unsigned tiny int, or unsigned char. timestamp is unix epoch.
The lowest laptime should be at the get a better standing in this sort. Should two peoples laptimes equal, then timestamp will be used to give the better standing in this sort. Should two peoples timestamp equal, then the person with less of a lapcount get's the better standing in this sort.
By better standing, I mean closer to the top of the array, closer to the index of zero where it a numerical array.
I think the array sorting functions built into php can do this with a callback, I was wondering what the best approch was for a weighted sort like this would be.
<?php
$racers = array('Dygear', 'filur', 'glyphon', 'AndroidXP', 'HorsePower', 'Becky Rose', 'kanutron', 'Victor');
foreach ($racers as $racer)
$query[$racer] = unserialize(file_get_contents('http://lfsworld.net/pubstat/get_stat2.php?version=1.4&ps=1&idk=35cP2S05Cvj3z7564aXKyw0Mqf1Hhx7P&s=2&action=pb&racer='.urlencode($racer)));
foreach ($query as $racer => $data)
{
foreach ($data as $row)
{
if ($row['track'] == '000' && $row['car'] == 'FOX')
$sortArray[$racer] = $row;
}
}
# Sort the Array $sortArray
var_dump($sortArray);
?>
What you want is a Schwartzian transform. Create a new array with the values to sort by as the key and the original values as the values, sort the array by key, then strip away the keys.
EDIT:
Quick-and-dirty implementation:
$times = Array(
Array('val1' => 1, 'val2' => 3, 'val3' => 8),
Array('val1' => 1, 'val2' => 2, 'val3' => 5),
Array('val1' => 3, 'val2' => 8, 'val3' => 6),
Array('val1' => 2, 'val2' => 2, 'val3' => 1),
Array('val1' => 4, 'val2' => 7, 'val3' => 3)
);
$timesdec = Array();
foreach($times as $time)
{
$timesdec[sprintf('%010d%010d', $time['val1'], $time['val3'])] = $time;
}
ksort($timesdec);
$newtimes = array_values($timesdec);
var_dump($newtimes);
This is untested, and I felt like using php 5.3. But, you can do the same without anonymous functions. You don't really need to use separate functions for each of the column comparisons like I did. But, it seems like you're really trying to replicate the way a database sorts multiple columns, so I'm giving you something close to it.
$comparatorSequence = array(
function($a, $b) {
return $a['laptime'] - $b['laptime'];
}
, function($a, $b) {
return $a['timestamp'] - $b['timestamp'];
}
, function($a, $b) {
return $a['lapcount'] - $b['lapcount'];
}
);
usort($theArray, function($a, $b) use ($comparatorSequence) {
foreach ($comparatorSequence as $cmpFn) {
$diff = call_user_func($cmpFn, $a, $b);
if ($diff !== 0) {
return $diff;
}
}
return 0;
});
This question already has answers here:
How can I sort arrays and data in PHP?
(14 answers)
Closed 8 years ago.
I have CSV data loaded into a multidimensional array. In this way each "row" is a record and each "column" contains the same type of data. I am using the function below to load my CSV file.
function f_parse_csv($file, $longest, $delimiter)
{
$mdarray = array();
$file = fopen($file, "r");
while ($line = fgetcsv($file, $longest, $delimiter))
{
array_push($mdarray, $line);
}
fclose($file);
return $mdarray;
}
I need to be able to specify a column to sort so that it rearranges the rows. One of the columns contains date information in the format of Y-m-d H:i:s and I would like to be able to sort with the most recent date being the first row.
Introducing: a very generalized solution for PHP 5.3+
I 'd like to add my own solution here, since it offers features that other answers do not.
Specifically, advantages of this solution include:
It's reusable: you specify the sort column as a variable instead of hardcoding it.
It's flexible: you can specify multiple sort columns (as many as you want) -- additional columns are used as tiebreakers between items that initially compare equal.
It's reversible: you can specify that the sort should be reversed -- individually for each column.
It's extensible: if the data set contains columns that cannot be compared in a "dumb" manner (e.g. date strings) you can also specify how to convert these items to a value that can be directly compared (e.g. a DateTime instance).
It's associative if you want: this code takes care of sorting items, but you select the actual sort function (usort or uasort).
Finally, it does not use array_multisort: while array_multisort is convenient, it depends on creating a projection of all your input data before sorting. This consumes time and memory and may be simply prohibitive if your data set is large.
The code
function make_comparer() {
// Normalize criteria up front so that the comparer finds everything tidy
$criteria = func_get_args();
foreach ($criteria as $index => $criterion) {
$criteria[$index] = is_array($criterion)
? array_pad($criterion, 3, null)
: array($criterion, SORT_ASC, null);
}
return function($first, $second) use (&$criteria) {
foreach ($criteria as $criterion) {
// How will we compare this round?
list($column, $sortOrder, $projection) = $criterion;
$sortOrder = $sortOrder === SORT_DESC ? -1 : 1;
// If a projection was defined project the values now
if ($projection) {
$lhs = call_user_func($projection, $first[$column]);
$rhs = call_user_func($projection, $second[$column]);
}
else {
$lhs = $first[$column];
$rhs = $second[$column];
}
// Do the actual comparison; do not return if equal
if ($lhs < $rhs) {
return -1 * $sortOrder;
}
else if ($lhs > $rhs) {
return 1 * $sortOrder;
}
}
return 0; // tiebreakers exhausted, so $first == $second
};
}
How to use
Throughout this section I will provide links that sort this sample data set:
$data = array(
array('zz', 'name' => 'Jack', 'number' => 22, 'birthday' => '12/03/1980'),
array('xx', 'name' => 'Adam', 'number' => 16, 'birthday' => '01/12/1979'),
array('aa', 'name' => 'Paul', 'number' => 16, 'birthday' => '03/11/1987'),
array('cc', 'name' => 'Helen', 'number' => 44, 'birthday' => '24/06/1967'),
);
The basics
The function make_comparer accepts a variable number of arguments that define the desired sort and returns a function that you are supposed to use as the argument to usort or uasort.
The simplest use case is to pass in the key that you 'd like to use to compare data items. For example, to sort $data by the name item you would do
usort($data, make_comparer('name'));
See it in action.
The key can also be a number if the items are numerically indexed arrays. For the example in the question, this would be
usort($data, make_comparer(0)); // 0 = first numerically indexed column
See it in action.
Multiple sort columns
You can specify multiple sort columns by passing additional parameters to make_comparer. For example, to sort by "number" and then by the zero-indexed column:
usort($data, make_comparer('number', 0));
See it in action.
Advanced features
More advanced features are available if you specify a sort column as an array instead of a simple string. This array should be numerically indexed, and must contain these items:
0 => the column name to sort on (mandatory)
1 => either SORT_ASC or SORT_DESC (optional)
2 => a projection function (optional)
Let's see how we can use these features.
Reverse sort
To sort by name descending:
usort($data, make_comparer(['name', SORT_DESC]));
See it in action.
To sort by number descending and then by name descending:
usort($data, make_comparer(['number', SORT_DESC], ['name', SORT_DESC]));
See it in action.
Custom projections
In some scenarios you may need to sort by a column whose values do not lend well to sorting. The "birthday" column in the sample data set fits this description: it does not make sense to compare birthdays as strings (because e.g. "01/01/1980" comes before "10/10/1970"). In this case we want to specify how to project the actual data to a form that can be compared directly with the desired semantics.
Projections can be specified as any type of callable: as strings, arrays, or anonymous functions. A projection is assumed to accept one argument and return its projected form.
It should be noted that while projections are similar to the custom comparison functions used with usort and family, they are simpler (you only need to convert one value to another) and take advantage of all the functionality already baked into make_comparer.
Let's sort the example data set without a projection and see what happens:
usort($data, make_comparer('birthday'));
See it in action.
That was not the desired outcome. But we can use date_create as a projection:
usort($data, make_comparer(['birthday', SORT_ASC, 'date_create']));
See it in action.
This is the correct order that we wanted.
There are many more things that projections can achieve. For example, a quick way to get a case-insensitive sort is to use strtolower as a projection.
That said, I should also mention that it's better to not use projections if your data set is large: in that case it would be much faster to project all your data manually up front and then sort without using a projection, although doing so will trade increased memory usage for faster sort speed.
Finally, here is an example that uses all the features: it first sorts by number descending, then by birthday ascending:
usort($data, make_comparer(
['number', SORT_DESC],
['birthday', SORT_ASC, 'date_create']
));
See it in action.
You can use array_multisort()
Try something like this:
foreach ($mdarray as $key => $row) {
// replace 0 with the field's index/key
$dates[$key] = $row[0];
}
array_multisort($dates, SORT_DESC, $mdarray);
For PHP >= 5.5.0 just extract the column to sort by. No need for the loop:
array_multisort(array_column($mdarray, 0), SORT_DESC, $mdarray);
With usort. Here's a generic solution, that you can use for different columns:
class TableSorter {
protected $column;
function __construct($column) {
$this->column = $column;
}
function sort($table) {
usort($table, array($this, 'compare'));
return $table;
}
function compare($a, $b) {
if ($a[$this->column] == $b[$this->column]) {
return 0;
}
return ($a[$this->column] < $b[$this->column]) ? -1 : 1;
}
}
To sort by first column:
$sorter = new TableSorter(0); // sort by first column
$mdarray = $sorter->sort($mdarray);
Multiple row sorting using a closure
Here's another approach using uasort() and an anonymous callback function (closure). I've used that function regularly. PHP 5.3 required – no more dependencies!
/**
* Sorting array of associative arrays - multiple row sorting using a closure.
* See also: http://the-art-of-web.com/php/sortarray/
*
* #param array $data input-array
* #param string|array $fields array-keys
* #license Public Domain
* #return array
*/
function sortArray( $data, $field ) {
$field = (array) $field;
uasort( $data, function($a, $b) use($field) {
$retval = 0;
foreach( $field as $fieldname ) {
if( $retval == 0 ) $retval = strnatcmp( $a[$fieldname], $b[$fieldname] );
}
return $retval;
} );
return $data;
}
/* example */
$data = array(
array( "firstname" => "Mary", "lastname" => "Johnson", "age" => 25 ),
array( "firstname" => "Amanda", "lastname" => "Miller", "age" => 18 ),
array( "firstname" => "James", "lastname" => "Brown", "age" => 31 ),
array( "firstname" => "Patricia", "lastname" => "Williams", "age" => 7 ),
array( "firstname" => "Michael", "lastname" => "Davis", "age" => 43 ),
array( "firstname" => "Sarah", "lastname" => "Miller", "age" => 24 ),
array( "firstname" => "Patrick", "lastname" => "Miller", "age" => 27 )
);
$data = sortArray( $data, 'age' );
$data = sortArray( $data, array( 'lastname', 'firstname' ) );
I know it's 2 years since this question was asked and answered, but here's another function that sorts a two-dimensional array. It accepts a variable number of arguments, allowing you to pass in more than one key (ie column name) to sort by. PHP 5.3 required.
function sort_multi_array ($array, $key)
{
$keys = array();
for ($i=1;$i<func_num_args();$i++) {
$keys[$i-1] = func_get_arg($i);
}
// create a custom search function to pass to usort
$func = function ($a, $b) use ($keys) {
for ($i=0;$i<count($keys);$i++) {
if ($a[$keys[$i]] != $b[$keys[$i]]) {
return ($a[$keys[$i]] < $b[$keys[$i]]) ? -1 : 1;
}
}
return 0;
};
usort($array, $func);
return $array;
}
Try it here: http://www.exorithm.com/algorithm/view/sort_multi_array
You can sort an array using usort function.
$array = array(
array('price'=>'1000.50','product'=>'product 1'),
array('price'=>'8800.50','product'=>'product 2'),
array('price'=>'200.0','product'=>'product 3')
);
function cmp($a, $b) {
return $a['price'] > $b['price'];
}
usort($array, "cmp");
print_r($array);
Output :
Array
(
[0] => Array
(
[price] => 134.50
[product] => product 1
)
[1] => Array
(
[price] => 2033.0
[product] => product 3
)
[2] => Array
(
[price] => 8340.50
[product] => product 2
)
)
Example
Here is a php4/php5 class that will sort one or more fields:
// a sorter class
// php4 and php5 compatible
class Sorter {
var $sort_fields;
var $backwards = false;
var $numeric = false;
function sort() {
$args = func_get_args();
$array = $args[0];
if (!$array) return array();
$this->sort_fields = array_slice($args, 1);
if (!$this->sort_fields) return $array();
if ($this->numeric) {
usort($array, array($this, 'numericCompare'));
} else {
usort($array, array($this, 'stringCompare'));
}
return $array;
}
function numericCompare($a, $b) {
foreach($this->sort_fields as $sort_field) {
if ($a[$sort_field] == $b[$sort_field]) {
continue;
}
return ($a[$sort_field] < $b[$sort_field]) ? ($this->backwards ? 1 : -1) : ($this->backwards ? -1 : 1);
}
return 0;
}
function stringCompare($a, $b) {
foreach($this->sort_fields as $sort_field) {
$cmp_result = strcasecmp($a[$sort_field], $b[$sort_field]);
if ($cmp_result == 0) continue;
return ($this->backwards ? -$cmp_result : $cmp_result);
}
return 0;
}
}
/////////////////////
// usage examples
// some starting data
$start_data = array(
array('first_name' => 'John', 'last_name' => 'Smith', 'age' => 10),
array('first_name' => 'Joe', 'last_name' => 'Smith', 'age' => 11),
array('first_name' => 'Jake', 'last_name' => 'Xample', 'age' => 9),
);
// sort by last_name, then first_name
$sorter = new Sorter();
print_r($sorter->sort($start_data, 'last_name', 'first_name'));
// sort by first_name, then last_name
$sorter = new Sorter();
print_r($sorter->sort($start_data, 'first_name', 'last_name'));
// sort by last_name, then first_name (backwards)
$sorter = new Sorter();
$sorter->backwards = true;
print_r($sorter->sort($start_data, 'last_name', 'first_name'));
// sort numerically by age
$sorter = new Sorter();
$sorter->numeric = true;
print_r($sorter->sort($start_data, 'age'));
Before I could get the TableSorter class to run I had came up with a function based on what Shinhan had provided.
function sort2d_bycolumn($array, $column, $method, $has_header)
{
if ($has_header) $header = array_shift($array);
foreach ($array as $key => $row) {
$narray[$key] = $row[$column];
}
array_multisort($narray, $method, $array);
if ($has_header) array_unshift($array, $header);
return $array;
}
$array is the MD Array you want to sort.
$column is the column you wish to sort by.
$method is how you want the sort performed, such as SORT_DESC
$has_header is set to true if the first row contains header values that you don't want sorted.
The "Usort" function is your answer.
http://php.net/usort
I tried several popular array_multisort() and usort() answers and none of them worked for me. The data just gets jumbled and the code is unreadable. Here's a quick a dirty solution. WARNING: Only use this if you're sure a rogue delimiter won't come back to haunt you later!
Let's say each row in your multi array looks like: name, stuff1, stuff2:
// Sort by name, pull the other stuff along for the ride
foreach ($names_stuff as $name_stuff) {
// To sort by stuff1, that would be first in the contatenation
$sorted_names[] = $name_stuff[0] .','. name_stuff[1] .','. $name_stuff[2];
}
sort($sorted_names, SORT_STRING);
Need your stuff back in alphabetical order?
foreach ($sorted_names as $sorted_name) {
$name_stuff = explode(',',$sorted_name);
// use your $name_stuff[0]
// use your $name_stuff[1]
// ...
}
Yeah, it's dirty. But super easy, won't make your head explode.