Mappa_array di PHP comprese le chiavi


208

C'è un modo di fare qualcosa del genere:

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

Ma invece di chiamare array_keyse array_valuespassare direttamente la $test_arrayvariabile?

L'output desiderato è:

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

Vedi anche: stackoverflow.com/search?q=each_with_index per un approccio contrastante a questo problema generale
dreftymac

Risposte:


207

Non con array_map, poiché non gestisce le chiavi.

array_walk fa:

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

Tuttavia cambia l'array fornito come parametro, quindi non è esattamente una programmazione funzionale (dato che la domanda è taggata in questo modo). Inoltre, come sottolineato nel commento, questo cambierà solo i valori dell'array, quindi le chiavi non saranno quelle specificate nella domanda.

Puoi scrivere una funzione che risolva i punti sopra di te se lo desideri, in questo modo:

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

Tranne che in questo caso, si desidera $a = "$b loves $a"corrispondere all'output desiderato dell'OP.
cmbuckley,

1
corretto, cambiato :) è bello quanto diversi abbiano fatto array_map da array_walk.
eis,

Bene grazie. Per evitare di rovinare l'array originale, ecco cosa ho fatto alla fine (guarda la mia risposta di seguito)
José Tomás Tocino,

3
Questa non è una "programmazione funzionale", poiché array_walk()non restituisce l'array risultante, ma un valore bool.
maggio

@mae sì, come ho scritto anche nella risposta - invece di restituire il valore cambia il parametro
eis

145

Questo è probabilmente il più breve e facile da ragionare su:

$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
Mi sono appena reso conto che la domanda in particolare diceva di non usare array_keys(). Sembra un requisito sciocco, però.
Kevin Beal,

3
La domanda ha fornito una soluzione utilizzando array_keys (), sarebbe sciocco fornire una risposta che non abbia alcun vantaggio (ad esempio, chiamare meno funzioni) rispetto alla soluzione corrente.
Chinoto Vokro,

La risposta alla domanda originale è NO, e questa è la soluzione più appropriata.
usoban,

65

Ecco la mia soluzione molto semplice e compatibile con PHP 5.5:

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

Il callable che fornisci dovrebbe restituire un array con due valori, vale a dire return [key, value]. La chiamata interna a array_mapquindi produce una matrice di matrici. Questo viene quindi riconvertito in un array monodimensionale da 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));

Produzione

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

Applicazione parziale

Nel caso in cui sia necessario utilizzare la funzione più volte con matrici diverse ma con la stessa funzione di mappatura, è possibile eseguire qualcosa chiamato applicazione di funzione parziale (correlata a ' curry '), che consente di passare nell'array di dati solo su chiamata:

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

Che produce lo stesso output, dato $funce $ordinalssono come prima.

NOTA: se la funzione mappata restituisce la stessa chiave per due input diversi, il valore associato alla chiave successiva vincerà. Invertire la matrice di input e il risultato di output array_map_assocper consentire ai tasti precedenti di vincere. (Le chiavi restituite nel mio esempio non possono scontrarsi perché incorporano la chiave dell'array di origine, che a sua volta deve essere unica.)


Alternativa

Di seguito è una variante di quanto sopra, che potrebbe rivelarsi più logico per alcuni, ma richiede PHP 5.6:

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

In questa variante, la funzione fornita (su cui è mappata la matrice di dati) dovrebbe invece restituire una matrice associativa con una riga, ad es return [key => value]. Il risultato della mappatura del callable viene quindi semplicemente decompresso e passato a array_merge. Come in precedenza, la restituzione di una chiave duplicata comporterà la vincita dei valori successivi.

nb Alex83690 ha notato in un commento che l'utilizzo array_replacequi invece di array_mergepreservare le chiavi intere. array_replacenon modifica l'array di input, quindi è sicuro per il codice funzionale.

Se usi PHP da 5.3 a 5.5, il seguente è equivalente. Utilizza array_reducee l' +operatore di array binario per convertire l'array bidimensionale risultante in un array unidimensionale preservando le chiavi:

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

Entrambe queste varianti verrebbero utilizzate così:

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

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

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

Nota =>invece di ,in $func.

L'output è lo stesso di prima e ognuno può essere parzialmente applicato come prima.


 Sommario

L'obiettivo della domanda originale è rendere l'invocazione della chiamata il più semplice possibile, a scapito di avere una funzione più complicata che viene invocata; in particolare, avere la possibilità di passare l'array di dati come singolo argomento, senza suddividere le chiavi e i valori. Utilizzando la funzione fornita all'inizio di questa risposta:

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

Oppure, solo per questa domanda, possiamo semplificare la array_map_assoc()funzione che elimina le chiavi di output, poiché la domanda non le chiede:

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

Quindi la risposta è NO , non puoi evitare di chiamare array_keys, ma puoi astrarre il posto in cui array_keysviene chiamato in una funzione di ordine superiore, che potrebbe essere abbastanza buona.


7
Sembra che questo dovrebbe essere contrassegnato come la risposta corretta.
eddiewould

6
Grazie @eddiewould, ma sono in ritardo di circa 4 anni e mezzo :) Sono venuto qui alla ricerca di una soluzione, non ho trovato nessuno che mi piacesse, quindi ne ho inventato uno mio.
Nicholas Shanks,

