Array_map de PHP, y compris les clés


208

Existe-t-il un moyen de faire quelque chose comme ça:

$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)));

Mais au lieu d'appeler array_keysetarray_values passer directement$test_array variable?

La sortie souhaitée est:

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

Voir aussi: stackoverflow.com/search?q=each_with_index pour une approche contrastée de ce problème général
dreftymac

Réponses:


207

Pas avec array_map, car il ne gère pas les clés.

array_walk fait:

$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"
// }

Cependant, cela change le tableau donné en paramètre, donc ce n'est pas exactement une programmation fonctionnelle (comme vous avez la question étiquetée comme ça). De plus, comme indiqué dans le commentaire, cela ne changera que les valeurs du tableau, donc les clés ne seront pas celles que vous avez spécifiées dans la question.

Vous pouvez écrire une fonction qui corrige les points au-dessus de vous si vous le souhaitez, comme ceci:

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"
// }

Sauf que dans ce cas, vous voulez $a = "$b loves $a", faire correspondre la sortie souhaitée de l'OP.
cmbuckley

1
correct, changé :) il est agréable de voir à quel point ils ont fait array_map de array_walk.
eis

Bien, merci. Afin d'éviter de gâcher la matrice d'origine, voici ce que j'ai finalement fait (regardez ma réponse ci-dessous)
José Tomás Tocino

3
Ce n'est pas de la "programmation fonctionnelle" car array_walk()cela ne renvoie pas le tableau résultant, mais un booléen à la place.
mae

@mae oui, comme je l'ai également écrit dans la réponse - au lieu de renvoyer la valeur, il modifie le paramètre
eis

145

C'est probablement le plus court et le plus facile à raisonner sur:

$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
Je viens de réaliser que la question disait spécifiquement de ne pas utiliser array_keys(). Cela semble cependant une exigence stupide.
Kevin Beal

3
La question a fourni une solution en utilisant array_keys (), il serait idiot de fournir une réponse qui n'a aucun avantage (par exemple, appeler moins de fonctions) sur la solution actuelle.
Chinoto Vokro

La réponse à la question d'origine est NON, et c'est la solution la plus appropriée.
usoban

65

Voici ma solution très simple, compatible avec PHP 5.5:

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

L'appelable que vous fournissez doit lui-même renvoyer un tableau avec deux valeurs, c'est-à-dire return [key, value]. L'appel interne à array_mapproduit donc un tableau de tableaux. Celui-ci est ensuite reconverti en un tableau à une dimension par array_column.

Usage

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

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

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

Production

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

Application partielle

Dans le cas où vous devez utiliser la fonction plusieurs fois avec des tableaux différents mais la même fonction de mappage, vous pouvez faire quelque chose appelé application de fonction partielle (liée au `` currying ''), qui vous permet de ne transmettre le tableau de données qu'à l'invocation:

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));

Qui produit la même sortie, donnée $funcet $ordinalssont comme précédemment.

REMARQUE: si votre fonction mappée renvoie la même clé pour deux entrées différentes, la valeur associée à la dernière clé gagnera. Inversez le tableau d'entrée et le résultat de sortie array_map_assocpour permettre aux clés antérieures de gagner. (Les clés renvoyées dans mon exemple ne peuvent pas entrer en collision car elles incorporent la clé du tableau source, qui à son tour doit être unique.)


Alternative

Voici une variante de ce qui précède, qui pourrait s'avérer plus logique pour certains, mais nécessite PHP 5.6:

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

Dans cette variante, votre fonction fournie (sur laquelle le tableau de données est mappé) devrait plutôt renvoyer un tableau associatif avec une ligne, c'est-à-dire return [key => value]. Le résultat du mappage de l'appelable est ensuite simplement décompressé et transmis à array_merge. Comme précédemment, le retour d'une clé en double entraînera la victoire des valeurs ultérieures.

nb Alex83690 a noté dans un commentaire que l'utilisation array_replaceici à la place de array_mergepréserverait les clés entières. array_replacene modifie pas le tableau d'entrée, est donc sans danger pour le code fonctionnel.

