如何按列值对子数组进行分组?

107
我有以下数组。
Array
(
    [0] => Array
        (
            [id] => 96
            [shipping_no] => 212755-1
            [part_no] => reterty
            [description] => tyrfyt
            [packaging_type] => PC
        )

    [1] => Array
        (
            [id] => 96
            [shipping_no] => 212755-1
            [part_no] => dftgtryh
            [description] => dfhgfyh
            [packaging_type] => PC
        )

    [2] => Array
        (
            [id] => 97
            [shipping_no] => 212755-2
            [part_no] => ZeoDark
            [description] => s%c%s%c%s
            [packaging_type] => PC
        )

)

我该如何按照id对数组进行分组?是否有可用的原生php函数可以实现此功能?

尽管这种方法可行,但我想使用foreach来实现,因为上面的方法会返回重复的项,而我正试图避免这种情况。

在上面的例子中,id有2个项,所以它需要被放置在id内部。


你是否也想要删除重复的内容? - Baba
大多数解决方案都使用了一个FOREACH循环。 - Justin John
@JustinJohn 大多数解决方案都使用一个FOREACH来创建数组,但最终结果并不是一个数组。我正在寻找更好的解决方案。 - Red
1
你的意思是最终结果不是一维数组。 - Justin John
我的意思是,我需要使用foreach循环已创建的数组,将其转换为HTML元素值。 - Red
21个回答

213

没有原生的方法,只需使用循环。

$result = array();
foreach ($data as $element) {
    $result[$element['id']][] = $element;
}

这个如何知道相似的ID? - fiberOptics
BAKARI请查看以下相关帖子:https://stackoverflow.com/a/47926978/2943403、https://stackoverflow.com/a/61968973/2943403以及https://stackoverflow.com/a/52485161/2943403。 - mickmackusa
1
你忘记在最后加上 ksort($result, SORT_NUMERIC); 了。问题显示已排序键。 - Erdal G.
@fiberOptics 结果数组无条件地将新索引元素推入每个组(由 $element['id'] 明确分组)。对于不熟悉这种技术/语言的研究人员,这个问题可能需要更多的解释。 - mickmackusa

47

在更加函数式的编程风格中,您可以使用array_reduce

$groupedById = array_reduce($data, function (array $accumulator, array $element) {
  $accumulator[$element['id']][] = $element;

  return $accumulator;
}, []);

43
你可以尝试以下方法:
$group = array();

foreach ( $array as $value ) {
    $group[$value['id']][] = $value;
}

var_dump($group);

输出:

array
  96 => 
    array
      0 => 
        array
          'id' => int 96
          'shipping_no' => string '212755-1' (length=8)
          'part_no' => string 'reterty' (length=7)
          'description' => string 'tyrfyt' (length=6)
          'packaging_type' => string 'PC' (length=2)
      1 => 
        array
          'id' => int 96
          'shipping_no' => string '212755-1' (length=8)
          'part_no' => string 'dftgtryh' (length=8)
          'description' => string 'dfhgfyh' (length=7)
          'packaging_type' => string 'PC' (length=2)
  97 => 
    array
      0 => 
        array
          'id' => int 97
          'shipping_no' => string '212755-2' (length=8)
          'part_no' => string 'ZeoDark' (length=7)
          'description' => string 's%c%s%c%s' (length=9)
          'packaging_type' => string 'PC' (length=2)

18

我受.NET LINQ启发,匆忙完成了这个。

<?php

// callable type hint may be "closure" type hint instead, depending on php version
function array_group_by(array $arr, callable $key_selector) {
  $result = array();
  foreach ($arr as $i) {
    $key = call_user_func($key_selector, $i);
    $result[$key][] = $i;
  }  
  return $result;
}

 $data = array(
        array(1, "Andy", "PHP"),
        array(1, "Andy", "C#"),
        array(2, "Josh", "C#"),
        array(2, "Josh", "ASP"),
        array(1, "Andy", "SQL"),
        array(3, "Steve", "SQL"),
    );

$grouped = array_group_by($data, function($i){  return $i[0]; });

