提问人:Jack co 提问时间:8/26/2022 最后编辑:mickmackusaJack co 更新时间:8/27/2022 访问量:135
按两列对行数组进行排序,同时不移动“锁定”(粘性)行
Sort an array of rows by two columns while not moving "locked" (sticky) rows
问:
我需要按分数对项目数组进行排序,然后按日期排序,而无需移动锁定值大于零的行。
换句话说,排序完成后,带有 和 的行应保持在相同/原始位置。lock=3
lock=5
[
['id' => 7867867, 'lock' => 0, 'score' => 322, 'strtotime' => 16614713],
['id' => 7867867, 'lock' => 0, 'score' => 444, 'strtotime' => 16614613],
['id' => 7867867, 'lock' => 3, 'score' => 0, 'strtotime' => 16613713],
['id' => 7867867, 'lock' => 0, 'score' => 11, 'strtotime' => 16612713],
['id' => 7867867, 'lock' => 5, 'score' => 0, 'strtotime' => 16614413],
['id' => 7867867, 'lock' => 0, 'score' => 42, 'strtotime' => 16614113],
['id' => 7867867, 'lock' => 0, 'score' => 22, 'strtotime' => 16614013],
]
我使用以下代码对 than 进行排序,但这会影响不应该移动的行。score
strtotime
usort($array, function ($a, $b) {
if ( $a->score == $b->score ) { //score are same
return $b->strtotime <=> $a->strtotime; //sort by strtotime
}
return $b->score <=> $a->score; //else sort by score
});
我想要的输出是:
[
['id' => 7867867, 'lock' => 0, 'score' => 11, 'strtotime' => 16612713],
['id' => 7867867, 'lock' => 0, 'score' => 22, 'strtotime' => 16614013],
['id' => 7867867, 'lock' => 3, 'score' => 0, 'strtotime' => 16613713],
['id' => 7867867, 'lock' => 0, 'score' => 42, 'strtotime' => 16614113],
['id' => 7867867, 'lock' => 5, 'score' => 0, 'strtotime' => 16614413],
['id' => 7867867, 'lock' => 0, 'score' => 322, 'strtotime' => 16614713],
['id' => 7867867, 'lock' => 0, 'score' => 444, 'strtotime' => 16614613],
]
答:
1赞
IMSoP
8/26/2022
#1
在排序过程中,您无法访问列表中的绝对位置,只能访问正在比较的一对项目,因此我以这种方式处理它:
- 从列表中删除“锁定”值
- 对其他所有内容进行排序
- 将锁定的值放回原处
对于步骤 1,只需遍历数组,生成两个新数组:
$result = [];
$locked = [];
foreach ( $input as $item ) {
if ( $item['lock'] > 0 ) {
$locked[] = $item;
}
else {
$result[] = $item;
}
}
第 2 步是您已经拥有的代码,使用我称之为 的解锁项数组,因为它最终将包含最终结果。$result
对于步骤 3,您可以使用 array_splice 将项目放入数组中所选位置,然后向下移动所有内容。
这里需要注意的重要一点是,您插入的顺序很重要:如果您将项目 X 插入位置 5,然后将项目 Y 插入位置 3,则位置 X 将向前移动到位置 6。因此,如果您锁定的物品尚未按顺序排列,请对它们进行排序:
usort($locked, fn($a,$b) => $a['lock'] <=> $b['lock']);
然后循环,将它们拼接到所需的位置:
foreach ( $locked as $item ) {
array_splice($result, $item['lock'], 0, $item);
}
然后你应该完成:)
0赞
mickmackusa
8/27/2022
#2
在没有任何过滤、临时数组或拼接的情况下,我精心制作了一个带有三个嵌套循环的手动排序算法(具有条件短路以获得最佳性能)以提供“粘性排序”。
没有函数调用,因此执行速度相当快。我添加了内联注释来帮助理解代码。
下面的代码片段不关心非零锁值是什么。在发布的问题中,锁定的行已经位于它们应该在结果中的位置。该算法从不移动具有非零锁定值的行。这使得脚本很容易适应其他场景,其中另一个标志指示“固定行”。
代码:(演示)
$maxIndex = count($array) - 1;
for ($a = 0; $a < $maxIndex; ++$a) {
if ($array[$a]['lock'] !== 0) {
continue; // cannot move locked row
}
for ($b = 0; $b < $maxIndex; ++$b) {
if ($array[$b]['lock'] !== 0) {
continue; // cannot move locked row
}
// find next movable row
for ($c = $b + 1; $c <= $maxIndex; ++$c) {
if ($array[$c]['lock'] === 0) {
break; // $c is index of non-locked row
}
}
if ($c > $maxIndex) {
break; // no more movable rows
}
// sort movable rows
if (
$array[$b]['score'] > $array[$c]['score']
|| ($array[$b]['score'] === $array[$c]['score']
&& $array[$b]['strtotime'] > $array[$c]['strtotime'])
) {
[$array[$b], $array[$c]] = [$array[$c], $array[$b]];
}
}
}
var_export($array);
评论
lock