1
Sarò quel ragazzo. PHP 5.3 non dovrebbe più essere un requisito per la data di questa risposta. A PARER MIO.
Erutan409,

1
La tua prima soluzione alternativa non è valida. è necessario sostituire array_mergeda array_replacepreservare le chiavi che sarebbero interi.
Alex83690,

1
@ Alex83690 Grazie! Anche se direi "invalido" è leggermente fuorviante - va bene se non si dispone di chiavi intere (come era vero nel mio caso).
Nicholas Shanks,

20

Con PHP5.3 o successivo:

$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
Penso che il requisito fosse "invece di chiamare array_keys e array_values, passando direttamente la variabile $ test_array", questo può essere usato senza array_keys?
eis,

4

È così che l'ho implementato nel mio progetto.

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

Molto pulito e non altera l'array originale!
Raffaele Candeliere,

4

Guarda qui! C'è una soluzione banale!

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

Come indicato nella domanda, array_map ha già esattamente la funzionalità richiesta . Le altre risposte qui sono estremamente complicate: array_walknon è funzionale.

uso

Esattamente come ti aspetteresti dal tuo esempio:

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

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

le altre risposte complicano le cose perché la domanda specificata qrrqy_keys()non dovrebbe essere usata per #reasons
Brad Kent

2

Con "ciclo manuale" intendevo scrivere una funzione personalizzata che utilizza foreach. Ciò restituisce un nuovo array come array_mapfa perché l'ambito della funzione fa sì $arrayche sia una copia, non un riferimento:

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

La tua tecnica usando array_mapwith array_keyssebbene in realtà sembra più semplice ed è più potente perché puoi usare nullcome callback per restituire le coppie chiave-valore:

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

array in loop con riferimento, può causare
eventi

Non è inquietante, significa solo che te ne sei dimenticato unset( $value )perché esiste ancora nell'ambito definito.
Aziz Punjani,

@azis, stava scherzando sulla spettralità, riferendosi all'articolo. Creerà effetti inaspettati se si dimentica di non essere impostato.
Janenz00,

1
Grazie per la risposta, ma ho pensato che fosse abbastanza chiaro che non volevo usare un loop tradizionale.
José Tomás Tocino,

@ janenz00 Vedi la risposta modificata per chiarimenti. Volevo dire loop in un ambito variabile pulito.
Ryanve,

1

Sulla base della risposta di eis , ecco cosa ho fatto alla fine per evitare di rovinare l'array originale:

$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
Perché è più semplice che passare i valori e le chiavi dell'array direttamente a array_map? È più lento e complicato, non vedo il vantaggio.
Ariel,

1
@Ariel puoi sostenere l'affermazione che sarebbe più lento, anche con numeri grandi? Deve iterare l'array solo una volta, quindi penso che dovrebbe essere molto più veloce in notazione O grande. Sono d'accordo sulla complessità però.
eis,

@eis È più lento perché sta creando l'array di risultati uno alla volta in PHP, invece che in massa in C. Tuttavia evita la chiamata array_keys (sebbene sia veloce poiché è in C). Benchmark it - vedi quale è più veloce, non sono davvero sicuro, ma di solito più code = codice più lento. Nella complessità, però, è decisamente peggio, ed è più importante della velocità per la maggior parte del tempo.
Ariel,

1
Non è necessario inviare il terzo argomento in array_walkquanto non lo si fa riferimento nella chiusura.
Steven Lu,

1

Ho realizzato questa funzione, basandomi sulla risposta di 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;
}

Esempio:

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

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

Produzione:

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

Ovviamente, puoi usare array_values per restituire esattamente ciò che OP desidera.

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

@KevinBeal Uso questa funzione molto nel mio lavoro. Potresti indicare dove sono gli errori?
Julio Vedovatto,

2
In primo luogo, al codice così com'è manca un segno di spunta $arrdi tipo array, tuttavia se si digitano gli argomenti come callablee arraysi può invece rilasciare il segno di spunta su is_callable. Successivamente, si assegna un valore a $ valore che non viene utilizzato. Dovresti semplicemente ignorare il valore restituito. In terzo luogo, sarebbe meglio generare un'eccezione nel callback piuttosto che restituire false. Quindi restituiresti sempre un valore valido o lanci sempre.
Nicholas Shanks,

1

La libreria YaLinqo * è adatta per questo tipo di attività. È una porta di LINQ da .NET che supporta pienamente valori e chiavi in ​​tutti i callback e assomiglia a SQL. Per esempio:

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

o semplicemente:

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

Ecco '"$k loves $v"'una scorciatoia per la sintassi di chiusura completa supportata da questa libreria. toArray()alla fine è facoltativo. La catena del metodo restituisce un iteratore, quindi se il risultato deve essere iterato solo utilizzando foreach,toArray chiamata può essere rimossa.

* sviluppato da me


1

Farei qualcosa del genere:

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

?>

risultati:

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

1