var_dump($grouped);

?>

然后,你就得到了

array(3) {
  [1]=>
  array(3) {
    [0]=>
    array(3) {
      [0]=>
      int(1)
      [1]=>
      string(4) "Andy"
      [2]=>
      string(3) "PHP"
    }
    [1]=>
    array(3) {
      [0]=>
      int(1)
      [1]=>
      string(4) "Andy"
      [2]=>
      string(2) "C#"
    }
    [2]=>
    array(3) {
      [0]=>
      int(1)
      [1]=>
      string(4) "Andy"
      [2]=>
      string(3) "SQL"
    }
  }
  [2]=>
  array(2) {
    [0]=>
    array(3) {
      [0]=>
      int(2)
      [1]=>
      string(4) "Josh"
      [2]=>
      string(2) "C#"
    }
    [1]=>
    array(3) {
      [0]=>
      int(2)
      [1]=>
      string(4) "Josh"
      [2]=>
      string(3) "ASP"
    }
  }
  [3]=>
  array(1) {
    [0]=>
    array(3) {
      [0]=>
      int(3)
      [1]=>
      string(5) "Steve"
      [2]=>
      string(3) "SQL"
    }
  }
}

9

先消费并缓存要按组进行的列值,然后将剩余数据作为新的子数组推入您在结果中创建的组。

function array_group(array $data, $by_column)
{
    $result = [];
    foreach ($data as $item) {
        $column = $item[$by_column];
        unset($item[$by_column]);
        $result[$column][] = $item;
    }
    return $result;
}

这个正确答案与此页面上的其他答案不同,因为它从推入每个组的子数组中删除了分组列。 - mickmackusa

5

如果您需要一种具有完整测试套件的Composer替代方案,则array_group_by函数可以实现您所需的功能。完全披露:我是该库的作者。

$grouped = array_group_by($arr, 'id');

它还支持多级分组,甚至可以通过使用自定义回调函数进行复杂分组:
// Multilevel grouping
$grouped = array_group_by($arr, 'id', 'part_no');

// Grouping by a callback/callable function
$grouped = array_group_by($records, function ($row) {
    return $row->city;
});

警告:此方法 array_group_by 引入变量 $key 作为方法参数,并在 foreach 中被覆盖。 - DannyFeliz
1
如果那个 Github 链接失效或移动,这个答案将变得无用。你的工作解决方案应该完全/静态地写在你的答案中。如果你是被引用代码的所有者,你应该进行明确的归属。请编辑你的答案。 - mickmackusa
如果您在Stack Overflow上发布脚本,它将仅显示有限高度。这意味着研究人员无需“链接追踪”即可查看您建议背后的代码。 - mickmackusa
无论是否被嵌套代码覆盖,这都是一个好的实践。同样,许多其他语言(例如JavaScript和Python)要求变量被初始化,即使它将立即被嵌套代码覆盖。当我们想要符合最佳实践的可移植代码时,PHP支持未初始化的变量是不相关的。如果条件只是一个“if/else”,可以使用三元运算符,但在这种情况下并非如此。在导入库中的几行代码几乎不会造成膨胀。您再次忽略了主要原因:可维护性和可移植性。 - Jake Z
这种行为污染了评论区,我请求停止这种无效的讨论。我们显然有不同意见。例如,我认为在一个关于适当的React组件选择列表的Stack Overflow问题中,有人完整地发布react-select库是不可取的。 - Jake Z
显示剩余5条评论

4

$arr = 数据数组;

$fldName = 按列名分组;

function array_group_by( $arr, $fldName) {
    $groups = array();
    foreach ($arr as $rec) {
        $groups[$rec[$fldName]] = $rec;
    }
    return $groups;
}

function object_group_by( $obj, $fldName) {
    $groups = array();
    foreach ($obj as $rec) {
        $groups[$rec->$fldName] = $rec;
    }
    return $groups;
}

1
我认为你的代码缺少[]来实际将项目推入组中; $groups[$rec[$fldName]][] = $rec; 而不是 $groups[$rec[$fldName]] = $rec;。对象实现也是一样的。 - Jacer Omri