Si vous utilisez PHP 5.3 à 5.5, ce qui suit est équivalent. Il utilise array_reduceet l' +opérateur de tableau binaire pour convertir le tableau bidimensionnel résultant en un tableau unidimensionnel tout en préservant les clés:

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;
    }, []);
}

Usage

Ces deux variantes seraient utilisées ainsi:

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

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

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

Notez le =>au lieu de ,dans $func.

La sortie est la même qu'avant, et chacune peut être partiellement appliquée de la même manière qu'auparavant.


 Résumé

Le but de la question d'origine est de rendre l'appel de l'appel aussi simple que possible, au détriment d'avoir une fonction plus compliquée qui est invoquée; en particulier, pour pouvoir passer le tableau de données en un seul argument, sans diviser les clés et les valeurs. En utilisant la fonction fournie au début de cette réponse:

$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)));

Ou, pour cette question uniquement, nous pouvons faire une simplification de la array_map_assoc()fonction qui supprime les clés de sortie, car la question ne les demande pas:

$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));

Donc, la réponse est NON , vous ne pouvez pas éviter d'appeler array_keys, mais vous pouvez faire abstraction de l'endroit où array_keysest appelé une fonction d'ordre supérieur, ce qui pourrait être suffisant.


7
Il semble que cela devrait être marqué comme la bonne réponse.
eddiewould

6
Merci @eddiewould, mais j'ai environ 4 ans et demi de retard :) Je suis venu ici à la recherche d'une solution, je n'en ai pas trouvé, alors j'ai trouvé la mienne.
Nicholas Shanks

1
Je serai CE gars. PHP 5.3 ne devrait plus être une exigence pour la date de cette réponse. A MON HUMBLE AVIS.
Erutan409

1
Votre première solution alternative n'est pas valide. vous devez remplacer array_mergepar array_replacepour conserver les clés qui seraient des entiers.
Alex83690

1
@ Alex83690 Merci! Bien que je dirais que "invalide" est légèrement trompeur - c'est bien si vous n'avez pas de clés entières (comme c'était le cas dans mon propre cas).
Nicholas Shanks

20

Avec PHP5.3 ou version ultérieure:

$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
Je pense que l'exigence était "au lieu d'appeler array_keys et array_values, en passant directement la variable $ test_array", cela peut-il être utilisé sans array_keys?
eis

4

C'est ainsi que j'ai implémenté cela dans mon projet.

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));
}

Très propre et ne modifie pas le tableau d'origine!
Raffaele Candeliere

4

Regardez ici! Il y a une solution triviale!

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

Comme indiqué dans la question, a array_map déjà exactement les fonctionnalités requises . Les autres réponses ici compliquent sérieusement les choses: array_walkn'est pas fonctionnelle.

Usage

Exactement ce que vous attendez de votre exemple:

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

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

les autres réponses compliquent les choses car la question spécifiée qrrqy_keys()ne doit pas être utilisée pour # raisons
Brad Kent

2

Par «boucle manuelle», j'entendais écrire une fonction personnalisée qui utilise foreach. Cela retourne un nouveau tableau comme le array_mapfait parce que la portée de la fonction $arrayest une copie, pas une référence:

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

Votre technique utilisant array_mapavec array_keysbien que semble en fait plus simple et est plus puissante car vous pouvez utiliser nullcomme rappel pour renvoyer les paires clé-valeur:

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

boucle de tableau avec référence, peut provoquer des choses effrayantes
janenz00

Ce n'est pas effrayant, cela signifie simplement que vous l'avez oublié unset( $value )car il existe toujours dans la portée définie.
aziz punjani

@azis, plaisantait sur l'effroi, se référant à l'article. Cela créera des effets inattendus si vous oubliez de désactiver.
janenz00

1
Merci pour la réponse, mais je pensais qu'il était assez clair que je ne voulais pas utiliser une boucle traditionnelle.
José Tomás Tocino

@ janenz00 Voir la réponse modifiée pour plus de précisions. Je voulais dire boucler dans une portée de variable propre.
ryanve

1

Sur la base de la réponse d'eis , voici ce que j'ai finalement fait pour éviter de salir le tableau d'origine:

$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
Pourquoi est-ce plus facile que de simplement passer les valeurs et les clés du tableau directement à array_map? C'est plus lent et plus compliqué, je ne vois pas l'avantage.
Ariel

1
@Ariel pouvez-vous confirmer l'affirmation selon laquelle ce serait plus lent, même avec de grands nombres? Il n'a besoin d'itérer le tableau qu'une seule fois, donc je pense que les magnitudes devraient être plus rapides en grande notation O. Je suis cependant d'accord sur la complexité.
eis

@eis Il est plus lent car il crée le tableau de résultat un à la fois en PHP, au lieu de le faire en masse en C. Il évite cependant l'appel à array_keys (bien que ce soit rapide car il est en C). Comparez-le - voyez ce qui est plus rapide, je ne suis pas vraiment certain, mais généralement plus de code = code plus lent. Dans la complexité, c'est bien pire, et c'est plus important que la vitesse la plupart du temps.
Ariel

1
Vous n'avez pas besoin d'envoyer le troisième argument à array_walkcar vous ne le faites pas référence dans la fermeture.
Steven Lu

1

J'ai fait cette fonction, basée sur la réponse d'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;
}

Exemple:

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

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

Production:

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

Bien sûr, vous pouvez utiliser array_valuespour retourner exactement ce que OP souhaite.

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

@KevinBeal J'utilise beaucoup cette fonction dans mon travail. Pourriez-vous indiquer où se trouvent les erreurs?
Julio Vedovatto

2
Tout d'abord, le code tel qu'il se présente manque une vérification $arrde type tableau, cependant si vous tapez-hint vos arguments comme callableet arrayvous pouvez à la place déposer la vérification is_callable. Ensuite, vous effectuez une affectation à $ value qui est ensuite inutilisée. Vous devez simplement ignorer la valeur de retour. Troisièmement, il serait préférable de lever une exception dans le rappel que de retourner false. Vous devez alors toujours renvoyer une valeur valide ou toujours lancer.
Nicholas Shanks

1

La bibliothèque YaLinqo * est bien adaptée à ce type de tâche. C'est un port de LINQ de .NET qui prend entièrement en charge les valeurs et les clés dans tous les rappels et ressemble à SQL. Par exemple:

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

ou juste:

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

Voici '"$k loves $v"'un raccourci pour la syntaxe de fermeture complète que cette bibliothèque prend en charge. toArray()à la fin est facultative. La chaîne de méthodes renvoie un itérateur, donc si le résultat doit simplement être itéré au cours de l'utilisation foreach, l' toArrayappel peut être supprimé.

* développé par mes soins


1

Je ferais quelque chose comme ça:

<?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",
)));

?>

Résultats:

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

1

J'ajouterai encore une autre solution au problème en utilisant la version 5.6 ou ultérieure. Je ne sais pas si c'est plus efficace que les excellentes solutions (probablement pas), mais pour moi c'est juste plus simple à lire:

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

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

En utilisant strval()comme exemple de fonction dans le array_map, cela générera:

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

J'espère que je ne suis pas le seul à trouver cela assez simple à comprendre. array_combinecrée un key => valuetableau à partir d'un tableau de clés et d'un tableau de valeurs, le reste est assez explicite.


1

Vous pouvez utiliser la méthode map de cette bibliothèque de tableaux pour obtenir exactement ce que vous voulez aussi facilement que:

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

il conserve également les clés et renvoie un nouveau tableau, sans parler de quelques modes différents pour répondre à vos besoins.


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

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

La source


0

J'aime toujours la variante javascript de la carte du tableau. La version la plus simple serait:

