按列值将二维数组排序为不超过 N 种的循环升序组

问题描述 投票:0回答:3

我有一个包含其他关联数组作为其值的数组。这些“子”数组中的每一个都映射有键值对:

$items = [
    ['type' => 1, 'text' => 'A'],
    ['type' => 2, 'text' => 'B'],
    ['type' => 2, 'text' => 'C'],
    ['type' => 3, 'text' => 'D'],
    ['type' => 2, 'text' => 'E'],
    ['type' => 1, 'text' => 'F'],
    ['type' => 4, 'text' => 'G'],
    ['type' => 2, 'text' => 'H'],
    ['type' => 1, 'text' => 'I'],
    ['type' => 4, 'text' => 'J'],
    ['type' => 2, 'text' => 'K'],
    ['type' => 4, 'text' => 'L'],
    ['type' => 2, 'text' => 'M'],
    ['type' => 3, 'text' => 'N'],
    ['type' => 3, 'text' => 'O'],
    ['type' => 1, 'text' => 'P'],
    ['type' => 1, 'text' => 'Q'],
    ['type' => 2, 'text' => 'R'],
    ['type' => 3, 'text' => 'S'],
    ['type' => 2, 'text' => 'T'],
    ['type' => 3, 'text' => 'U'],
    ['type' => 2, 'text' => 'V'],
    ['type' => 3, 'text' => 'W'],
    ['type' => 2, 'text' => 'X'],
    ['type' => 4, 'text' => 'Y'],
    ['type' => 2, 'text' => 'Z'],
];

如何对该数组进行排序并作为另一个数组返回,并满足以下要求?

  1. 它需要按其
    type
    对关联数组进行排序。
  2. 它需要通过选择
    $items
    数组中显示的前 3 个项目来完成此操作,然后转到下一个类型,即
    2
    ,并执行相同的操作。我的代码中
    types
    的最大数量是
    8
    ,但理论上它应该适用于任何数字。
  3. 如果一次选出了每种类型中的 3 个,则使用数组中的其余项目重新启动,再次从类型
    1
    开始。

本质上,你应该得到另一个数组——从上到下——具有交替顺序的值和类型,如下所示:

[
    ['type' => 1, 'text' => 'A'],
    ['type' => 1, 'text' => 'F'],
    ['type' => 1, 'text' => 'I'],
    ['type' => 2, 'text' => 'B'],
    ['type' => 2, 'text' => 'C'],
    ['type' => 2, 'text' => 'E'],
    ['type' => 3, 'text' => 'D'],
    ['type' => 3, 'text' => 'N'],
    ['type' => 3, 'text' => 'O'],
    ['type' => 4, 'text' => 'G'],
    ['type' => 4, 'text' => 'J'],
    ['type' => 4, 'text' => 'L'],
    ['type' => 1, 'text' => 'P'],
    ['type' => 1, 'text' => 'Q'],
    ['type' => 2, 'text' => 'H'],
    ['type' => 2, 'text' => 'K'],
    ['type' => 2, 'text' => 'M'],
    ['type' => 3, 'text' => 'S'],
    ['type' => 3, 'text' => 'U'],
    ['type' => 3, 'text' => 'W'],
    ['type' => 4, 'text' => 'Y'],
    ['type' => 2, 'text' => 'R'],
    ['type' => 2, 'text' => 'T'],
    ['type' => 2, 'text' => 'V'],
    ['type' => 2, 'text' => 'X'],
    ['type' => 2, 'text' => 'Z'],
]

text
与排序无关。

我实际上已经制作了一个 YouTube 视频来更好地解释应该发生的事情:Video

我当前的思考过程是使用一个名为

$groups
的单独数组,使用
foreach
循环八次不同的时间,并且在每次循环中,如果不适合特定类型,则继续循环。例如:

// My above $items array example here

// Initialize $groups
$groups = [];

