Есть ли способ сделать что-то вроде этого:
$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
переменную $test_array
?
Желаемый результат:
array(2) { [0]=> string(27) "first_key loves first_value" [1]=> string(29) "second_key loves second_value" }
Не с 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);
Однако он изменяет массив, заданный как параметр, поэтому это не совсем функциональное программирование (так как у вас есть вопрос, помеченный таким образом).
Вы можете написать такую функцию, если хотите.
Вероятно, это самый короткий и простой способ:
$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') )
С 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) ) );
Вот мое очень простое, совместимое с 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_curried(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_curried($func); var_dump($my_mapping($ordinals));
Что дает тот же результат, что и $func
и $ordinals
, как раньше.
ПРИМЕЧАНИЕ. Если ваша отображаемая функция возвращает один и тот же ключ для двух разных входов, значение, связанное с более поздним ключом, будет выиграно. array_map_assoc
входной массив и выведите результат 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
. Как и раньше, возврат дублирующего ключа приведет к получению более поздних значений.
Если вы используете PHP с 5.3 по 5.5, то это эквивалентно. Он использует array_reduce
и оператор binary +
array для преобразования полученного двумерного массива в одномерный массив при сохранении ключей:
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));
Обратите внимание на =>
вместо ,
в $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
в функцию более высокого порядка, что может быть достаточно хорошим.
Основываясь на ответе 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);
Под «ручным циклом» я имел в виду написать пользовательскую функцию, которая использует 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); }
Библиотека 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
можно удалить.
* разработан мной
Я сделал эту функцию, исходя из ответа 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
для возврата именно того, что хочет OP.
array_values(array_map_(function($key, $value){ return $key . " loves " . $value; }, $test_array))
Я вижу, что отсутствует очевидный ответ:
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; }
с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
, весь набор текущих ключей и значений опускается на выходе, он просто пропускается. Эта функция полностью un map
py, но это сделало бы эту функцию отличным stunt double для array_filter_assoc
, если бы была такая функция.
Если вы опускаете второй элемент ( 1 => ...
) (часть значения ) в обратном null
вместо реального значения используется значение null
.
Любые другие элементы, кроме клавиш с 0
и 1
в обратном вызове, игнорируются.
И, наконец, если лямбда возвращает любое значение, кроме null
или массива, оно обрабатывается так, как если бы оба ключа и значение были опущены, так что:
null
используется как значение ПРЕДУПРЕЖДЕНИЕ:
Имейте в виду, что эта последняя особенность – всего лишь остаток предыдущих функций, и это, вероятно, совершенно бесполезно. Опираясь на эту особенность, она очень обескуражена, так как эта функция будет непредсказуемо изменена и неожиданно изменена в будущих выпусках.
ЗАМЕТКА:
В отличие от array_map
, все параметры без массива, переданные в array_map_assoc
, за исключением первого параметра обратного вызова, незаметно передаются в массивы.
ПРИМЕРЫ:
// TODO: examples, anyone?