Array_map de PHP que incluye claves


208

¿Hay alguna manera de hacer algo como esto?

$test_array = array("first_key" => "first_value", 
                    "second_key" => "second_value");

var_dump(array_map(function($a, $b) { return "$a loves $b"; }, 
         array_keys($test_array), 
         array_values($test_array)));

Pero en lugar de llamar array_keysy array_values, ¿pasar directamente la $test_arrayvariable?

La salida deseada es:

array(2) {
  [0]=>
  string(27) "first_key loves first_value"
  [1]=>
  string(29) "second_key loves second_value"
}

Vea también: stackoverflow.com/search?q=each_with_index para un enfoque contrastante de este problema general
dreftymac

Respuestas:


207

No con array_map, ya que no maneja claves.

array_walk hace:

$test_array = array("first_key" => "first_value",
                    "second_key" => "second_value");
array_walk($test_array, function(&$a, $b) { $a = "$b loves $a"; });
var_dump($test_array);

// array(2) {
//   ["first_key"]=>
//   string(27) "first_key loves first_value"
//   ["second_key"]=>
//   string(29) "second_key loves second_value"
// }

Sin embargo, cambia la matriz dada como parámetro, por lo que no es exactamente una programación funcional (ya que tiene la pregunta etiquetada así). Además, como se señaló en el comentario, esto solo cambiará los valores de la matriz, por lo que las claves no serán lo que especificó en la pregunta.

Si lo desea, puede escribir una función que arregle los puntos por encima de usted:

function mymapper($arrayparam, $valuecallback) {
  $resultarr = array();
  foreach ($arrayparam as $key => $value) {
    $resultarr[] = $valuecallback($key, $value);
  }
  return $resultarr;
}

$test_array = array("first_key" => "first_value",
                    "second_key" => "second_value");
$new_array = mymapper($test_array, function($a, $b) { return "$a loves $b"; });
var_dump($new_array);

// array(2) {
//   [0]=>
//   string(27) "first_key loves first_value"
//   [1]=>
//   string(29) "second_key loves second_value"
// }

Excepto que, en este caso, desea $a = "$b loves $a"que coincida con la salida deseada del OP.
cmbuckley

1
correcto, cambiado :) es agradable lo diferente que han hecho array_map de array_walk.
Eis

Genial gracias. Para evitar estropear la matriz original, esto es lo que eventualmente hice (mira mi respuesta a continuación)
José Tomás Tocino

3
Sin embargo, esto no es "programación funcional" ya array_walk()que no devuelve la matriz resultante, sino un bool.
mae

@mae sí, como he escrito en la respuesta tan bien - en lugar de devolver el valor que cambia el parámetro
EIS

145

Este es probablemente el más corto y fácil de razonar sobre:

$states = array('az' => 'Arizona', 'al' => 'Alabama');

array_map(function ($short, $long) {
    return array(
        'short' => $short,
        'long'  => $long
    );
}, array_keys($states), $states);

// produces:
array(
     array('short' => 'az', 'long' => 'Arizona'), 
     array('short' => 'al', 'long' => 'Alabama')
)

15
Me acabo de dar cuenta de que la pregunta específicamente decía que no se usara array_keys(). Sin embargo, eso parece un requisito tonto.
Kevin Beal

3
La pregunta proporcionó una solución usando array_keys (), sería una tontería proporcionar una respuesta que no tenga ninguna ventaja (por ejemplo, llamar a menos funciones) sobre la solución actual.
Chinoto Vokro

La respuesta a la pregunta original es NO, y esta es la solución más adecuada.
usoban

65

Aquí está mi solución muy simple, compatible con PHP 5.5:

function array_map_assoc(callable $f, array $a) {
    return array_column(array_map($f, array_keys($a), $a), 1, 0);
}

El invocable que proporcione debería devolver una matriz con dos valores, es decir return [key, value]. La llamada interna a, array_mappor lo tanto, produce una matriz de matrices. Esto se convierte de nuevo en una matriz de una sola dimensión por array_column.

