Массив массивов 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"
}

20 ответов

Решение

Не с 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"
}

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

Если вам нужно многократно использовать эту функцию с разными массивами, но с одной и той же функцией отображения, вы можете сделать то, что называется частичным применением функции (связано с " 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, Как и ранее, возврат дублирующего ключа приведет к выигрышу более поздних значений.

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

Обратите внимание => вместо , в $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 вызывается в функцию более высокого порядка, которая может быть достаточно хорошей.

$array = [
  'category1' => 'first category',
  'category2' => 'second category',
];
 
$new = array_map(function($key, $value) {
  return "{$key} => {$value}";
}, array_keys($array), $array);

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

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

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

Библиотека 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 звонок может быть удален.

* разработано мной

Закрытия будет работать, если вам это нужно только один раз. Я бы использовал генератор.

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

$x_result = (function(array $arr) {
    foreach ($arr as $key => $value) {
        yield "$key loves $value";
    }
})($test_array);

var_dump(iterator_to_array($x_result));

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

Для чего-то многоразового:

function xmap(callable $cb, array $arr)
{
    foreach ($arr as $key => $value) {
        yield $cb($key, $value);
    }
}

var_dump(iterator_to_array(
    xmap(function($a, $b) { return "$a loves $b"; }, $test_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);
}

Вы можете использовать метод map из этой библиотеки массивов, чтобы добиться именно того, что вы хотите, так же легко, как:

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

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

Вы можете передать 2 аргумента функции, чтобы получить ключ и значение для вашего массива. как:

      array_map(function($key, $value) {
    return [$key => $value];
}, array_keys($my_array), $my_array);

Основываясь на ответе 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);

Мне всегда нравится javascript-вариант массива map. Самый простой вариант этого будет:

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

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

Другой способ сделать это с (без) сохранением ключей:

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

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

$variables = array_reduce($invoice['variables'], function ($result, $item) {
                $result[$item['variable']] = $item['value'];
                return $result;
            }, 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')), новый ключ будет назначен, как если бы $array[] = $value использовался. Это не mapЭто обычное поведение, но иногда оно пригодится, наверное.

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

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

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

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

    1. назначен новый ключ для элемента
    2. null используется как значение
ПРЕДУПРЕЖДЕНИЕ:
Помните, что эта последняя функция является лишь остатком предыдущих функций и, вероятно, совершенно бесполезна. Полагаться на эту функцию крайне не рекомендуется, поскольку эта функция будет случайно исключена и неожиданно изменена в будущих выпусках.

НОТА:
В отличие от в array_mapвсе параметры, не являющиеся массивами, переданы array_map_assoc, за исключением первого параметра обратного вызова, молча приводятся к массивам.

ПРИМЕРЫ:
// TODO: examples, anyone?

Другие вопросы по тегам