2

使用LINQ非常容易,PHP中有多个库实现了LINQ,包括YaLinqo*。它允许在数组和对象上执行类似SQL的查询。 groupBy函数专门用于分组,您只需要指定要分组的字段即可:

$grouped_array = from($array)->groupBy('$v["id"]')->toArray();

'$v["id"]'是一个简写,代表了function ($v) { return $v["id"]; },该库支持此功能。

结果将与被接受的答案完全相同,只需要更少的代码。

*由我开发


你能给我一些提示如何按多个字段分组吗?在我的用例中,我有一个带有日期字段的对象,需要按年、月和日期进行分组。类似于 { 2016 => { 09 => { 15 => $object } } }。 - fnagel
1
@fnagel 嵌套分组有些复杂。请参阅有关嵌套 groupBy 的支持问题。它包括一个帮助函数 group_by_multiple,以及对嵌套分组工作原理的详细说明。使用此函数,您的查询将如下所示:group_by_multiple($objects, [ '$v->date->format("Y")', '$v->date->format("n")', '$v->date->format("j")' ]) - Athari

2

1. GROUP BY一个键

这个函数类似于数组的GROUP BY,但有一个重要的限制:只能有一个分组“列”($identifier)。

function arrayUniqueByIdentifier(array $array, string $identifier)
{
    $ids = array_column($array, $identifier);
    $ids = array_unique($ids);
    $array = array_filter($array,
        function ($key, $value) use($ids) {
            return in_array($value, array_keys($ids));
        }, ARRAY_FILTER_USE_BOTH);
    return $array;
}

2. 检测表格(二维数组)中的唯一行

这个函数是用来过滤“行”的。如果我们把一个二维数组看作是一张表格,那么它的每一个元素都是一行。因此,我们可以使用此函数来移除重复的行。如果两行(第一维的元素)相等,则表示它们所有列(第二维的元素)都相等。对于“列”值的比较应用以下规则:如果一个值是简单的类型,则会将该值本身用于比较;否则会使用它的类型(arrayobjectresourceunknown type)。

策略很简单:从原始数组中创建一个浅层次的数组,其中元素是原始数组的“列”imploded后的结果;然后在其上应用 array_unique(...) 函数;最后使用检测到的ID来过滤原始数组。

function arrayUniqueByRow(array $table = [], string $implodeSeparator)
{
    $elementStrings = [];
    foreach ($table as $row) {
        // To avoid notices like "Array to string conversion".
        $elementPreparedForImplode = array_map(
            function ($field) {
                $valueType = gettype($field);
                $simpleTypes = ['boolean', 'integer', 'double', 'float', 'string', 'NULL'];
                $field = in_array($valueType, $simpleTypes) ? $field : $valueType;
                return $field;
            }, $row
        );
        $elementStrings[] = implode($implodeSeparator, $elementPreparedForImplode);
    }
    $elementStringsUnique = array_unique($elementStrings);
    $table = array_intersect_key($table, $elementStringsUnique);
    return $table;
}

如果"column"值的类型是object,也可以改进比较方法,检测其类。

$implodeSeparator应该更加复杂,例如spl_object_hash($this)


3. 检测表格(二维数组)中具有唯一标识列的行

此解决方案依赖于第二个解决方案。现在,完整的“行”不需要是唯一的。如果一个“行”的所有相关“字段”(第二维的元素)与相应“键”(具有相同键的元素)的“字段”(第二维的元素)相等,则两个“行”(第一维的元素)现在相等。

“相关”的“字段”是具有键等于传递的“标识符”元素之一的“字段”(第二维的元素)。

function arrayUniqueByMultipleIdentifiers(array $table, array $identifiers, string $implodeSeparator = null)
{
    $arrayForMakingUniqueByRow = $removeArrayColumns($table, $identifiers, true);
    $arrayUniqueByRow = $arrayUniqueByRow($arrayForMakingUniqueByRow, $implodeSeparator);
    $arrayUniqueByMultipleIdentifiers = array_intersect_key($table, $arrayUniqueByRow);
    return $arrayUniqueByMultipleIdentifiers;
}