Uso

$ordinals = [
    'first' => '1st',
    'second' => '2nd',
    'third' => '3rd',
];

$func = function ($k, $v) {
    return ['new ' . $k, 'new ' . $v];
};

var_dump(array_map_assoc($func, $ordinals));

Salida

array(3) {
  ["new first"]=>
  string(7) "new 1st"
  ["new second"]=>
  string(7) "new 2nd"
  ["new third"]=>
  string(7) "new 3rd"
}

Aplicación parcial

En caso de que necesite usar la función muchas veces con diferentes matrices pero con la misma función de mapeo, puede hacer algo llamado aplicación de función parcial (relacionada con ' curry '), que le permite pasar solo la matriz de datos tras la invocación:

function array_map_assoc_partial(callable $f) {
    return function (array $a) use ($f) {
        return array_column(array_map($f, array_keys($a), $a), 1, 0);
    };
}

...
$my_mapping = array_map_assoc_partial($func);
var_dump($my_mapping($ordinals));

Lo que produce el mismo resultado, dado $funcy $ordinalsson como antes.

NOTA: si su función asignada devuelve la misma tecla para dos entradas diferentes, el valor asociado con la tecla posterior ganará. Invierta la matriz de entrada y el resultado de salida array_map_assocpara permitir que las teclas anteriores ganen. (Las claves devueltas en mi ejemplo no pueden colisionar ya que incorporan la clave de la matriz fuente, que a su vez debe ser única).


Alternativa

La siguiente es una variante de lo anterior, que puede resultar más lógico para algunos, pero requiere PHP 5.6:

function array_map_assoc(callable $f, array $a) {
    return array_merge(...array_map($f, array_keys($a), $a));
}

En esta variante, su función suministrada (sobre la cual se asigna la matriz de datos) debería devolver una matriz asociativa con una fila, es decir return [key => value]. El resultado de mapear el invocable simplemente se desempaqueta y se pasa a array_merge. Como antes, devolver una clave duplicada dará como resultado valores posteriores ganadores.

nb Alex83690 ha notado en un comentario que usar array_replaceaquí en lugar de array_mergepreservaría las claves de enteros. array_replaceno modifica la matriz de entrada, por lo que es seguro para el código funcional.

Si está en PHP 5.3 a 5.5, lo siguiente es equivalente. Utiliza array_reducey el +operador de matriz binaria para convertir la matriz bidimensional resultante en una matriz unidimensional mientras conserva las claves:

function array_map_assoc(callable $f, array $a) {
    return array_reduce(array_map($f, array_keys($a), $a), function (array $acc, array $a) {
        return $acc + $a;
    }, []);
}

Uso

Ambas variantes se usarían así:

$ordinals = [
    'first' => '1st',
    'second' => '2nd',
    'third' => '3rd',
];

$func = function ($k, $v) {
    return ['new ' . $k => 'new ' . $v];
};

var_dump(array_map_assoc($func, $ordinals));

Tenga =>en cuenta el en lugar de ,en $func.

El resultado es el mismo que antes, y cada uno se puede aplicar parcialmente de la misma manera que antes.


 Resumen

El objetivo de la pregunta original es hacer que la invocación de la llamada sea lo más simple posible, a expensas de tener una función más complicada que se invoca; especialmente, tener la capacidad de pasar la matriz de datos como un solo argumento, sin dividir las claves y los valores. Usando la función provista al comienzo de esta respuesta:

$test_array = ["first_key" => "first_value",
               "second_key" => "second_value"];

$array_map_assoc = function (callable $f, array $a) {
    return array_column(array_map($f, array_keys($a), $a), 1, 0);
};

$f = function ($key, $value) {
    return [$key, $key . ' loves ' . $value];
};

var_dump(array_values($array_map_assoc($f, $test_array)));

O, solo para esta pregunta, podemos hacer una simplificación de la array_map_assoc()función que elimina las teclas de salida, ya que la pregunta no las solicita:

$test_array = ["first_key" => "first_value",
               "second_key" => "second_value"];

