Карта массива PHP, включая ключи

есть ли способ сделать что-то вроде этого:

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

15 ответов


Не с 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')
)

вот мое очень простое, 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"
}

частичное применение

в случае, если вам нужно использовать функцию много раз с различные массивы, но та же функция отображения, вы можете сделать что-то под названием частичное применение функции (связанные с ‘карринг’), который позволяет передавать только массив данных при вызове:

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. Как и ранее, возврат дубликата ключа приведет к выигрышу более поздних значений.

n.b. Alex83690 отметил в комментарии, что с помощью array_replace здесь вместо array_merge сохранит целочисленные ключи. array_replace не изменяет входной массив, поэтому безопасен для функционального кода.

если вы находитесь на PHP 5.3 до 5.5, следующее эквивалентно. Он использует


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

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, вот что я в конечном итоге сделал, чтобы не испортить исходный массив:

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

вот как я реализовал это в своем проекте.

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

Я сделал эту функцию, на основе ответ 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;
}

работает точно так же, как array_map. Почти.

на самом деле, это не чисто map Как вы знаете это из других языков. Php очень странный, поэтому он требует некоторых очень странных пользовательских функций, потому что мы не хотим разбивать наш точно сломанный worse is better подход.

на самом деле, это не на самом деле map на всех. Тем не менее, это все еще очень полезно.

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

  • вторая разница-это способ обработки ключа после его возврата из обратного вызова; возвращаемое значение из функции обратного вызова должно быть array('new_key', 'new_value'). Ключи могут и будут изменены, те же ключи могут даже привести к перезаписи предыдущего значения, если тот же ключ был возвращен. Это не распространено map поведение, но это позволяет переписывать ключи.

  • третья странная вещь, если вы опускаете key в возвращаемом значении (либо array(1 => 'value') или array(null, 'value')), новый ключ будет назначен, как будто . Это не map ' ы обычное поведение либо, но это иногда пригодится, я думаю.

  • четвертая странная вещь, если функция обратного вызова не возвращает значение или возвращает null, весь набор текущих ключей и значений опущен из выходных данных, его просто пропустили. Эта функция полностью неmappy, но это сделало бы эту функцию отличной двойной трюк для array_filter_assoc, если бы была такая функция.

  • если вы опустите второй элемент (1 => ...) (the стоимостью часть) в обратном вызове,null используется вместо реального значения.

  • любые другие элементы, кроме тех, кто с ключами 0 и 1 в возвращении обратного вызова игнорируемый.

  • и, наконец, если лямбда возвращает любое значение, кроме null или массив, он рассматривается как если бы и ключ и значение были опущены, так что:

    1. назначен новый ключ для элемента
    2. null используется как значение
Внимание:
Имейте в виду, что эта последняя функция-всего лишь остаток предыдущих функций, и она, вероятно, совершенно бесполезна. Опираясь на эту функцию очень обескуражен, так как эта функция будет случайным образом устаревший и неожиданно изменился в будущих выпусках.

Примечание:
В отличие от in array_map, все параметры без массива передаются в array_map_assoc, за исключением первого параметра обратного вызова, молча преобразован в массивы.

примеры:
// TODO: examples, anyone?


мне всегда нравится 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;
    });
);

Я бы сделал что-то вроде этого:

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

Я добавлю еще одно решение проблемы, используя 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 массив из массива ключей и массива значений, остальное довольно понятно.


посмотреть здесь! Существует тривиальное решение!

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

Я нашел это статьи и это сделало трюк для меня, как с array_map em array_walk вы не можете этого сделать.

$variables = array_reduce($invoice['variables'], function ($result, $item) {
                $result[$item['variable']] = $item['value'];
                return $result;
            }, array());

конечно, есть некоторые уловы, такие как дубликаты ключей, но если у вас нет этой проблемы, это сделает трюк.