PHP's array_map, включаючи клавіші


208

Чи є спосіб зробити щось подібне:

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

Але замість того, щоб дзвонити array_keysі array_valuesбезпосередньо передавати $test_arrayзмінну?

Бажаний вихід:

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

Дивіться також: stackoverflow.com/search?q=each_with_index про контрастний підхід до цієї загальної проблеми
dreftymac

Відповіді:


206

Не з array_map, оскільки він не обробляє ключі.

array_walk робить:

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

Але він змінює масив, заданий як параметр, тому це не зовсім функціональне програмування (як у вас питання позначене таким чином). Крім того, як зазначено в коментарі, це змінить лише значення масиву, тому ключі не будуть такими, які ви вказали у питанні.

Ви можете написати функцію, яка фіксує точки над собою, якби ви цього хотіли:

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

За винятком того, що в цьому випадку ви хочете $a = "$b loves $a", щоб відповідати бажаному результату OP.
cmbuckley

1
правильно, змінено :) приємно, наскільки різні вони зробили array_map з array_walk.
eis

Приємно, дякую. Щоб уникнути зіпсування оригінального масиву, ось що я врешті-решт зробив (дивіться мою відповідь нижче)
Жозе Томаш Точіно

3
Це не "функціональне програмування", хоча array_walk()не повертає отриманий масив, а натомість bool.
май

@mae так, як я написав у відповідь , а також - замість повернення значення , яке вона змінює параметр
EIS

145

Це, мабуть, найкоротший і найпростіший міркування про:

$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
Я щойно зрозумів, що питання спеціально сказав не використовувати array_keys(). Але це здається дурною вимогою.
Кевін Біл

3
Питання дало рішення за допомогою array_keys (), було б нерозумно надати відповідь, яка не має переваги (наприклад, виклик менше функцій) перед поточним рішенням.
Chinoto Vokro

Відповідь на початкове запитання - НІ, і це найбільш підходяще рішення.
usoban

65

Ось моє дуже просте, сумісне з PHP 5.5 рішення:

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

Викликаний, який ви надаєте, повинен сам повертати масив з двома значеннями, тобто return [key, value]. Отже, внутрішній виклик array_mapвиробляє масив масивів. Потім це конвертується назад в одновимірний масив array_column.

Використання

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

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

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

Вихідні дані

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

Часткове застосування

Якщо вам потрібно багато разів використовувати функцію з різними масивами, але однакову функцію відображення, ви можете зробити щось, що називається додатком часткової функції (пов’язаним з ' currying '), що дозволяє передавати масив даних лише після виклику:

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

Який виробляє такий же результат, враховуючи $funcі $ordinalsє такими , як раніше.

ПРИМІТКА: якщо ваша картографічна функція повертає одну і ту ж клавішу для двох різних входів, значення, пов'язане з пізнішим ключем, виграє. Зворотний вхідний масив і результат виводу, array_map_assocщоб дозволити виграти більш ранні клавіші. (Повернені ключі в моєму прикладі не можуть зіткнутися, оскільки вони містять ключ вихідного масиву, який, в свою чергу, повинен бути унікальним.)


Альтернатива

Далі йде варіант вищезазначеного, який може виявитися більш логічним для деяких, але вимагає PHP 5.6:

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

У цьому варіанті надана функція (над якою відображається масив даних) повинна замість цього повертати асоціативний масив з одним рядком, тобто return [key => value]. Результат відображення відображуваного дзвінка просто розпаковується та передається array_merge. Як і раніше, повернення дубліката ключа призведе до виграшу пізніших значень.

nb Alex83690 зазначив у коментарі, що використання array_replaceтут замість array_mergeзбереже цілі клавіші. array_replaceне змінює вхідний масив, тому безпечний для функціонального коду.

Якщо ви перебуваєте на PHP 5.3 до 5.5, наступне еквівалентно. Він використовує array_reduceі +оператор двійкового масиву для перетворення отриманого двовимірного масиву вниз до одновимірного масиву, зберігаючи ключі:

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

Використання

Обидва ці варіанти будуть використані таким чином:

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

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

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

Зауважте =>замість ,in $func.

Вихід такий же, як і раніше, і кожен може бути частково застосований так само, як і раніше.


 Підсумок

Мета оригінального запитання - зробити виклик якомога простішим за рахунок наявності більш складної функції, яка викликається; особливо мати можливість передавати масив даних як єдиний аргумент, не розбиваючи ключі та значення. Використовуючи функцію, надану на початку цієї відповіді:

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

Або, лише для цього питання, ми можемо зробити спрощення array_map_assoc()функціонування, яке скидає вихідні клавіші, оскільки питання не запитує їх:

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

Отже, відповідь " НІ" , ви не можете уникнути дзвінків array_keys, але ви можете абстрагувати місце, куди array_keysвикликується, функцію вищого порядку, яка може бути досить хорошою.


7
Схоже, це має бути позначено як правильну відповідь.
eddiewould

6
Дякую @eddiewould, але мені вже близько півтора років :) Я прийшов сюди шукати рішення, не знайшов жодного, який мені сподобався, тому придумав своє.
Ніколас Шенкс

