按自定义顺序对php数组排序

时间:2012-06-21 19:28:46

标签: php arrays multidimensional-array sorting

我有一个数组数组:

Array ( 
    [0] => Array (
        [id] = 7867867,
        [title] = 'Some Title'),
    [1] => Array (
        [id] = 3452342,
        [title] = 'Some Title'),
    [2] => Array (
        [id] = 1231233,
        [title] = 'Some Title'),
    [3] => Array (
        [id] = 5867867,
        [title] = 'Some Title')
)

需要按特定顺序进行:

  1. 3452342
  2. 5867867
  3. 7867867
  4. 1231233
  5. 我该怎么做呢?我之前已对数组进行了排序,并阅读了大量关于它的帖子,但它们总是基于比较(即valueA< valueB)。

    非常感谢帮助。

8 个答案:

答案 0 :(得分:99)

您可以使用usort()精确指定数组的排序方式。在这种情况下,$order数组可以在比较函数中使用。

以下示例使用closure来简化生活。

$order = array(3452342, 5867867, 7867867, 1231233);
$array = array(
    array('id' => 7867867, 'title' => 'Some Title'),
    array('id' => 3452342, 'title' => 'Some Title'),
    array('id' => 1231233, 'title' => 'Some Title'),
    array('id' => 5867867, 'title' => 'Some Title'),
);

usort($array, function ($a, $b) use ($order) {
    $pos_a = array_search($a['id'], $order);
    $pos_b = array_search($b['id'], $order);
    return $pos_a - $pos_b;
});

var_dump($array);

这项工作的关键是让正在比较的值成为id数组中$order的位置。

比较函数通过在$order数组中查找要比较的两个项的id的位置来工作。如果$a['id']位于$b['id']数组中的$order之前,则函数的返回值将为负数($a较少,因此“浮动”到顶部)。如果$a['id']位于$b['id']之后,则函数返回正数($a更大,因此“下沉”)。

最后,使用封闭没有特殊原因;这只是我快速编写这些一次性功能的首选方式。它同样可以使用普通的命名函数。

答案 1 :(得分:14)

为此额外要求扩展salathe's answer

  

现在当我向数组添加项目而不是排序时会发生什么?一世   不管他们出现什么样的顺序,只要它出现在那些顺序之后   我确实指明了。