function removeArrayColumns(array $table, array $columnNames, bool $isWhitelist = false)
{
    foreach ($table as $rowKey => $row) {
        if (is_array($row)) {
            if ($isWhitelist) {
                foreach ($row as $fieldName => $fieldValue) {
                    if (!in_array($fieldName, $columnNames)) {
                        unset($table[$rowKey][$fieldName]);
                    }
                }
            } else {
                foreach ($row as $fieldName => $fieldValue) {
                    if (in_array($fieldName, $columnNames)) {
                        unset($table[$rowKey][$fieldName]);
                    }
                }
            }
        }
    }
    return $table;
}

重复调用 in_array() 不是最佳实践。 - mickmackusa
检测唯一行与分组有什么关系? - Alex78191

2

很简单,你可以使用我的groupBy()函数按数组中的任何“键”进行分组。

$data = [
    [
        "id" => 96,
        "shipping_no" => "212755-1",
        "part_no" => "reterty",
        "description" => "tyrfyt",
        "packaging_type" => "PC"
    ],
    [
        "id" => 96,
        "shipping_no" => "212755-1",
        "part_no" => "dftgtryh",
        "description" => "dfhgfyh",
        "packaging_type" => "PC"
    ],
    [
        "id" => 97,
        "shipping_no" => "212755-2",
        "part_no" => "ZeoDark",
        "description" => "s%c%s%c%s",
        "packaging_type" => "PC"
    ]
];

function groupBy($array, $key) {
    $groupedData = [];
    $data = [];
    $_id = "";
    for ($i=0; $i < count($array); $i++) { 
        $row = $array[$i];
        if($row[$key] != $_id){
            if(count($data) > 0){
                $groupedData[] = $data;
            }
    
            $_id = $row[$key];
            $data = [
                $key => $_id
            ];
        }
    
        unset($row[$key]);
        $data["data"][] = $row;

        if($i == count($array) - 1){
            $groupedData[] = $data;
        }
    }
    
    return $groupedData; 
}

print_r(groupBy($data, "id"));

结果将是:

Array
(
    [0] => Array
        (
            [id] => 96
            [data] => Array
                (
                    [0] => Array
                        (
                            [shipping_no] => 212755-1
                            [part_no] => reterty
                            [description] => tyrfyt
                            [packaging_type] => PC
                        )

                    [1] => Array
                        (
                            [shipping_no] => 212755-1
                            [part_no] => dftgtryh
                            [description] => dfhgfyh
                            [packaging_type] => PC
                        )

                )

        )

    [1] => Array
        (
            [id] => 97
            [data] => Array
                (
                    [0] => Array
                        (
                            [shipping_no] => 212755-2
                            [part_no] => ZeoDark
                            [description] => s%c%s%c%s
                            [packaging_type] => PC
                        )

                )

        )

)

如果您更改“key”参数,则应该无需更改即可正常工作:
print_r(groupBy($data, "shipping_no"));

Array
(
    [0] => Array
        (
            [shipping_no] => 212755-1
            [data] => Array
                (
                    [0] => Array
                        (
                            [id] => 96
                            [part_no] => reterty
                            [description] => tyrfyt
                            [packaging_type] => PC
                        )

                    [1] => Array
                        (
                            [id] => 96
                            [part_no] => dftgtryh
                            [description] => dfhgfyh
                            [packaging_type] => PC
                        )

                )

        )

    [1] => Array
        (
            [shipping_no] => 212755-2
            [data] => Array
                (
                    [0] => Array
                        (
                            [id] => 97
                            [part_no] => ZeoDark
                            [description] => s%c%s%c%s
                            [packaging_type] => PC
                        )

                )

        )

)

这个晚回答缺少教育性的解释。坦白地说,它看起来对我来说太复杂/过度设计了,我无法费心理解语法的逻辑/原因(对于如此基本的任务来说,变量、条件和函数调用太多了)。 - mickmackusa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接