Aggiungerò ancora un'altra soluzione al problema utilizzando la versione 5.6 o successive. Non so se sia più efficiente delle già ottime soluzioni (probabilmente no), ma per me è solo più semplice da leggere:

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

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

Usando strval()come funzione di esempio in array_map, questo genererà:

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

Spero di non essere il solo a trovarlo abbastanza semplice da capire. array_combinecrea una key => valuematrice da una matrice di chiavi e una matrice di valori, il resto è piuttosto autoesplicativo.


1

È possibile utilizzare il metodo map da questa libreria di array per ottenere esattamente ciò che si desidera con la stessa facilità di:

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

inoltre conserva le chiavi e restituisce un nuovo array, per non parlare delle diverse modalità per soddisfare le tue esigenze.


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

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

fonte


0

Mi piace sempre la variante javascript di array map. La versione più semplice sarebbe:

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

Quindi ora puoi semplicemente passare una funzione di callback su come costruire i valori.

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

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

È più utile avere i dati come ultimo argomento per qualsiasi funzione che scrivi, poiché puoi quindi creare una nuova funzione che inserisce un callback (comportamento) specifico - cioè ottieni la composizione della funzione: si h(g(f($data)))applica f, quindi g, quindi hal tuo dati. È generalmente considerato più versatile nella programmazione funzionale avere una funzione che esegue la stessa operazione su dati subacquei, piuttosto che avere una funzione che applica funzioni divers a un set di dati fisso.
Nicholas Shanks,

Nel tuo esempio hai solo 1 argomento per la funzione. Trovo più facile inserire i dati come primo argomento, come array_filter, array_reduce e le funzioni array in javascript.
Blablabla,

Questo è il mio punto! Passando gli ultimi dati, permette di curry la funzione (crea una nuova funzione che unisce il loop con l'operazione specifica) e applicare che ai dati chiamando la nuova funzione con un solo parametro. Questo principio è spiegato meglio di me qui, in questa risposta: stackoverflow.com/a/5863222
Nicholas Shanks,

L'uso di una funzione di composizione in un linguaggio come PHP non è una soluzione migliore a questo problema?
Blablabla,

1
È un'alternativa ma richiede sostanzialmente maggiori investimenti in FP, ad esempio questo: github.com/nickshanks/fp-php-talk/blob/master/lib.php#L24 o questo: github.com/nickshanks/php-fp/blob /master/src/fp.php#L62
Nicholas Shanks,

0

Un altro modo per farlo con (fuori) la conservazione delle chiavi:

$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

Vedo che manca la risposta ovvia:

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

Funziona esattamente come array_map. Quasi.

In realtà, non è puro mapcome lo conosci da altre lingue. Php è molto strano, quindi richiede alcune funzioni utente molto strane, perché non vogliamo spezzare il nostro worse is betterapproccio rotto con precisione .

Davvero, non lo è mapaffatto. Tuttavia, è ancora molto utile.

  • La prima evidente differenza rispetto a array_map è che il callback prende output each()da ogni array di input invece che dal solo valore. È ancora possibile scorrere più array contemporaneamente.

  • La seconda differenza è il modo in cui viene gestita la chiave dopo la restituzione dalla richiamata; il valore restituito dalla funzione di richiamata dovrebbe essere array('new_key', 'new_value'). Le chiavi possono e verranno modificate, le stesse chiavi possono anche causare la sovrascrittura del valore precedente, se viene restituita la stessa chiave. Questo non è un mapcomportamento comune , tuttavia ti consente di riscrivere le chiavi.

  • La terza cosa strana è che, se si omette il keyvalore restituito (da array(1 => 'value')o array(null, 'value')), verrà assegnata una nuova chiave, come se $array[] = $valuefosse usata. Neanche questo è mapun comportamento comune, ma a volte è utile, credo.

  • La quarta cosa strana è che, se la funzione di callback non restituisce un valore o ritorna null, l'intera serie di chiavi e valori correnti viene omessa dall'output, viene semplicemente ignorata. Questa funzione è totalmente mapspiacevole, ma renderebbe questa funzione un'acrobazia eccellente anche array_filter_assocse esistesse tale funzione.

  • Se si omette il secondo elemento ( 1 => ...) (la parte del valore ) nel ritorno del callback, nullviene utilizzato al posto del valore reale.

  • Tutti gli altri elementi tranne quelli con chiavi 0e 1nel ritorno di callback vengono ignorati.

  • E infine, se lambda restituisce un valore diverso da nullo array, viene trattato come se sia la chiave che il valore fossero omessi, quindi:

    1. viene assegnata una nuova chiave per l'elemento
    2. null è usato come valore
ATTENZIONE:
tenere presente che quest'ultima funzionalità è solo un residuo delle funzionalità precedenti ed è probabilmente completamente inutile. Affidarsi a questa funzione è altamente scoraggiato, poiché questa funzione sarà deprecata in modo casuale e modificata in modo imprevisto nelle versioni future.

NOTA:
A differenza di in array_map, tutti i parametri non array passati a array_map_assoc, ad eccezione del primo parametro callback, vengono silenziosamente trasmessi agli array.

ESEMPI:
// TODO: examples, anyone?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.