$array_map_assoc = function (callable $f, array $a) {
    return array_map($f, array_keys($a), $a);
};

$f = function ($key, $value) {
    return $key . ' loves ' . $value;
};

var_dump($array_map_assoc($f, $test_array));

Entonces, la respuesta es NO , no puede evitar llamar array_keys, pero puede abstraer el lugar donde array_keysse llama a una función de orden superior, lo que podría ser lo suficientemente bueno.


77
Parece que esto debería marcarse como la respuesta correcta.
eddiewould

66
Gracias @eddiewould, pero llevo unos 4 años y medio demasiado tarde :) Vine aquí buscando una solución, no encontré ninguna que me gustara, así que se me ocurrió la mía.
Nicholas Shanks

1
Yo seré ESE chico. PHP 5.3 ya no debería ser un requisito para la fecha de esta respuesta. EN MI HUMILDE OPINIÓN.
Erutan409

1
Su primera solución alternativa no válida. debe reemplazar array_mergepor array_replacepara preservar claves que serían enteros.
Alex83690

1
@ Alex83690 ¡Gracias! Aunque diría que "inválido" es un poco engañoso, está bien si no tienes ninguna clave entera (como era cierto en mi propio caso).
Nicholas Shanks

20

Con PHP5.3 o posterior:

$test_array = array("first_key" => "first_value", 
                    "second_key" => "second_value");

var_dump(
    array_map(
        function($key) use ($test_array) { return "$key loves ${test_array[$key]}"; },
        array_keys($test_array)
    )
);

1
Creo que el requisito era "en lugar de llamar a array_keys y array_values, pasando directamente la variable $ test_array", ¿se puede usar sin array_keys?
EIS

4

Así es como he implementado esto en mi proyecto.

function array_map_associative(callable $callback, $array) {
    /* map original array keys, and call $callable with $key and value of $key from original array. */
    return array_map(function($key) use ($callback, $array){
        return $callback($key, $array[$key]);
    }, array_keys($array));
}

Muy limpio, y no altera la matriz original!
Raffaele Candeliere

4

¡Mira aquí! ¡Hay una solución trivial!

function array_map2(callable $f, array $a)
{
    return array_map($f, array_keys($a), $a);
}

Como se indica en la pregunta, array_map ya tiene exactamente la funcionalidad requerida . Las otras respuestas aquí complican array_walkdemasiado las cosas: no es funcional.

Uso

Exactamente como esperarías de tu ejemplo:

$test_array = array("first_key" => "first_value", 
                    "second_key" => "second_value");

var_dump(array_map2(function($a, $b) { return "$a loves $b"; }, $test_array));

las otras respuestas complican demasiado las cosas porque la pregunta especificada qrrqy_keys()no debe usarse para #razones
Brad Kent

2

Por "bucle manual" me refería a escribir una función personalizada que usa foreach. Esto devuelve una nueva matriz como lo array_maphace porque el alcance de la función hace $arrayque sea una copia, no una referencia:

function map($array, callable $fn) {
  foreach ($array as $k => &$v) $v = call_user_func($fn, $k, $v);
  return $array;
}

Sin embargo, su técnica array_mapcon usar array_keysparece más simple y es más poderosa porque puede usarla nullcomo devolución de llamada para devolver los pares clave-valor:

function map($array, callable $fn = null) {
  return array_map($fn, array_keys($array), $array);
}

looping matriz con referencia, puede causar cosas espeluznantes que sucedan
janenz00

No es espeluznante, solo significa que se olvidó unset( $value )porque todavía existe en el alcance definido.
aziz punjani

@azis, bromeaba sobre lo espeluznante, refiriéndose al artículo. Creará efectos inesperados si olvida desarmar.
janenz00

1
Gracias por la respuesta, pero pensé que estaba bastante claro que no quería usar un bucle tradicional.
José Tomás Tocino

@ janenz00 Vea la respuesta editada para aclaraciones. Me refería a bucle en un alcance variable limpio.
Ryan

1