1
Я буду ТАКИЙ хлопець. PHP 5.3 більше не має бути вимогою до дати цієї відповіді. ІМХО.
Ерутан409

1
Ваше перше альтернативне рішення недійсне. ви повинні замінити array_mergeна array_replaceзбереження ключі , які були б цілими числами.
Alex83690

1
@ Alex83690 Дякую! Хоча я б сказав , «недійсний» трохи вводить в оману - це добре , якщо у вас немає яких - або цілочисельних ключів (як це було вірно в моєму випадку).
Микола Хвостовики

20

З PHP5.3 або новішою версією:

$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
Я думаю, що вимога полягала в тому, що "замість виклику array_keys та array_values, безпосередньо передаючи змінну $ test_array", чи можна це використовувати без array_keys?
eis

4

Ось як я це реалізував у своєму проекті.

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

Дуже чистий і не змінює початковий масив!
Raffaele Candeliere

4

Послухайте! Є банальне рішення!

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

Як зазначено в питанні, array_map вже є саме необхідний функціонал . Інші відповіді тут серйозно надскладні: array_walkне є функціональними.

Використання

Саме так, як ви очікували від свого прикладу:

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

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

в інших відповідях надскладні речі, оскільки вказане питання qrrqy_keys()не слід використовувати для #reasons
Бред Кент

2

Під "ручним циклом" я мав на увазі написати спеціальну функцію, яка використовує foreach. Це повертає новий масив, як array_mapце робиться, оскільки область дії функції стає $arrayкопією, а не посиланням:

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

Ваша техніка за array_mapдопомогою, array_keysхоча насправді здається простішою та потужнішою, оскільки ви можете використовувати nullяк зворотний виклик для повернення пар ключ-значення:

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

циклічний масив із посиланням може спричинити моторошні речі
janenz00

Це не моторошно, це просто означає, що ви його забули, unset( $value )оскільки воно все ще існує у визначеному обсязі.
aziz punjani

@azis, жартував про моторошність, посилаючись на статтю. Це створить несподівані ефекти, якщо ви забудете зняти налаштування.
janenz00

1
Дякую за відповідь, але я подумав, що цілком зрозуміло, що я не хочу використовувати традиційний цикл.
Хосе Томаш Точіно

@ janenz00 Див. відредаговану відповідь для уточнення. Я мав на увазі циклічний цикл в чистому змінній області.
ryanve

1

Виходячи з відповіді eis , ось що я в кінцевому підсумку зробив, щоб уникнути псування оригінального масиву:

$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
Чому це простіше, ніж просто передати значення масиву та ключі безпосередньо до array_map? Це повільніше і складніше, я не бачу переваги.
Аріель

1
@Ariel чи можете ви створити резервну копію твердження, що це буде повільніше, навіть якщо це велика кількість? Їй потрібно повторити масив лише один раз, тому я думаю, що він повинен бути набирати величини швидше у великих O-позначеннях. Я згоден щодо складності.
eis

@eis Це повільніше, оскільки він створює масив результатів по одному в PHP, а не масово в C. Це дозволяє уникати виклику array_keys (хоча це швидко, оскільки він знаходиться в C). Визначте це - подивіться, що швидше, я не дуже впевнений, але зазвичай більше коду = повільніший код. У складності це, безумовно, гірше, і це більш важливо, ніж швидкість більшості часу.
Аріель

1
Вам не потрібно надсилати третій аргумент, array_walkоскільки ви не посилаєтесь на нього під час закриття.
Стівен Лу

1

Я зробив цю функцію, грунтуючись на відповіді 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;
}

Приклад:

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

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

Вихід:

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

Звичайно, ви можете використовувати, array_valuesщоб повернути саме те, що хоче ОП.

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

@KevinBeal Я багато використовую цю функцію у своїй роботі. Не могли б ви вказати, де помилки?
Хуліо Ведовато

2
По-перше, у наявному коді відсутній чек, що $arrмає масив типів, однак якщо ви вводите натяки на свої аргументи як callableі arrayзамість цього ви можете скинути чек на is_callable. Далі ви присвоюєте значення $, яке потім не використовується. Вам слід просто проігнорувати повернене значення. По-третє, було б краще кинути виняток у зворотному дзвінку, ніж повертати помилкове. Тоді ви або завжди повертаєте дійсне значення, або завжди кидаєте.
Ніколас Шенкс

1

Для подібних завдань добре підходить бібліотека YaLinqo *. Це порт LINQ від .NET, який повністю підтримує значення та ключі у всіх зворотних викликах та нагадує SQL. Наприклад:

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

або просто:

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

Ось '"$k loves $v"'ярлик для синтаксису повного закриття, який підтримує ця бібліотека. toArray()врешті-решт необов’язково. Ланцюжок методів повертає ітератор, тож якщо результат просто потрібно переглядати за допомогою foreach, toArrayвиклик може бути видалений.

* розроблений мною


1

Я б зробив щось подібне:

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

?>

Результати:

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

1

