按自定义顺序对数组的php数组进行排序

1cklez4t  于 2022-12-10  发布在  PHP
关注(0)|答案(8)|浏览(174)

我有一个数组的数组:

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.小行星345
1.五八六七八六七
1.七八六七八六七
1.小行星1231233
我该怎么做呢?我以前对数组进行过排序,也读过很多关于它的帖子,但它们总是基于比较的(IidoEvalueA〈valueB)。
帮助是感激不尽的。

qyuhtwio

qyuhtwio1#

您可以使用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数组中的位置。
比较函数的工作原理是查找要比较的两个项的id在$order数组中的位置。则函数的返回值将为负($a较小,因此“浮动”到顶部)。如果$a['id']$b['id']之后,则函数返回一个正数($a较大,因此“下沉”)。
最后,使用闭包没有特殊的理由;这是我快速编写这些一次性函数的常用方法。它同样可以使用一个普通的命名函数。

yhxst69z

yhxst69z2#

扩展salathe's answer以满足此附加要求:
现在,当我将项添加到数组而不是排序中时会发生什么?我不关心它们出现的顺序,只要它在我指定的项之后。
您需要在排序函数中添加两个附加条件:
1.“无关”项目必须被视为大于白名单项目
1.两个“无关”项目必须视为相等
因此,修改后的代码将是:

$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")
);

shuffle($array);  // for testing
var_dump($array); // before

usort($array, function ($a, $b) use ($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
        return 1;                       // $a > $b
    } else if ($b === false) {          // $b is a dont care
        return -1;                      // $a < $b
    } else {
        return $a - $b;                 // sort $a and $b ascending
    }
});
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"
  }                            |    }
}                              |  }
06odsfpq

06odsfpq3#

其他使用迭代调用array_search()的方法的答案并不像它们所能做到的那样有效。通过重新构造/翻转“order”查找数组,您可以完全省略所有array_search()调用--使您的任务更加高效和简洁。我将使用最现代的“飞船操作符”(<=>)、但是早期的技术对比较行的作用相同。“空合并运算符”(??)将检查查找数组中给定id值的存在性,检查方式与isset()相同--这总是比X1 M6 N1 X或X1 M7 N1 X更有效。
代码:(Demo)(Demo with 7.4 arrow function syntax)(Demo with lower than PHP7

// restructure with values as keys, and keys as order (ASC)
$order = array_flip([3452342, 5867867, 7867867, 1231233]);
// generating $order = [3452342 => 0, 5867867 => 1, 7867867 => 2, 1231233 => 3];

$default = count($order);
// generating $default = 4

usort($array, function($a, $b) use($order, $default) {
    return ($order[$a['id']] ?? $default) <=> ($order[$b['id']] ?? $default);
});

var_export($array);
dgsult0t

dgsult0t4#

更高效的解决方案

$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(就像在accepted answer中一样)并不能提高性能,因为每次比较都要迭代它。
在下面的代码片段中,您可以看到三个主要的排序函数:

  • slowsort

接受的答案。搜索每次比较的位置。

  • mediumsort

通过提前计算位置改进了slowsort

  • fastsort

改进了mediumsort,避免了同时搜索。
请注意,这些函数通过提供一个后备值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');
f2uvfpb9

f2uvfpb95#

不用排序你也能如愿以偿。
1.如果没有重复的id,并且$order包含$array中的所有id值,并且$array中的id列包含$order中的所有值,则可以通过将这些值转换为$order中的键,然后将临时的第一级键分配给array,则将$array合并或替换为$order

$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));
  1. $array中可能有重复的ID:
$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);
wgeznvg7

wgeznvg76#

@salathe对于那些很难理解salathe的usort在做什么的人:
$array中的每一项都是锦标赛中的“冠军”,将位于新数组的开始(除了他们希望成为0号而不是1号)。
$a是主场冠军,$B是比赛中的对手冠军。
来自回调的$pos_a和$pos_B是在争夺冠军a和b时将使用的属性。在本例中,该属性是$order中冠军id的索引。
然后是在返回时的战斗。现在我们看看拥有更多或更少的属性是更好的。在一个usort战斗中,主场冠军想要一个负数,这样他就可以更早地进入阵列。客场冠军想要一个正数。如果有一个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;
    });
hl0ma9xz

hl0ma9xz7#

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

e0uiprwp

e0uiprwp8#

我遇到了同样的问题,@mickmackusa有我需要的答案。当有NULL值时,所选的答案不排序。例如:

$order = array(3, 2, 10);
$array = array(
    array('id' => NULL, 'title' => 'any order since null but not top'),
    array('id' => NULL, 'title' => 'any order since null but not top'),
    array('id' => NULL, 'title' => 'any order since null but not top'),
    array('id' => 2, 'title' => 'should be top'),
);
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;
});

以上结果将显示以下输出:

array(4) {
  [0]=>
  array(2) {
    ["id"]=>
    NULL
    ["title"]=>
    string(32) "any order since null but not top"
  }
  [1]=>
  array(2) {
    ["id"]=>
    NULL
    ["title"]=>
    string(32) "any order since null but not top"
  }
  [2]=>
  array(2) {
    ["id"]=>
    NULL
    ["title"]=>
    string(32) "any order since null but not top"
  }
  [3]=>
  array(2) {
    ["id"]=>
    int(2)
    ["title"]=>
    string(13) "should be top"
  }
}

在@mickmackusa的答案中,它不仅消除了排序中的null,而且还将排序基础中可用的第一个值放入其中,因此,由于数组中唯一可用的值是2,因此它将位于顶部。
虽然它不能在PHP 5.6中工作。所以我把它转换成PHP 5.6兼容的。这是我得到的

usort($array, function($a, $b) use($order, $default) {
    $a = (isset($order[$a['id']]) ? $order[$a['id']] : $default);
    $b = (isset($order[$b['id']]) ? $order[$b['id']] : $default);

    if($a == $b) return 0;
    elseif($a > $b) return 1;
    return -1;
});

上述排序的结果为

array(4) {
  [0]=>
  array(2) {
    ["id"]=>
    int(2)
    ["title"]=>
    string(13) "should be top"
  }
  [1]=>
  array(2) {
    ["id"]=>
    NULL
    ["title"]=>
    string(32) "any order since null but not top"
  }
  [2]=>
  array(2) {
    ["id"]=>
    NULL
    ["title"]=>
    string(32) "any order since null but not top"
  }
  [3]=>
  array(2) {
    ["id"]=>
    NULL
    ["title"]=>
    string(32) "any order since null but not top"
  }
}

我希望我的代码转换将有助于开发谁的工作在一个过时的服务器与较低的php版本。

相关问题