Según la respuesta de eis , esto es lo que eventualmente hice para evitar estropear la matriz original:

$test_array = array("first_key" => "first_value",
                    "second_key" => "second_value");

$result_array = array();
array_walk($test_array, 
           function($a, $b) use (&$result_array) 
           { $result_array[] = "$b loves $a"; }, 
           $result_array);
var_dump($result_array);

2
¿Por qué es esto más fácil que simplemente pasar los valores de la matriz y las claves directamente a array_map? Es más lento y más complicado, no veo la ventaja.
Ariel

1
@Ariel, ¿puedes respaldar la afirmación de que sería más lento, incluso con grandes números? Necesita iterar la matriz solo una vez, por lo que creo que debería ser magnitudes más rápido en la notación O grande. Sin embargo, estoy de acuerdo con la complejidad.
EIS

@eis Es más lento porque está creando la matriz de resultados uno a la vez en PHP, en lugar de en masa en C. Sin embargo, evita la llamada a array_keys (aunque eso es rápido ya que está en C). Compárelo: vea cuál es más rápido, no estoy realmente seguro, pero generalmente más código = código más lento. Sin embargo, en complejidad es definitivamente peor, y eso es más importante que la velocidad la mayor parte del tiempo.
Ariel

1
No es necesario que envíe el tercer argumento array_walkya que no lo está haciendo referencia en el cierre.
Steven Lu

1

Hice esta función, basada en la respuesta de eis :

function array_map_($callback, $arr) {
    if (!is_callable($callback))
        return $arr;

    $result = array_walk($arr, function(&$value, $key) use ($callback) {
        $value = call_user_func($callback, $key, $value);
    });

    if (!$result)
        return false;

    return $arr;
}

Ejemplo:

$test_array = array("first_key" => "first_value", 
                "second_key" => "second_value");

var_dump(array_map_(function($key, $value){
    return $key . " loves " . $value;
}, $arr));

Salida:

array (
  'first_key' => 'first_key loves first_value,
  'second_key' => 'second_key loves second_value',
)

Por supuesto, puede usar array_valuespara devolver exactamente lo que OP quiere.

array_values(array_map_(function($key, $value){
    return $key . " loves " . $value;
}, $test_array))

@KevinBeal Utilizo mucho esta función en mi trabajo. ¿Podría señalar dónde están los errores?
Julio Vedovatto

2
En primer lugar, al código tal como está le falta una verificación que $arres de tipo matriz, sin embargo, si escribe una pista de sus argumentos como callabley en arraysu lugar puede soltar la verificación is_callable. A continuación, realiza una asignación a $ value que luego no se utiliza. Simplemente debe ignorar el valor de retorno. En tercer lugar, sería mejor lanzar una excepción en la devolución de llamada que devolver falso. Entonces, siempre devolverá un valor válido o siempre arrojará.
Nicholas Shanks

1

La biblioteca YaLinqo * es adecuada para este tipo de tarea. Es un puerto de LINQ de .NET que admite completamente valores y claves en todas las devoluciones de llamada y se asemeja a SQL. Por ejemplo:

$mapped_array = from($test_array)
    ->select(function ($v, $k) { return "$k loves $v"; })
    ->toArray();

o solo:

$mapped_iterator = from($test_array)->select('"$k loves $v"');

Aquí '"$k loves $v"'hay un acceso directo para la sintaxis de cierre completo que admite esta biblioteca. toArray()Al final es opcional. La cadena de métodos devuelve un iterador, por lo que si el resultado solo necesita repetirse usando foreach, la toArrayllamada puede eliminarse.

* desarrollado por mí


1

Haría algo como esto:

<?php

/**
 * array_map_kv()
 *   An array mapping function to map with both keys and values.
 *
 * @param $callback callable
 *   A callback function($key, $value) for mapping values.
 * @param $array array
 *   An array for mapping.
 */
function array_map_kv(callable $callback, array $array) {
  return array_map(
    function ($key) use ($callback, $array) {
      return $callback($key, $array[$key]); // $callback($key, $value)
    },
    array_keys($array)
  );
}