您需要在排序功能中添加两个附加条件:

  1. “不关心”项目必须被视为大于“关心”项目
  2. 两个“不关心”的项目必须被视为相等
  3. 所以修改后的代码是:

    $order = array(
        3452342,
        5867867,
        7867867,
        1231233
    );
    $array = array(
        array("id" => 7867867, "title" => "Must Be #3"),
        array("id" => 3452342, "title" => "Must Be #1"),
        array("id" => 1231233, "title" => "Must Be #4"),
        array("id" => 5867867, "title" => "Must Be #2"),
        array("id" => 1111111, "title" => "Dont Care #1"),
        array("id" => 2222222, "title" => "Dont Care #2"),
        array("id" => 3333333, "title" => "Dont Care #3"),
        array("id" => 4444444, "title" => "Dont Care #4")
    );
    function custom_compare($a, $b){
        global $order;
        $a = array_search($a["id"], $order);
        $b = array_search($b["id"], $order);
        if($a === false && $b === false) { // both items are dont cares
            return 0;                      // a == b
        }
        else if ($a === false) {           // $a is a dont care item
            return 1;                      // $a > $b
        }
        else if ($b === false) {           // $b is a dont care item
            return -1;                     // $a < $b
        }
        else {
            return $a - $b;
        }
    }
    shuffle($array);  // for testing
    var_dump($array); // before
    usort($array, "custom_compare");
    var_dump($array); // after
    

    输出:

    Before                         |  After
    -------------------------------+-------------------------------
    array(8) {                     |  array(8) {
      [0]=>                        |    [0]=>
      array(2) {                   |    array(2) {
        ["id"]=>                   |      ["id"]=>
        int(4444444)               |      int(3452342)
        ["title"]=>                |      ["title"]=>
        string(12) "Dont Care #4"  |      string(10) "Must Be #1"
      }                            |    }
      [1]=>                        |    [1]=>
      array(2) {                   |    array(2) {
        ["id"]=>                   |      ["id"]=>
        int(3333333)               |      int(5867867)
        ["title"]=>                |      ["title"]=>
        string(12) "Dont Care #3"  |      string(10) "Must Be #2"
      }                            |    }
      [2]=>                        |    [2]=>
      array(2) {                   |    array(2) {
        ["id"]=>                   |      ["id"]=>
        int(1231233)               |      int(7867867)
        ["title"]=>                |      ["title"]=>
        string(10) "Must Be #4"    |      string(10) "Must Be #3"
      }                            |    }
      [3]=>                        |    [3]=>
      array(2) {                   |    array(2) {
        ["id"]=>                   |      ["id"]=>
        int(1111111)               |      int(1231233)
        ["title"]=>                |      ["title"]=>
        string(12) "Dont Care #1"  |      string(10) "Must Be #4"
      }                            |    }
      [4]=>                        |    [4]=>
      array(2) {                   |    array(2) {
        ["id"]=>                   |      ["id"]=>
        int(5867867)               |      int(2222222)
        ["title"]=>                |      ["title"]=>
        string(10) "Must Be #2"    |      string(12) "Dont Care #2"
      }                            |    }
      [5]=>                        |    [5]=>
      array(2) {                   |    array(2) {
        ["id"]=>                   |      ["id"]=>
        int(2222222)               |      int(1111111)
        ["title"]=>                |      ["title"]=>
        string(12) "Dont Care #2"  |      string(12) "Dont Care #1"
      }                            |    }
      [6]=>                        |    [6]=>
      array(2) {                   |    array(2) {
        ["id"]=>                   |      ["id"]=>
        int(3452342)               |      int(3333333)
        ["title"]=>                |      ["title"]=>
        string(10) "Must Be #1"    |      string(12) "Dont Care #3"
      }                            |    }
      [7]=>                        |    [7]=>
      array(2) {                   |    array(2) {
        ["id"]=>                   |      ["id"]=>
        int(7867867)               |      int(4444444)
        ["title"]=>                |      ["title"]=>
        string(10) "Must Be #3"    |      string(12) "Dont Care #4"
      }                            |    }
    }                              |  }
    

答案 2 :(得分:4)

如果要维护索引关联,则需要定义自己的比较函数并使用usortuasort

答案 3 :(得分:4)

使用array_search()迭代调用方法的其他答案效率不高。通过重组/翻转“订单”查找数组,您可以完全省略所有array_search()次调用 - 使您的任务更加高效和简洁。我将使用最现代的“宇宙飞船运营商”(<=>),但早期的技术对比较线的作用相同。

方法#1 - usortidDemo

中存在所有$order值时
$order=array_flip([3452342,5867867,7867867,1231233]);  // restructure with values as keys, and keys as order (ASC)
// generating $order=[3452342=>0,5867867=>1,7867867=>2,1231233=>3];
$array=[
    ['id'=>7867867,'title'=>'Some Title'],
    ['id'=>3452342,'title'=>'Some Title'],
    ['id'=>1231233,'title'=>'Some Title'],
    ['id'=>5867867,'title'=>'Some Title']
];

usort($array,function($a,$b)use($order){
    return $order[$a['id']]<=>$order[$b['id']];
    // when comparing ids 3452342 & 1231233, the actual comparison is 0 vs 3
});
// uasort() if you want to preserve keys

var_export($array);

方法#2 - usortidDemo)中不存在某些$order值时 *注意,isset()是一个比array_search()

更便宜的通话
$order=array_flip([3452342,5867867,7867867,1231233]);  // restructure with values as keys, and keys as order (ASC)
// generating $order=[3452342=>0,5867867=>1,7867867=>2,1231233=>3];
$outlier=1+max($order);
// generating $outlier=4
$array=[
    ['id'=>7867867,'title'=>'Some Title'],
    ['id'=>3452342,'title'=>'Some Title'],
    ['id'=>'foo','title'=>'Some Title'],
    ['id'=>1231233,'title'=>'Some Title'],
    ['id'=>'bar','title'=>'Some Title'],
    ['id'=>5867867,'title'=>'Some Title']
];

usort($array,function($a,$b)use(&$order,$outlier){  // make $order modifiable with &
    if(!isset($order[$a['id']])){$order[$a['id']]=$outlier;}  // update lookup array with [id]=>[outlier number]
    if(!isset($order[$b['id']])){$order[$b['id']]=$outlier;}  // and again
    return $order[$a['id']]<=>$order[$b['id']];
});

