PHP array_map, включая ключи
Есть ли способ сделать что-то вроде этого:
$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"
}
Ответы
Ответ 1
Не с 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"
// }
Ответ 2
Это, вероятно, самый короткий и простой способ:
$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')
)
Ответ 3
Вот мое очень простое, совместимое с 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"
}
Частичное выражение
Если вам нужно многократно использовать функцию с разными массивами, но с одной и той же функцией отображения, вы можете сделать что-то под названием приложение с частичной функцией (связанное сcurry), что позволяет вам только передать массив данных при вызове:
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
. Как и ранее, возврат дублирующего ключа приведет к выигрышу более поздних значений.
нотабене 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;
}, []);
}
Usage
UsageОба эти варианта будут использоваться следующим образом:
$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
вызывается в функцию более высокого порядка, что может быть достаточно хорошим ,
Ответ 4
С 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)
)
);
Ответ 5
Так я реализовал это в своем проекте.
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));
}
Ответ 6
В "ручном цикле" я имел в виду написать пользовательскую функцию, которая использует 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);
}
Ответ 7
Основываясь на eis answer, вот что я в итоге сделал, чтобы избежать беспорядка исходного массива:
$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);
Ответ 8
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
можно удалить.
*, разработанный мной
Ответ 9
Я сделал эту функцию на основе eis answer:
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))
Ответ 10
Я бы сделал что-то вроде этого:
<?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"
}
Ответ 11
Смотри сюда! Существует тривиальное решение!
function array_map2(callable $f, array $a)
{
return array_map($f, array_keys($a), $a);
}
Как указано в вопросе, array_map
уже имеет требуемую функциональность. Другие ответы здесь серьезно array_walk
ситуацию: 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));
Ответ 12
Я добавлю еще одно решение проблемы с использованием версии 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
из массива ключей и массива значений, остальное довольно понятно.
Ответ 13
Вы можете использовать метод map из этой библиотеки массивов, чтобы достичь именно того, что вы хотите, так же легко, как:
Arr::map($test_array, function($a, $b) { return "$a loves $b"; });
также он сохраняет ключи и возвращает новый массив, не говоря уже о нескольких различных режимах, соответствующих вашим потребностям.
Ответ 14
Я вижу, что отсутствует очевидный ответ:
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
.
-
Любые другие элементы, кроме клавиш с 0
и 1
в обратном вызове, игнорируются.
-
И, наконец, если lambda возвращает любое значение, кроме null
или массива, оно обрабатывается так, как если бы оба ключа и значение были опущены, так:
- назначен новый ключ для элемента
-
null
используется как значение
ПРЕДУПРЕЖДЕНИЕ:
Имейте в виду, что эта последняя особенность - всего лишь остаток предыдущих функций, и это, вероятно, совершенно бесполезно. Опираясь на эту функцию крайне не рекомендуется, так как эта функция будет случайным образом устарела и неожиданно изменена в будущих выпусках.
Примечание:
В отличие от array_map
, все параметры, отличные от массива, переданные в array_map_assoc
, за исключением первого параметра обратного вызова, незаметно передаются в массивы.
Примеры:
// TODO: examples, anyone?
Ответ 15
Мне всегда нравится 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;
});
);
Ответ 16
Другой способ сделать это с (у) сохранением ключей:
$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)
);
Ответ 17
Я нашел эту статью, и она сделала трюк для меня, как с array_map em array_walk, вы не можете этого сделать.
$variables = array_reduce($invoice['variables'], function ($result, $item) {
$result[$item['variable']] = $item['value'];
return $result;
}, array());
Конечно, есть некоторые уловы, такие как дубликаты ключей, но если у вас нет этой проблемы, это сделает трюк.