web-dev-qa-db-fra.com

Tableau PHP avec les clés

Y a-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_keys et array_values, en passant directement la variable $test_array?

La sortie souhaitée est:

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

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 modifie le tableau donné en paramètre, donc ce n'est pas une programmation fonctionnelle (vous avez la question étiquetée comme ça). En outre, comme indiqué dans le commentaire, cela ne modifiera que les valeurs du tableau, les clés ne seront donc 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"
// }
146
eis

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')
)
108
Man Personson

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, à savoir return [key, value]. L'appel interne à array_map génère donc un tableau de tableaux. Ceci 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));

Sortie

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

Application partielle

Si vous devez utiliser la fonction plusieurs fois avec plusieurs tableaux, mais avec la même fonction de mappage, vous pouvez effectuer une opération appelée application de fonction partielle (liée à ' currying '), qui vous permet uniquement de passer le tableau de données lors de 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));

Ce qui produit le même résultat, étant donné que $func et $ordinals sont comme précédemment.

REMARQUE: si votre fonction mappée retourne la même clé pour deux entrées différentes, la valeur associée à la clé la plus récente l'emportera. Inversez le tableau d'entrée et le résultat de sortie de array_map_assoc pour permettre aux clés précédentes 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 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, la fonction fournie (sur laquelle le tableau de données est mappé) doit plutôt renvoyer un tableau associatif avec une ligne, c'est-à-dire return [key => value]. Le résultat du mappage de l'appelable est alors simplement décompressé et passé à array_merge. Comme précédemment, le renvoi d'une clé en double entraîne la victoire des valeurs ultérieures.

nb Alex83690 a noté dans un commentaire que l'utilisation de array_replace ici au lieu de array_merge permettrait de conserver les clés entières. array_replace ne modifie pas le tableau d'entrée, il est donc sans danger pour le code fonctionnel.

Si vous êtes sur PHP 5.3 à 5.5, ce qui suit est équivalent. Il utilise array_reduce et l'opérateur de tableau binaire + pour convertir le tableau à deux dimensions obtenu en un tableau à une dimension 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 que précédemment et chacune peut être partiellement appliquée de la même manière qu'avant.


 Résumé

Le but de la question initiale est de simplifier le plus possible l'appel de l'appel, au détriment d'une fonction plus complexe invoquée. en particulier, pour pouvoir passer le tableau de données en tant qu’argument unique, sans fractionner 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 simplifier la fonction array_map_assoc() 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 NO, vous ne pouvez pas éviter d'appeler array_keys, mais vous pouvez résumer l'endroit où array_keys est appelé dans une fonction d'ordre supérieur, ce qui peut être suffisant.

44
Nicholas Shanks

Avec PHP 5.3 ou plus tard:

$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)
    )
);
20
Tadas Sasnauskas

Voici comment 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));
}
3
Jijo

YaLinqo library * convient parfaitement à ce type de tâche. C'est un port de LINQ à partir de .NET qui supporte pleinement 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"');

'"$k loves $v"' est ici un raccourci pour la syntaxe de fermeture complète prise en charge par cette bibliothèque. toArray() à la fin est facultatif. La chaîne de méthodes renvoie un itérateur. Par conséquent, si le résultat doit simplement être itéré à l'aide de foreach, l'appel toArray peut être supprimé.

* développé par moi

2
Athari

D'après la réponse de eis , voici ce que j'ai finalement fait pour éviter de gâcher 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

Par "boucle manuelle", je voulais dire écrire une fonction personnalisée qui utilise foreach. Cela retourne un nouveau tableau comme array_map parce que la portée de la fonction fait que $array est une copie et non 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_map avec array_keys semble en réalité plus simple et plus puissante, car vous pouvez utiliser null comme rappel pour renvoyer les paires clé-valeur:

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

J'ai créé cette fonction en me basant sur la réponse 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;
}

Exemple:

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

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

Sortie:

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

Bien sûr, vous pouvez utiliser array_values pour renvoyer exactement ce que veut OP.

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

Regarde 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, array_mapa déjà exactement la fonctionnalité requise. Les autres réponses ici trop compliquent sérieusement les choses: array_walk n'est pas fonctionnel.

Utilisation  

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

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
Koala Yeung

J'ajouterai une autre solution au problème avec la version 5.6 ou ultérieure. Je ne sais pas si c'est plus efficace que les solutions déjà géniales (probablement pas), mais pour moi, c'est simplement 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, ceci générera:

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

Espérons que je ne suis pas le seul à trouver cela assez simple à comprendre .array_combine crée un tableau key => value à partir d'un tableau de clés et d'un tableau de valeurs, le reste est assez explicite.

0
Francesco D.M.

J'aime toujours la variante javascript de la carte matricielle. 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 pour construire les valeurs.

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

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

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 map comme vous le savez dans d'autres langues. Php est très étrange, il nécessite donc des fonctions utilisateur très étranges, car nous ne voulons pas casser notre approche worse is better avec précision.

Vraiment, ce n'est pas réellement map du tout. Pourtant, cela reste très utile.

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

  • La deuxième différence est la manière dont la clé est traité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 provoquer le remplacement de la valeur précédente, si la même clé était renvoyée. Ce n'est pas un comportement commun de map, mais il vous permet de réécrire des clés.

  • Troisièmement, si vous omettez key dans la valeur renvoyée (par array(1 => 'value') ou array(null, 'value')), une nouvelle clé va être affectée, comme si $array[] = $value était utilisé. Ce n'est pas non plus le comportement habituel de map, mais il est parfois utile, je suppose.

  • La quatrième chose étrange est que, si la fonction de rappel ne renvoie pas de valeur ou renvoie null, l'ensemble complet des clés et des valeurs actuelles est omis de la sortie, il est simplement ignoré. Cette fonctionnalité est totalement unmappy, mais elle ferait de cette fonction un excellent cascadeur double pour array_filter_assoc, si une telle fonction existait.

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

  • Tous les autres éléments sauf ceux avec les clés 0 et 1 dans le retour du rappel sont ignorés.

  • Et enfin, si lambda renvoie une valeur autre que null ou un tableau, elle est traitée comme si la clé et la valeur avaient été omises, ainsi: 

    1. nouvelle clé pour l'élément est attribué
    2. null est utilisé comme valeur 
ATTENTION:
N'oubliez pas que cette dernière fonctionnalité n'est qu'un résidu des fonctionnalités précédentes et qu'elle est probablement totalement inutile. Il est fortement déconseillé de s’appuyer sur cette fonctionnalité, car elle sera aléatoire. obsolète et changé de manière inattendue dans les versions futures .

REMARQUE:
Contrairement à array_map, tous les paramètres autres que les tableaux transmis à array_map_assoc, à l'exception du premier paramètre de rappel, sont transférés en mode silencieux dans des tableaux.

EXEMPLES:
// TODO: examples, anyone?

0
enrey

Une autre façon de faire cela avec (out) préserver les 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)
);
0
Blablaenzo

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

de plus, il préserve les clés et renvoie un nouveau tableau, sans oublier quelques modes différents pour répondre à vos besoins.

0
Minwork