// use it
var_dump(array_map_kv(function ($key, $value) {
  return "{$key} loves {$value}";
}, array(
  "first_key" => "first_value",
  "second_key" => "second_value",
)));

?>

Resultados:

array(2) {
  [0]=>
  string(27) "first_key loves first_value"
  [1]=>
  string(29) "second_key loves second_value"
}

1

Agregaré otra solución al problema usando la versión 5.6 o posterior. No sé si es más eficiente que las soluciones ya excelentes (probablemente no), pero para mí es más simple de leer:

$myArray = [
    "key0" => 0,
    "key1" => 1,
    "key2" => 2
];

array_combine(
    array_keys($myArray),
    array_map(
        function ($intVal) {
            return strval($intVal);
        },
        $myArray
    )
);

Usando strval()como una función de ejemplo en el array_map, esto generará:

array(3) {
  ["key0"]=>
  string(1) "0"
  ["key1"]=>
  string(1) "1"
  ["key2"]=>
  string(1) "2"
}

Espero no ser el único que encuentre esto bastante simple de entender. array_combinecrea una key => valuematriz a partir de una matriz de claves y una matriz de valores, el resto es bastante explicativo.


1

Puede usar el método de mapa de esta biblioteca de matriz para lograr exactamente lo que desea tan fácilmente como:

Arr::map($test_array, function($a, $b) { return "$a loves $b"; });

También conserva las claves y devuelve una nueva matriz, sin mencionar algunos modos diferentes para satisfacer sus necesidades.


1
$array = [
  'category1' => 'first category',
  'category2' => 'second category',
];

$new = array_map(function($key, $value) {
  return "{$key} => {$value}";
}, array_keys($array), $array);

Fuente


0

Siempre me gusta la variante de JavaScript del mapa de matriz. La versión más simple sería:

/**
 * @param  array    $array
 * @param  callable $callback
 * @return array
 */
function arrayMap(array $array, callable $callback)
{
    $newArray = [];

    foreach( $array as $key => $value )
    {
        $newArray[] = call_user_func($callback, $value, $key, $array);
    }

    return $newArray;
}

Entonces, ahora puede pasarle una función de devolución de llamada cómo construir los valores.

$testArray = [
    "first_key" => "first_value", 
    "second_key" => "second_value"
];

var_dump(
    arrayMap($testArray, function($value, $key) {
        return $key . ' loves ' . $value;
    });
);

Es más útil tener los datos como último argumento para cualquier función que escriba, ya que puede crear una nueva función que incorpore alguna devolución de llamada específica (comportamiento), es decir, obtenga la composición de la función: se h(g(f($data)))aplica f, luego g, ha su datos. En general, se considera más versátil en la programación funcional tener una función que realice la misma operación en los datos de los buzos, que tener una función que aplique diversas funciones a un conjunto de datos fijo.
Nicholas Shanks

En su ejemplo, solo tiene 1 argumento para la función. Me resulta más fácil poner los datos como primer argumento, como array_filter, array_reduce y las funciones de matriz en javascript.
blablabla

¡Ese es mi punto! Con la aprobación de los datos de la última, que le permite ganarse el la función (crear una nueva función que combina el bucle con la operación específica) y solicitar que los datos llamando a la nueva función con un solo parámetro. Este director se explica mejor que yo aquí, en esta respuesta: stackoverflow.com/a/5863222
Nicholas Shanks

¿Usar una función de redacción en un lenguaje como PHP no es una mejor solución para este problema?
blablabla

1
Es una alternativa, pero requiere mucha más inversión en FP, por ejemplo esto: github.com/nickshanks/fp-php-talk/blob/master/lib.php#L24 o esto: github.com/nickshanks/php-fp/blob /master/src/fp.php#L62
Nicholas Shanks

0

Otra forma de hacer esto con (fuera) conservando claves:

$test_array = [
    "first_key"     => "first_value",
    "second_key"    => "second_value"
];

