Agrupe filas en una matriz asociativa de matrices asociativas por valor de columna y conserve las claves originales de primer nivel

7 minutos de lectura

Avatar de usuario de Anson Kao
anson kao

Tengo una matriz de subarreglos en el siguiente formato:

[
    'a' => ['id' => 20, 'name' => 'chimpanzee'],
    'b' => ['id' => 40, 'name' => 'meeting'],
    'c' => ['id' => 20, 'name' => 'dynasty'],
    'd' => ['id' => 50, 'name' => 'chocolate'],
    'e' => ['id' => 10, 'name' => 'bananas'],
    'f' => ['id' => 50, 'name' => 'fantasy'],
    'g' => ['id' => 50, 'name' => 'football']
]

Y me gustaría agruparlo en una nueva matriz según el campo de identificación en cada subarreglo.

array
(
    10 => array
          (
            e => array ( id = 10, name = bananas )
          )
    20 => array
          (
            a => array ( id = 20, name = chimpanzee )
            c => array ( id = 20, name = dynasty )
          )
    40 => array
          (
            b => array ( id = 40, name = meeting )
          )
    50 => array
          (
            d => array ( id = 50, name = chocolate )
            f => array ( id = 50, name = fantasy )
            g => array ( id = 50, name = football )
          )
)

Avatar de usuario de Tim Cooper
tim cooper

$arr = array();

foreach ($old_arr as $key => $item) {
   $arr[$item['id']][$key] = $item;
}

ksort($arr, SORT_NUMERIC);

  • @Herbert, ¿supongo que escribir en una identificación inexistente afecta el rendimiento? o dispara advertencias de PHP?

    –Anson Kao

    27/09/2011 a las 20:10

  • @SampleJACK: Mi error. A primera vista, pensé que estaba verificando que existe una identificación en $old_arr. Ahora que lo examino más de cerca, usando array_key_exists no agrega nada a este código. El resultado es exactamente el mismo sin él. En términos de rendimiento: llama a una función en una matriz dentro de un bucle que tiene que compensar cualquier impacto de rendimiento que reciba al escribir en una clave inexistente, por lo que sugiero dejar todo if() bloquear.

    – Herbert

    27/09/2011 a las 20:30


  • @Herbert: Lo agregué porque pensé que se mostraría un error si el umbral de informe de errores era demasiado bajo. Lo probé y no parece quejarse.

    –Tim Cooper

    27/09/2011 a las 20:39

  • @Tim: Sí, tengo mi informe de errores activado para mostrar todo y, tienes razón, no tengo quejas. No quise insinuar que era un código incorrecto de ninguna manera. SampleJACK mencionó el rendimiento y, después de pensarlo, tiene sentido dejarlo. Con toda honestidad, pensé que estaba verificando las identificaciones en las matrices internas. Eso me enseñará a leer con más atención. :p Todavía recibes mi +1 por un buen código.

    – Herbert

    27/09/2011 a las 20:50


  • Agregué una respuesta para la posteridad para aclarar de lo que he estado hablando.

    – Herbert

    27/09/2011 a las 20:51

foreach($array as $key => $value){
   $newarray[$value['id']][$key] = $value;
}

var_dump($newarray);

pan comido 😉

  • Probablemente igualmente fácil de explicar cómo funciona su código y por qué cree que es la mejor técnica para usar.

    – mickmackusa

    17 de junio de 2020 a las 8:14

  • Pero realmente, no hay ningún valor nuevo en mantener esta respuesta en la página. Esta respuesta de solo código (un duplicado exacto de la técnica de Tim) se publicó 10 minutos después de que Tim publicara.

    – mickmackusa

    17 de junio de 2020 a las 23:25

El siguiente código adapta el código de @Tim Cooper para mitigar Undefined index: id errores en el caso de que una de las matrices internas no contenga un identificación:

$arr = array();

foreach($old_arr as $key => $item)
{
    if(array_key_exists('id', $item))
        $arr[$item['id']][$key] = $item;
}

ksort($arr, SORT_NUMERIC);

Sin embargo, eliminará las matrices internas sin una identificación.

P.ej

$old_arr = array(
    'a' => array ( 'id' => 20, 'name' => 'chimpanzee' ),
    'b' => array ( 'id' => 40, 'name' => 'meeting' ),
    'c' => array ( 'id' => 20, 'name' => 'dynasty' ),
    'd' => array ( 'id' => 50, 'name' => 'chocolate' ),
    'e' => array ( 'id' => 10, 'name' => 'bananas' ),
    'f' => array ( 'id' => 50, 'name' => 'fantasy' ),
    'g' => array ( 'id' => 50, 'name' => 'football' ),
    'h' => array ( 'name' => 'bob' )
);

dejará caer la matriz ‘h’ por completo.

  • Este es un “problema inventado”, no representado en la pregunta del OP. Probablemente sea mejor encontrar otra pregunta que plantee este problema y publicarla allí.

    – mickmackusa

    17 de junio de 2020 a las 23:13


Avatar de usuario de Piotr Olaszewski
Piotr Olaszewski

También puedes usar Arrays::groupBy() de golosinas de ouzo:

$groupBy = Arrays::groupBy($array, Functions::extract()->id);

print_r($groupBy);

Y resultado:

Array
(
    [20] => Array
        (
            [0] => Array
                (
                    [id] => 20
                    [name] => chimpanzee
                )

            [1] => Array
                (
                    [id] => 20
                    [name] => dynasty
                )

        )

    [40] => Array
        (
            [0] => Array
                (
                    [id] => 40
                    [name] => meeting
                )

        )

    [50] => Array
        (
            [0] => Array
                (
                    [id] => 50
                    [name] => chocolate
                )

            [1] => Array
                (
                    [id] => 50
                    [name] => fantasy
                )

            [2] => Array
                (
                    [id] => 50
                    [name] => football
                )

        )

    [10] => Array
        (
            [0] => Array
                (
                    [id] => 10
                    [name] => bananas
                )

        )

)

Y aquí están los documentos para arreglos y Funciones.

Aquí hay una función que tomará una matriz como primer argumento y un criterio (una cadena o función de devolución de llamada) como segundo argumento. La función devuelve una nueva matriz que agrupa la matriz como se solicitó.

/**
 * Group items from an array together by some criteria or value.
 *
 * @param  $arr array The array to group items from
 * @param  $criteria string|callable The key to group by or a function the returns a key to group by.
 * @return array
 *
 */
function groupBy($arr, $criteria): array
{
    return array_reduce($arr, function($accumulator, $item) use ($criteria) {
        $key = (is_callable($criteria)) ? $criteria($item) : $item[$criteria];
        if (!array_key_exists($key, $accumulator)) {
            $accumulator[$key] = [];
        }

        array_push($accumulator[$key], $item);
        return $accumulator;
    }, []);
}

Aquí está la matriz dada:

$arr = array(
    'a' => array ( 'id' => 20, 'name' => 'chimpanzee' ),
    'b' => array ( 'id' => 40, 'name' => 'meeting' ),
    'c' => array ( 'id' => 20, 'name' => 'dynasty' ),
    'd' => array ( 'id' => 50, 'name' => 'chocolate' ),
    'e' => array ( 'id' => 10, 'name' => 'bananas' ),
    'f' => array ( 'id' => 50, 'name' => 'fantasy' ),
    'g' => array ( 'id' => 50, 'name' => 'football' )
);

Y ejemplos usando la función con una cadena y una función de devolución de llamada:

$q = groupBy($arr, 'id');
print_r($q);

$r = groupBy($arr, function($item) {
    return $item['id'];
});
print_r($r);

Los resultados son los mismos en ambos ejemplos:

Array
(
    [20] => Array
        (
            [0] => Array
                (
                    [id] => 20
                    [name] => chimpanzee
                )

            [1] => Array
                (
                    [id] => 20
                    [name] => dynasty
                )

        )

    [40] => Array
        (
            [0] => Array
                (
                    [id] => 40
                    [name] => meeting
                )

        )

    [50] => Array
        (
            [0] => Array
                (
                    [id] => 50
                    [name] => chocolate
                )

            [1] => Array
                (
                    [id] => 50
                    [name] => fantasy
                )

            [2] => Array
                (
                    [id] => 50
                    [name] => football
                )

        )

    [10] => Array
        (
            [0] => Array
                (
                    [id] => 10
                    [name] => bananas
                )

        )

)

Pasar la devolución de llamada es una exageración en el ejemplo anterior, pero usar la devolución de llamada encuentra su uso cuando pasa una matriz de objetos, una matriz multidimensional o tiene algo arbitrario por el que desea agrupar.

Avatar de usuario de SaeedPooyanfar
SaeedPooyanfar

Tal vez valga la pena mencionar que también puedes usar php array_reduce función

$items = [
    ['id' => 20, 'name' => 'chimpanzee'],
    ['id' => 40, 'name' => 'meeting'],
    ['id' => 20, 'name' => 'dynasty'],
    ['id' => 50, 'name' => 'chocolate'],
    ['id' => 10, 'name' => 'bananas'],
    ['id' => 50, 'name' => 'fantasy'],
    ['id' => 50, 'name' => 'football'],
];

// Grouping
$groupedItems = array_reduce($items, function ($carry, $item) {
    $carry[$item['id']][] = $item;
    return $carry;
}, []);
// Sorting
ksort($groupedItems, SORT_NUMERIC);

print_r($groupedItems);

https://www.php.net/manual/en/function.array-reduce.php

avatar de usuario de mickmackusa
mickmackusa

Debido a cómo el algoritmo de clasificación de PHP trata las matrices multidimensionales: clasifica por tamaño, luego compara los elementos uno a la vez, en realidad puede usar una clasificación de conservación de claves en la entrada ANTES de la reestructuración. En la programación de estilo funcional, esto significa que no necesita declarar la matriz de resultados como una variable.

Código: (Manifestación)

asort($array);
var_export(
    array_reduce(
        array_keys($array),
        function($result, $k) use ($array) {
            $result[$array[$k]['id']][$k] = $array[$k];
            return $result;
        }
    )
);

Debo decir que la programación funcional no es muy atractiva para esta tarea porque se deben conservar las claves de primer nivel.

A pesar de array_walk() es más sucinto, aún requiere que la matriz de resultados se pase al cierre como una variable de referencia. (Manifestación)

asort($array);
$result = [];
array_walk(
    $array,
    function($row, $k) use (&$result) {
        $result[$row['id']][$k] = $row;
    }
);
var_export($result);

Probablemente recomendaría un bucle clásico para esta tarea. Lo único que debe hacer el bucle es reorganizar las teclas de primer y segundo nivel. (Manifestación)

asort($array);
$result = [];
foreach ($array as $k => $row) {
    $result[$row['id']][$k] = $row;
}
var_export($result);

Para ser completamente honesto, espero que ksort() será más eficiente que la clasificación previa al bucle, pero quería una alternativa viable.

¿Ha sido útil esta solución?