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

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