var_export($array);

替代方法#2 - usort id

中不存在$order个值时

...我还想提一下,在某些情况下,避免isset()的迭代双重调用可能不如在调用{{1}之前完全准备$order数组}}

这个单行将确保没有丢失usort()值,因此除了排序函数内的比较行之外不需要任何其他内容。 (Full Snippet Demo

id

答案 4 :(得分:1)

@salathe对于那些很难理解salathe's usort正在做什么的人:

$ array中的每个项目都是&#39;冠军&#39;在锦标赛中,在一个新阵列的开头(除了不是第一个,他们想成为0号)。

$ a是主场冠军,$ b是对手中的对手冠军。

来自回调的$ pos_a和$ pos_b是在争夺冠军a和b时使用的属性。在这种情况下,此属性是$ order中的冠军id的索引。

然后就会有回归的斗争。现在我们看看是否有更多或更少的属性更好。在一场激烈的战斗中,主场冠军想要一个负数,这样他就可以更快地进入阵中。客场冠军想要一个正数。并且应该有一个0是一个平局。

所以按照这个比喻,当离开冠军属性($ order中的索引)从主队属性中减去时,客场冠军属性越大,获得正数就越不可能获胜。但是,如果您要颠倒使用属性的方式,那么现在可以从客场冠军中减去主场冠军的属性。在这种情况下,客场冠军的更多数字更有可能使他的比赛结束为正数。

代码如下:

注意:代码运行很多次就像真正的锦标赛有很多战斗,以决定谁先获得(即0 /阵列开始)

//tournament with goal to be first in array
    usort($champions, function ($home, $away) use ($order) {
        $home_attribute = array_search($a['id'], $order);
        $away_attribute = array_search($b['id'], $order);
        //fight with desired outcome for home being negative and away desiring positive
        return $home_attribute - $away_attribute;
    });

答案 5 :(得分:0)

如果没有排序,您也可以得到它。

没有重复的ID;

<?php

    $order = array(3452342, 5867867, 7867867, 1231233);
    $array = array(
        array('id' => 7867867, 'title' => 'Some Title'),
        array('id' => 3452342, 'title' => 'Some Title'),
        array('id' => 1231233, 'title' => 'Some Title'),
        array('id' => 5867867, 'title' => 'Some Title'),
    );

    $order = array_flip($order);
    $array = array_column($array,null,"id");
    $result = array_replace($order,$array);
    var_dump(array_values($result));

具有重复的ID,

<?php

    $order = array(3452342, 5867867, 7867867, 1231233);
    $array = array(
        array('id' => 7867867, 'title' => 'Some Title'),
        array('id' => 3452342, 'title' => 'Some Title'),
        array('id' => 1231233, 'title' => 'Some Title'),
        array('id' => 5867867, 'title' => 'Some Title'),
    );

    $order_dict = array_flip($order);
    $order_dict = array_combine($order,array_fill(0,count($order),[]));
    foreach($array as $item){
        $order_dict[$item["id"]][] = $item;
    }
    //$order_dict = array_filter($order_dict);  // if there is empty item on some id in $order array
    $result = [];
    foreach($order_dict as $items){
        foreach($items as $item){
            $result[] = $item;
        }
    }
    var_dump($result);

答案 6 :(得分:0)

更高效的解决方案

$dict = array_flip($order);
$positions = array_map(function ($elem) use ($dict) { return $dict[$elem['id']] ?? INF; }, $array);
array_multisort($positions, $array);

不要在每次比较时重新计算排名

当数组很大或获取ID的成本较高时,使用usort()可能会很糟糕,因为您需要为每个比较重新计算ID。尝试将array_multisort()与预先计算的位置一起使用(请参见下面的示例中的mediumsortfastsort),这并不复杂。

在每个比较中都在order数组中搜索id(就像在接受的答案中一样)不会提高性能,因为您会在每个比较中对其进行迭代。计算一次。

在下面的代码段中,您可以看到主要的三个排序功能:

  • slowsort
    接受的答案。在每次比较中搜索位置。
  • mediumsort
    通过提前计算位置改进了slowsort
  • fastsort
    通过避免搜索alltogher改进了mediumsort

请注意,这些ID的后备元素通过提供后备值INF来处理ID顺序不正确的元素。如果您的订单数组与原始数组的ID一对一匹配,则避免全部排序,而只是将元素插入正确的位置即可。我添加了一个功能cheatsort,正是该功能。

您通常可以按权重对数组进行排序(请参见示例中的weightedsort)。确保只计算一次重量,以获得良好的性能。

性能(对于长度为1000的数组)

fastsort     about  1 ms
mediumsort   about  3 ms
slowsort     about 60 ms

提示:对于更大的数组,差异会变得更糟。

排序功能比较

<?php

/**
 * accepted answer
 *
 * re-evaluate position in order on each comparison
 */
function slowsort(&$array, $order, $key = 'id')
{
  usort($array, function ($a, $b) use ($order, $key) {
    $pos_a = array_search($a[$key], $order);
    $pos_b = array_search($b[$key], $order);
    return $pos_a - $pos_b;
  });
}

/**
 * calculate element positions once
 */
function mediumsort(&$array, $order, $key = 'id')
{
  $positions = array_map(function ($elem) use ($order, $key) {
    return array_search($elem[$key], $order);
  }, $array);
  array_multisort($positions, $array);
}

/**
 * calculate positions without searching
 */
function fastsort(&$array, $order, $key = 'id')
{
  $dict = array_flip($order);
  $positions = array_map(function ($elem) use ($dict, $key) {
    return $dict[$elem[$key]] ?? INF;
  }, $array);
  array_multisort($positions, $array);
}

/**
 * when each order element gets used exactly once, insert elements directly
 */
function cheatsort(&$array, $order, $key = 'id')
{
  $dict = array_flip($order);
  $copy = $array;
  foreach ($copy as $elem) {
    $pos = $dict[$elem[$key]];
    $array[$pos] = $elem;
  }
}

/**
 * Sort elements in $array by their weight given by $weight_func
 * 
 * You could rewrite fastsort and mediumsort by replacing $position by a weight function
 */
function weightedsort(&$array, $weight_func)
{
  $weights = array_map($weight_func, $array);
  array_multisort($weights, $array);
}



/**
 * MEASUREMENTS
 */

/**
 * Generate the sorting problem
 */
function generate($size = 1000)
{
  $order = array();
  $array = array();

  for ($i = 0; $i < $size; $i++) {
    $id = random_int(0, PHP_INT_MAX);
    $order[] = $id;
    $array[] = array('id' => $id);
  }
  shuffle($order);
  return [$array, $order];
}

/**
 * Time $callable in ms
 */
function time_it($callable)
{
  $then = microtime(true);
  $callable();
  $now = microtime(true);
  return 1000 * ($now - $then);
}

/**
 * Time a sort function with name $sort_func
 */
function time_sort($sort_func) 
{
  echo "Timing $sort_func", PHP_EOL;
  [$array, $order] = generate();
  echo time_it(function () use ($sort_func, &$array, $order) {
    $sort_func($array, $order);
  }) . ' ms' . PHP_EOL;
}

time_sort('cheatsort');
time_sort('fastsort');
time_sort('mediumsort');
time_sort('slowsort');

答案 7 :(得分:-1)

这是我根据id值以ASC顺序对多维数组进行排序的方法:

$arrayFilter = array(
    array('product_tag' => 'xenia', 'id' => 4),
    array('product_tag' => 'worn',  'id' => 5),
    array('product_tag' => 'woven', 'id' => 3),
    array('product_tag' => 'nude', 'id' => 1)
);

for ($i = 0; $i < sizeof($arrayFilter); $i++) {
    for ($j=$i+1; $j < sizeof($arrayFilter); $j++) {
        if ($arrayFilter[$i]['id'] > $arrayFilter[$j]['id']) {
            $c = $arrayFilter[$i];
            $arrayFilter[$i] = $arrayFilter[$j];
            $arrayFilter[$j] = $c;
        }
    }
}
print_r($arrayFilter);

<强>输出:

Array
(
    [0] => Array
        (
            [product_tag] => nude
            [id] => 1
        )

    [1] => Array
        (
            [product_tag] => woven
            [id] => 3
        )

    [2] => Array
        (
            [product_tag] => xenia
            [id] => 4
        )

    [3] => Array
        (
            [product_tag] => worn
            [id] => 5
        )
)