// foreach for type 1
foreach ($items as $item) {
    // If any of the items are not of type 1, ignore and continue
    if ($item["type"] !== 1) {
        continue;
    }
    // Otherwise, push to $groups
    array_push($groups, $item["type"];
}

// foreach for type 2
foreach ($items as $item) {
    // If any of the items are not of type 2, ignore and continue
    if ($item["type"] !== 2) {
        continue;
    }
    // Otherwise, push to $groups
    array_push($groups, $item["type"];
}

// Do this for each possible type, up to type 8

这不仅看起来效率极低而且倒退,我只是觉得有更好的方法来解决这个问题或者我缺少另一个角度。我不相信上面的代码甚至接近解决方案。

php arrays algorithm sorting array-column
3个回答
2
投票

我认为设置

array_multisort()
来完成这项工作是合乎逻辑的(因为这是一项排序任务)。
请注意,我的演示将对实际输入数组进行排序(如果您需要保留原始数组,请自己制作一个副本)。

  1. 迭代输入数组。
  2. 使用递增的分组 ID 填充平面映射数组。
  3. 填充类型值的平面映射数组。
  4. 调用
    array_multisort()
    $grouper
    进行排序,然后对
    $column
    $items
    进行排序(然后就可以访问
    $items
    )。

代码:(演示)(替代演示

$maxConsecutive = 3;

$grouper = [];
$column = [];
foreach ($items as $row) {
    $column[] = $row['type'];  // preserve isolated type values
    $encountered[$row['type']] ??= 0; // declare the default value of 0 for the first encounter of a given type value
    $grouper[] = intdiv($encountered[$row['type']]++, $maxConsecutive); // access the cached counter for the given type, divide it by 3, omit any decimal values; then increment the counter (post-incrementation `++` only increases the value AFTER it is used)
}

array_multisort($grouper, $column, $items); // sort by grouper ASC, column ASC, then items ASC
var_export($items);

有些相关的是按循环升序对平面数组进行排序,在按升序循环序列对数据进行排序时,其硬编码分组 N 为 1(而不是 3)。


或者,您可以将数据分组为子集,然后排序,然后在填充新结果时使用分组数据。
不过,我要指出的是,这感觉不像排序任务的

array_multisort()
方法那么专业。

  1. 按行的
    type
    值对行进行分组。
  2. 按新的一级键排序。
  3. 使用自扩展
    foreach()
    循环(请注意签名中的
    &
    )仅将每组的前 N 行推入结果数组中。如果当前组还有元素,则将整个组推到数组末尾。

代码:(演示

$maxConsecutive = 3;

$grouped = [];
foreach ($items as $row) {
    $grouped[$row['type']][] = $row;
}

ksort($grouped);

$result = [];
foreach ($grouped as &$group) {
    array_push($result, ...array_splice($group, 0, $maxConsecutive));
    if ($group) {
        $grouped[] = $group;
    }
}
var_export($result);

作为微优化,您可以使用以下方法提取最后剩余组的整个有效负载:(Demo)

array_push($result, ...array_splice($group, 0, count($grouped) === 1 ? null : $maxConsecutive));

2
投票

这首先创建每种类型的计数,然后循环直到没有任何输出。内部循环将输出您想要的项目数量(只要该类型还有剩余的内容要输出),然后调整剩余的剩余部分。

我已经评论了代码,应该可以更清楚地解释它。

// Create text lookup array, indexed by type
$types = array_column($items, null, 'type');
// Count the number of each type there are
$breakdown = array_count_values(array_column($items, 'type'));

// Sort by the key to create desired order
ksort($breakdown);

$breakSize = 3;

do {
    // If flag doesn't change then nothing is left to output
    $anyOutput = false;
    foreach ($breakdown as $key => &$countLeft) {
        // Loop while less than the break size but also with something left
        for ($i = 0; $i < $breakSize && $countLeft > 0; $i++) {
            echo $types[$key]['text'] . PHP_EOL;
            // Adjust amount left
            $countLeft--;
            // Flag something put out
            $anyOutput = true;
        }
    }
} while ($anyOutput);

更新: 如果某些类型有不同的文本值,则需要跟踪这些数据。

因此,此代码为每种类型创建一个数组,并为每个类型创建一个文本值列表,然后在循环中,它在每个点从列表中删除第一个(使用

array_shift
)以输出下一个并将其从剩下的名单。

// Split into groups by the type
$breakdown = [];
foreach ($items as $item) {
    $breakdown[$item['type']][] = $item['text'];
}
// Sort by the key to create desired order
ksort($breakdown);

$breakSize = 3;

do {
    // If flag doesn't changem then run out of content
    $anyOutput = false;
    foreach ($breakdown as $key => &$textList) {
        // Loop while less than the break size but also with something left
        for ($i = 0; $i < $breakSize && !empty($textList); $i++) {
            echo array_shift($textList) . PHP_EOL;
            // Flag something put out
            $anyOutput = true;
        }
    }
} while ($anyOutput);

-1
投票

首先

我似乎找不到一种算法来匹配我想要做的事情。我似乎找不到太多,我开始怀疑我正在寻找的算法是否实际上还不存在

是的,你是对的,它不存在也不应该永远存在,算法不应该存在来输出特定的结果。它应该用于执行特定的行为/功能,而不是实现/输出特定的结果。

但是,您可以使用

uasort
根据数组的键对数组进行排序,如下所示:

$items = [
    ["type" => 1, "text" => "Type of 1"],
    ["type" => 1, "text" => "Type of 1"],
    ["type" => 1, "text" => "Type of 1"],
    ["type" => 1, "text" => "Type of 1"],
    ["type" => 2, "text" => "Type of 2"],
    ["type" => 2, "text" => "Type of 2"],
    ["type" => 2, "text" => "Type of 2"],
    ["type" => 2, "text" => "Type of 2"],
];

uasort($items, fn ($a, $b) => $a > $b ? 1 : -1); 

print_r($items);

然后,您可以将

array_column
之类的东西与内爆结合使用,以单线性方式将项目提取为值,或者您可以使用您已经提到的
foreach
循环。

© www.soinside.com 2019 - 2024. All rights reserved.