/**
 * @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;
}

Alors maintenant, vous pouvez simplement lui passer une fonction de rappel comment construire les valeurs.

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

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

Il est plus utile d'avoir les données comme dernier argument de n'importe quelle fonction que vous écrivez, car vous pouvez ensuite créer une nouvelle fonction qui incorpore un rappel (comportement) spécifique - c'est-à-dire que vous obtenez la composition de la fonction: h(g(f($data)))s'applique f, puis g, puis hà votre Les données. Il est généralement considéré comme plus polyvalent dans la programmation fonctionnelle d'avoir une fonction qui effectue la même opération sur des données de plongeurs, que d'avoir une fonction qui applique des fonctions de plongeurs à un ensemble de données fixe.
Nicholas Shanks

Dans votre exemple, vous n'avez qu'un seul argument pour la fonction. Je trouve plus facile de mettre les données en premier argument, comme array_filter, array_reduce et les fonctions de tableau en javascript.
blablabla

C'est mon point! En passant les données en dernier, il vous permet de curry la fonction (créer une nouvelle fonction qui combine le bouclage avec l'opération spécifique) et d'appliquer cela aux données en appelant la nouvelle fonction avec un seul paramètre. Ce principe est mieux expliqué que moi ici, dans cette réponse: stackoverflow.com/a/5863222
Nicholas Shanks

L'utilisation d'une fonction de composition dans un langage comme PHP n'est-elle pas une meilleure solution à ce problème?
blablabla

1
C'est une alternative mais nécessite beaucoup plus d'investissement dans la FP, par exemple ceci: github.com/nickshanks/fp-php-talk/blob/master/lib.php#L24 ou ceci: github.com/nickshanks/php-fp/blob /master/src/fp.php#L62
Nicholas Shanks

0

Une autre façon de le faire avec (out) la préservation des clés:

$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

Je vois qu'il manque la réponse évidente:

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;
}

Fonctionne exactement comme array_map. Presque.

En fait, ce n'est pas pur mapcomme vous le savez dans d'autres langues. Php est très bizarre, il nécessite donc des fonctions utilisateur très étranges, car nous ne voulons pas casser notre cassé avec précisionworse is better approche .

Vraiment, ce n'est pas mapdu tout le cas. Pourtant, c'est toujours très utile.

  • La première différence évidente avec array_map, est que le rappel prend les sorties de each()chaque tableau d'entrée au lieu de la valeur seule. Vous pouvez toujours parcourir plusieurs tableaux à la fois.

  • La deuxième différence est la façon dont la clé est gérée après son retour de rappel; la valeur de retour de la fonction de rappel doit être array('new_key', 'new_value'). Les clés peuvent et seront modifiées, les mêmes clés peuvent même entraîner l'écrasement de la valeur précédente, si la même clé a été retournée. Ce n'est pas un mapcomportement courant , mais il vous permet de réécrire des clés.

  • La troisième chose étrange est que si vous omettez la keyvaleur de retour (soit par array(1 => 'value')ou array(null, 'value')), une nouvelle clé va être affectée, comme si elle $array[] = $valueétait utilisée. Ce n'est pas mapun comportement commun non plus, mais il est parfois utile, je suppose.

  • Quatrième chose étrange: si la fonction de rappel ne renvoie pas de valeur, ou renvoie null, l'ensemble des clés et valeurs actuelles est omis de la sortie, il est simplement ignoré. Cette fonctionnalité est totalement mapinutilisable, mais elle ferait de cette fonction un excellent doublon pour array_filter_assoc, s'il y avait une telle fonction.

  • Si vous omettez le deuxième élément ( 1 => ...) (la partie valeur ) dans le retour du rappel, nullest utilisé à la place de la valeur réelle.

  • Tous les autres éléments à l'exception de ceux avec des clés 0et 1dans le retour de rappel sont ignorés.

  • Et enfin, si lambda retourne une valeur autre que nullou tableau, il est traité comme si la clé et la valeur étaient omises, donc:

    1. une nouvelle clé pour l'élément est affectée
    2. null est utilisé comme sa valeur
AVERTISSEMENT:
Gardez à l'esprit que cette dernière fonctionnalité n'est qu'un résidu des fonctionnalités précédentes et qu'elle est probablement complètement inutile. Il est fortement déconseillé de s'appuyer sur cette fonctionnalité, car cette fonctionnalité sera obsolète au hasard et modifiée de manière inattendue dans les versions futures.

REMARQUE:
contrairement à dans array_map, tous les paramètres non tableau passés à array_map_assoc, à l'exception du premier paramètre de rappel, sont castés en silence dans les tableaux.

EXEMPLES:
// TODO: examples, anyone?

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.