$f = function($ar) {
    return array_map(
        function($key, $val) {
            return "{$key} - {$val}";
        },
        array_keys($ar),
        $ar
    );
};

#-- WITHOUT preserving keys
$res = $f($test_array);

#-- WITH preserving keys
$res = array_combine(
    array_keys($test_array),
    $f($test_array)
);

-2

Veo que falta la respuesta obvia:

function array_map_assoc(){
    if(func_num_args() < 2) throw new \BadFuncionCallException('Missing parameters');

    $args = func_get_args();
    $callback = $args[0];

    if(!is_callable($callback)) throw new \InvalidArgumentException('First parameter musst be callable');

    $arrays = array_slice($args, 1);

    array_walk($arrays, function(&$a){
        $a = (array)$a;
        reset($a);
    });

    $results = array();
    $max_length = max(array_map('count', $arrays));

    $arrays = array_map(function($pole) use ($max_length){
        return array_pad($pole, $max_length, null);
    }, $arrays);

    for($i=0; $i < $max_length; $i++){
        $elements = array();
        foreach($arrays as &$v){
            $elements[] = each($v);
        }
        unset($v);

        $out = call_user_func_array($callback, $elements);

        if($out === null) continue;

        $val = isset($out[1]) ? $out[1] : null;

        if(isset($out[0])){
            $results[$out[0]] = $val;
        }else{
            $results[] = $val;
        }
    }

    return $results;
}

Funciona exactamente como array_map. Casi.

En realidad, no es puro mapcomo lo sabes de otros idiomas. Php es muy extraño, por lo que requiere algunas funciones de usuario muy extrañas, ya que no queremos romper nuestro worse is betterenfoque roto con precisión .

Realmente, en realidad no es mapen absoluto. Sin embargo, sigue siendo muy útil.

  • La primera diferencia obvia de array_map es que la devolución de llamada toma salidas de each()cada matriz de entrada en lugar de solo el valor. Todavía puede iterar a través de más matrices a la vez.

  • La segunda diferencia es la forma en que se maneja la clave después de que se devuelve de la devolución de llamada; El valor de retorno de la función de devolución de llamada debe ser array('new_key', 'new_value'). Las claves pueden y serán cambiadas, las mismas claves pueden incluso hacer que se sobrescriba el valor anterior, si se devuelve la misma clave. Este no es un mapcomportamiento común , pero le permite reescribir claves.

  • La tercera cosa extraña es que, si omite el keyvalor de retorno (ya sea por array(1 => 'value')o array(null, 'value')), se asignará una nueva clave, como si $array[] = $valuese usara. Ese tampoco mapes el comportamiento común, sin embargo, a veces resulta útil, supongo.

  • La cuarta cosa extraña es que, si la función de devolución de llamada no devuelve un valor, o devuelve null, todo el conjunto de claves y valores actuales se omite de la salida, simplemente se omite. Esta característica es totalmente un mappy, sin embargo, haría de esta función un excelente truco doble array_filter_assoc, si existiera dicha función.

  • Si omite el segundo elemento ( 1 => ...) (la parte del valor ) en el retorno de devolución de llamada, nullse utiliza en lugar del valor real.

  • Cualquier otro elemento excepto aquellos con claves 0y 1en el retorno de devolución de llamada se ignoran.

  • Y, por último, si lambda devuelve cualquier valor excepto nullo matriz, se trata como si se omitieran tanto la clave como el valor, por lo que:

    1. se asigna una nueva clave para el elemento
    2. null se usa como valor
ADVERTENCIA:
Tenga en cuenta que esta última característica es solo un residuo de las características anteriores y probablemente sea completamente inútil. Se desaconseja confiar en esta función, ya que esta característica será desaprobada al azar y cambiará inesperadamente en futuras versiones.

NOTA: a
diferencia de array_map, todos los parámetros que no son de matriz pasados ​​a array_map_assoc, con la excepción del primer parámetro de devolución de llamada, se convierten silenciosamente en matrices.

EJEMPLOS
// TODO: examples, anyone?

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.