Я додам ще одне рішення проблеми за допомогою версії 5.6 або пізнішої. Не знаю, чи це більш ефективно, ніж уже чудові рішення (напевно, ні), але мені просто читати:

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

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

Використовуючи strval()в якості прикладу функцію в array_map, це створить:

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

Сподіваюся, я не єдиний, хто вважає це досить простим для розуміння. array_combineстворює key => valueмасив з масиву ключів та масиву значень, решта досить зрозуміло.


1

Ви можете використовувати метод карти з цієї бібліотеки масиву, щоб досягти саме того, що ви хочете так просто, як:

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

також він зберігає ключі та повертає новий масив, не кажучи вже про кілька різних режимах, щоб відповідати вашим потребам.


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

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

Джерело


0

Мені завжди подобається javascript варіант масиву. Найпростішою його версією буде:

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

Отже, тепер ви можете просто передати функцію зворотного виклику, як будувати значення.

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

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

Корисніше мати дані в якості останнього аргументу для будь-якої функції, яку ви пишете, оскільки ви зможете створити нову функцію, яка виконує певний зворотний виклик (поведінку) - тобто ви отримуєте склад функції: h(g(f($data)))застосовується f, потім g, потім hдо вашої дані. Зазвичай у функціональному програмуванні вважається більш універсальним функція, яка виконує ту саму операцію над дайверськими даними, ніж функція, яка застосовує функції дайверів до фіксованого набору даних.
Ніколас Шенкс

У вашому прикладі у вас є лише 1 аргумент функції. Мені легше ставити дані в якості першого аргументу, як array_filter, array_reduce та функції масиву в javascript.
блаблабла

Це моя суть! Передаючи дані останнього, він дозволяє Каррі функцію (створити нову функцію , яка поєднує в собі нескінченний цикл з роботою конкретного) і застосувати , що до даних, викликаючи нову функцію з одним параметром. Ця голова пояснюється краще, ніж я можу, у цій відповіді: stackoverflow.com/a/5863222
Ніколас Шенкс

Чи використання функції компонування на мові, як PHP, не є кращим рішенням цієї проблеми?
блаблабла

1
Це альтернатива, але вимагає значно більших інвестицій у FP, наприклад, це: github.com/nickshanks/fp-php-talk/blob/master/lib.php#L24 або це: github.com/nickshanks/php-fp/blob /master/src/fp.php#L62
Ніколас Шенкс

0

Ще один спосіб зробити це за допомогою (із) збереження ключів:

$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

Я бачу, що пропускає очевидну відповідь:

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

Працює точно як array_map. Майже.

Насправді це не чисто, mapяк ви це знаєте з інших мов. Php дуже дивний, тому він вимагає деяких дуже дивних функцій користувача, тому що ми не хочемо порушувати наш точно порушений worse is betterпідхід.

Дійсно, це насправді mapзовсім не так. Але все-таки це дуже корисно.

  • Перша очевидна відмінність від array_map полягає в тому, що зворотний виклик приймає виходи each()з кожного вхідного масиву замість значення лише. Ви все одно можете повторити більше масивів одночасно.

  • Друга відмінність - це спосіб обробки ключа після його повернення з зворотного дзвінка; значення повернення з функції зворотного виклику повинно бути array('new_key', 'new_value'). Клавіші можуть і можуть бути змінені, ті ж ключі можуть навіть призвести до перезапису попереднього значення, якщо повернути той самий ключ. Це не звичайна mapповедінка, але вона дозволяє переписати ключі.

  • Третя дивна річ - якщо ви опустите keyзначення повернення (або за array(1 => 'value')або array(null, 'value')), новий ключ буде призначений, як ніби він $array[] = $valueбув використаний. Це теж не mapзвичайна поведінка, але, мабуть, це корисно іноді.

  • Четверта дивна річ - якщо функція зворотного виклику не повертає значення, або повертається null, весь набір поточних клавіш і значень пропускається з виводу, він просто пропускається. Ця функція є абсолютно нею map, але це зробило б цю функцію відмінним трюком подвійним array_filter_assoc, якби була така функція.

  • Якщо ви повернете другий елемент ( 1 => ...) ( частина значення ) у зворотному звороті, nullвикористовується замість реального значення.

  • Будь-які інші елементи, за винятком клавіш 0та 1повернення дзвінків, ігноруються.

  • І нарешті, якщо лямбда повертає будь-яке значення, крім nullабо масиву, це трактується так, як якщо б і ключ, і значення були опущені, так:

    1. призначається новий ключ для елемента
    2. null використовується як цінність
ПОПЕРЕДЖЕННЯ.
Майте на увазі, що ця остання особливість є лише залишком попередніх функцій і, ймовірно, абсолютно марна. Покладатися на цю функцію дуже не рекомендується, оскільки ця функція буде випадково застарілою в наступних випусках і несподівано змінена.

ПРИМІТКА.
На відміну від array_map, всі параметри без масиву, передані array_map_assoc, за винятком першого параметра зворотного виклику, мовчки передаються в масиви.

ПРИКЛАДИ:
// TODO: examples, anyone?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.