Есть ли в PHP функция, которая возвращает массив, состоящий из значения ключа из массива ассоциативных массивов?

Я уверен, что этот вопрос задавался раньше, мои извинения за то, что я не нашел его первым.

Исходный массив:

[0] => Array
    (
        [categoryId] => 1
        [eventId] => 2
        [eventName] => 3
        [vendorName] => 4
    )

[1] => Array
    (
        [categoryId] => 5
        [eventId] => 6
        [eventName] => 7
        [vendorName] => 8
    )

[2] => Array
    (
        [categoryId] => 9
        [eventId] => 10
        [eventName] => 11
        [vendorName] => 12
    )

Я надеялся на результат из: print_r(get_values_from_a_key_in_arrays('categoryId', $array));

[0] => 1
[1] => 5
[2] => 9

Я просто ищу что-то более чистое, чем писать свою собственную функцию на основе foreach. Если foreach - это ответ, у меня это уже есть.

Изменить: я не хочу использовать жестко закодированный ключ, я просто показывал пример вызова решения. Спасибо! ^_^

Быстрое решение для PHP 5.3:

private function pluck($key, $data) {
    return array_reduce($data, function($result, $array) use($key) {
        isset($array[$key]) && $result[] = $array[$key];
        return $result;
    }, array());
}

person Caleb Gray    schedule 18.05.2012    source источник
comment
Это известно как plucking и не является функцией PHP по умолчанию. Хотя вы можете легко написать его сами.   -  person Joost    schedule 19.05.2012
comment
Очень круто. Я делал это раньше, но я никогда не знал, что техническое имя для этого было plucking.   -  person Moses    schedule 19.05.2012
comment
Это хорошо, но array_reduce() — НЕПРАВИЛЬНАЯ функция. Он предназначен для уменьшения массива до одного значения. Это верно и для других языков. Вы должны переписать с помощью array_map() или array_walk() ... что также упростит код.   -  person doublejosh    schedule 25.06.2016
comment
@doublejosh Я не согласен. array_reduce здесь все в порядке. Он сводит массив массивов к массиву значений.   -  person Michael    schedule 28.07.2016
comment
Внутренняя операция действительно является сокращением, я, должно быть, посмотрел на внешнюю операцию, когда жаловался на ее использование.   -  person doublejosh    schedule 28.07.2016


Ответы (8)


Итак, самое интересное в высшем порядке функции коллекции/итератора, такие как выщипывать, фильтровать, каждый, map, и друзья в том, что их можно смешивать и сопоставлять, чтобы составить более сложный набор операций.

Большинство языков предоставляют эти типы функций (ищите такие пакеты, как collection, iterator или enumeration/enumerable)... некоторые предоставляют больше функций, чем другие, и вы обычно увидите, что функции названы по-разному в разных языках (например, collect == map, уменьшить == свернуть). Если функция не существует в вашем языке, вы можете создать ее из существующих.

Что касается вашего тестового случая... мы можем использовать array_reduce для реализации щипка. Первая опубликованная мной версия опиралась на array_map; однако я согласен с @salathe, что array_reduce более лаконичен для этой задачи; array_map — хороший вариант, но в конечном итоге вам придется проделать больше работы. array_reduce поначалу может показаться немного странным, но если обратный вызов аккуратно организован, все в порядке.

Менее наивный pluck также проверит, может ли он "вызвать" (функцию/метод) повторяющееся значение. В наивной реализации ниже мы предполагаем, что структура представляет собой хэш (ассоциативный массив).

Это настроит данные тестового сценария (фикстуры):

<?php

$data[] = array('categoryId' => 1,    'eventId' => 2,  'eventName' => 3,  'vendorName' => 4);
$data[] = array('categoryId' => 5,    'eventId' => 6,  'eventName' => 7,  'vendorName' => 8);
$data[] = array('categoryId' => 9,    'eventId' => 10, 'eventName' => 11, 'vendorName' => 12);
$data[] = array(/* no categoryId */   'eventId' => 10, 'eventName' => 11, 'vendorName' => 12);
$data[] = array('categoryId' => false,'eventId' => 10, 'eventName' => 11, 'vendorName' => 12);
$data[] = array('categoryId' => 0.0,  'eventId' => 10, 'eventName' => 11, 'vendorName' => 12);

Выберите предпочтительную версию pluck

$preferredPluck = 'pluck_array_reduce'; // or pluck_array_map

"puck" для PHP 5.3+: array_reduce обеспечивает краткую реализацию, хотя и не такую ​​простую для понимания, как версия array_map:

function pluck_array_reduce($key, $data) {
  return array_reduce($data, function($result, $array) use($key){
    isset($array[$key]) &&
      $result[] = $array[$key];

    return $result;
  }, array());
}

"puck" для PHP 5.3+: array_map не идеален для этого, поэтому нам нужно провести дополнительную проверку (и она по-прежнему не учитывает многие возможные случаи):

function pluck_array_map($key, $data) {
  $map = array_map(function($array) use($key){
    return isset($array[$key]) ? $array[$key] : null;
  }, $data);

  // is_scalar isn't perfect; to make this right for you, you may have to adjust
  return array_filter($map, 'is_scalar');
}

"снять" для устаревшего PHP ‹5.3

Мы могли бы использовать устаревшую create_function; однако это дурной тон, нерекомендуемый, а также совсем не элегантный, поэтому я решил его не показывать.

function pluck_compat($key, $data) {
  $map = array();
  foreach ($data as $array) {
    if (array_key_exists($key, $array)) {
      $map[] = $array[$key];
    }
  }
  unset($array);

  return $map;
}

Здесь мы выбираем версию "pluck" для вызова в зависимости от используемой версии PHP. Если вы запустите весь сценарий, вы должны получить правильный ответ независимо от того, какая у вас версия.

$actual   = version_compare(PHP_VERSION, '5.3.0', '>=')
          ? $preferredPluck('categoryId', $data)
          : pluck_compat('categoryId', $data);
$expected = array(1, 5, 9, false, 0.0);
$variance = count(array_diff($expected, $actual));

var_dump($expected, $actual);
echo PHP_EOL;
echo 'variance: ', $variance, PHP_EOL;

print @assert($variance)
    ? 'Assertion Failed'
    : 'Assertion Passed';

Обратите внимание, что нет окончания '?>'. Именно потому, что это не нужно. Больше пользы может быть от отказа от него, чем от его сохранения.

FWIW, похоже, что это добавляется в PHP 5.5 как столбец_массива.

person Wil Moore III    schedule 18.05.2012
comment
Вау, какая это версия PHP? Я никогда не видел «использовать», это довольно круто. - person Caleb Gray; 19.05.2012
comment
использование предоставляется в PHP 5.3, когда они представили анонимные функции (лямбды и замыкания): php.net/functions.anonymous - person Wil Moore III; 19.05.2012
comment
Ради тех, кто найдет это, не возражаете ли вы использовать функцию create_function и просто поместить array_map в вызов array_filter (избавившись от переменной $map)? - person Caleb Gray; 19.05.2012
comment
На данный момент мне интересно, что лучше: ваша версия или тесты tadeck. Если вы включаете проверку работоспособности isset, используйте create_function для совместимости с PHP 4 и покажите, что ваша версия работает быстрее, чем я выберу ваш ответ. - person Caleb Gray; 19.05.2012
comment
Привет, @CalebGray, я сделал прототип нескольких версий, и в итоге мне не понравилась версия create_function. Я отметил свои причины в обновленном ответе. - person Wil Moore III; 19.05.2012
comment
Привет @salathe; вы подняли хороший вопрос. Я почти сделал свою первоначальную реализацию с помощью array_reduce(); тем не менее, я обычно резервирую array_reduce для ситуаций, которые не могут быть легко обработаны чем-то другим. array_reduce потрясающий; тем не менее, для рассуждений требуется чуть больше когнитивных накладных расходов. - person Wil Moore III; 19.05.2012
comment
@wilmoore это не потребует больше когнитивных затрат, чем ваш (откровенно странный) выбор голого array_filter() после карты. - person salathe; 19.05.2012
comment
@CalebGray, если все дело в эталоне, то я с радостью принимаю, что, возможно, другая запись может лучше удовлетворить ваши требования. В общем, я бы не стал пытаться оптимизировать такую ​​вещь в реальном мире. Если бы эта функция нуждалась в оптимизации, я бы, скорее всего, добавил необходимую функциональность в расширение C. Так я бы больше заработал. Но в этом случае я могу также написать приложение с JRuby, чтобы в первую очередь получить скорость JVM и лучший язык (Ruby) для загрузки :) - person Wil Moore III; 19.05.2012
comment
Ха, достаточно честный салат. Спасибо, Уилмур, я принял ваш отличный ответ. - person Caleb Gray; 19.05.2012
comment
@CalebGray, спасибо, что приняли, и спасибо за вдумчивый вопрос. - person Wil Moore III; 19.05.2012
comment
@wilmoore ваши две функции дают разные результаты с ложными (не) выбранными значениями. - person salathe; 19.05.2012
comment
Привет @salathe, я полагаю, что голый array_filter() немного странный; однако это именно так, как PHP. Я бы хотел, чтобы это было не так. Это отбрасывание нулевых значений. Это стандартное поведение :) - person Wil Moore III; 19.05.2012
comment
@wilmoore использует "is_null" с фильтром (но тогда мой предыдущий комментарий останется в силе, но с нулевыми значениями). - person salathe; 19.05.2012
comment
@salathe: если он использует is_null в качестве обратного вызова, то только NULL значения не будут фильтроваться. Я думаю, что strlen был бы более подходящим обратным вызовом, хотя и немного несемантически. - person Alix Axel; 19.05.2012
comment
О, вот так... Тогда вы можете вместо этого использовать isset(). - person Alix Axel; 19.05.2012
comment
@AlixAxel Да, вы правы. Использование is_null в качестве обратного вызова оставляет в возвращаемом списке только значения NULL. Я также согласен с тем, что strlen (хотя это, вероятно, сработает) будет меньше, чем раскрытие намерений. В итоге я стал более подробным с обратным вызовом, который проверяет !is_null. - person Wil Moore III; 19.05.2012
comment
@wilmoore: Да, я не понял, что обратный вызов тоже должен был быть лямбда-функцией. В любом случае !is_null() === isset(), кроме этой хорошей работы. ;) - person Alix Axel; 19.05.2012
comment
is_scalar() не помогает с нескалярными значениями (ну, черт возьми!), которые массив мог бы счастливо содержать. (P.S. между моими пальцами и мозгом возник какой-то разрыв; я не собирался использовать is_null в качестве фильтра, но в фильтре.) - person salathe; 19.05.2012
comment
Если вы пользуетесь хостинговым решением, которое позволяет вам обновиться до PHP 5.5 через файл конфигурации, функция array_column — это именно то, что вам нужно. Просто обязательно прочитайте документацию по php для обновления. - person Nick Gronow; 05.11.2014
comment
@NicholasGronow Вы понизили ответ, потому что ненавидите ответы, которые не были написаны только на прошлой неделе? :)- - person Wil Moore III; 06.11.2014
comment
На самом деле я проголосовал за этот ответ :) И, поскольку я пытаюсь побудить людей оставаться в курсе PHP в своих решениях для хостинга, я решил, что подробнее расскажу об этом в комментарии. Хороший и исчерпывающий ответ Уилмур. - person Nick Gronow; 07.11.2014
comment
Круто, спасибо @NicholasGronow. Думаю, это был кто-то другой. Это действительно трудно сказать; хотя я, вероятно, просто что-то упускаю, так как не трачу много времени на SO. В любом случае, спасибо :) - person Wil Moore III; 08.11.2014
comment
можете ли вы объяснить, почему вы отключили переменную $array перед возвратом в примере 5.3+? Я чувствую, что GC должен позаботиться об этом для вас. - person Jon z; 06.03.2015
comment
@Jonz Я давно не писал PHP; однако я помню, что foreach выведет переменную за пределы блока. Конечно, как только функция выходит за рамки, сборщик мусора должен очиститься. Явно сбрасывать настройки не нужно; тем не менее, я также не люблю бросать мусор, хотя знаю, что его, скорее всего, в конечном итоге подберут; ну вот :) - person Wil Moore III; 06.03.2015

Картографирование — это то, что вам нужно:

$input = array(
    array(
        'categoryId' => 1,
        'eventId' => 2,
        'eventName' => 3,
        'vendorName' => 4,
    ),
    array(
        'categoryId' => 5,
        'eventId' => 6,
        'eventName' => 7,
        'vendorName' => 8,
    ),
    array(
        'categoryId' => 9,
        'eventId' => 10,
        'eventName' => 11,
        'vendorName' => 12,
    ),
);

$result = array_map(function($val){
    return $val['categoryId'];
}, $input);

Или создайте функцию, которую вы хотели:

function get_values_from_a_key_in_arrays($key, $input){
    return array_map(function($val) use ($key) {
        return $val[$key];
    }, $input);
};

а затем используя его:

$result = get_values_from_a_key_in_arrays('categoryId', $array);

Он будет работать в PHP >= 5.3, где разрешены анонимные обратные вызовы. Для более ранних версий вам нужно будет определить обратный вызов раньше и передать его имя вместо анонимной функции.

person Tadeck    schedule 18.05.2012
comment
ты можешь сделать return $val['categoryId']; return $val[$key]; ? - person Songo; 19.05.2012
comment
@Songo: Без создания массива, состоящего из count($input) значений $key, одним из решений было бы array_map(function($val, $key = 'categoryId') {return $val[$key];}, $input), но я не вижу никакой выгоды в том, чтобы делать это в лямбда-функции. - person Alix Axel; 19.05.2012
comment
@Songo: Другим решением было бы: array_map(function($val, $key) {return $val[$key];}, $input, array_fill(0, count($input), 'categoryId')). - person Alix Axel; 19.05.2012
comment
@Songo: я добавил функцию точно так, как просил OP, так что вы должны быть довольны;) - person Tadeck; 19.05.2012
comment
@AlixAxel: Спасибо, но сначала это довольно избыточно, так как $key никогда не устанавливается, так что в основном это способ написать то, что я сделал, по-другому. Второе решение, в свою очередь, является излишним, что приводит к снижению читабельности кода. Извините, я пошел своим путем, заключив его в другую функцию - надеюсь, вам понравится. - person Tadeck; 19.05.2012
comment
Не используйте create_function(), если не хотите array_map() с анонимной функцией: вместо этого используйте цикл (который в любом случае будет быстрее, чем вызов обратного вызова карты). - person salathe; 19.05.2012
comment
@AlixAxel в вашем первом решении, как передать значение $key лямбда-функции? - person Songo; 19.05.2012
comment
@Songo, посмотри его другое решение. - person salathe; 19.05.2012
comment
@salathe Ага!! теперь я понял :D неудивительно, почему я не мог заставить его работать :))) - person Songo; 19.05.2012
comment
@Tadeck: Да, это была моя точка зрения. Я как раз обращался к сомнениям Сонгоса. - person Alix Axel; 19.05.2012
comment
@AlixAxel: Хорошо, я привык к естественному способу выполнения обратных вызовов, например, в Python или JavaScript, и забыл о реализации замыканий в PHP и use;) Спасибо, салате. - person Tadeck; 19.05.2012

Для этого нет встроенной функции, но обычно она называется "pluck.

person Alix Axel    schedule 18.05.2012
comment
Начиная с PHP 5.5, есть встроенный: array_column. - person bishop; 26.10.2016

<?php
$a = array(
        array('a' => 1, 'b' => 2),
        array('a' => 2, 'b' => 2),
        array('a' => 3, 'b' => 2),
        array('a' => 4, 'b' => 2)
);

function get_a($v) {
        return $v['a'];
}

var_dump(array_map('get_a', $a));

Вы можете использовать функцию create_function или анонимную функцию (PHP 5.3 >=)

<?php
$a = array(
        array('a' => 1, 'b' => 2),
        array('a' => 2, 'b' => 2),
        array('a' => 3, 'b' => 2),
        array('a' => 4, 'b' => 2)
);

var_dump(array_map(create_function('$v', 'return $v["a"];'), $a));

Я бы написал функцию обратного вызова, как указано выше, а затем использовал ее с array_map.

person Rawkode    schedule 18.05.2012
comment
если вы используете PHP5.3 или выше, вы можете написать get_a как лямбда-функцию и сделать ее еще более компактной. (редактировать... как я заметил, другие ответы делают) - person Spudley; 19.05.2012
comment
@Spudley: Смотрите мой ответ, который делает это именно так, как вы упомянули. - person Tadeck; 19.05.2012
comment
@Tadeck - да, я видел это (и дал вам +1), но только после того, как сначала прокомментировал этот ответ. - person Spudley; 19.05.2012
comment
create_function отстой, и он доступен до версии PHP 5.3. - person Alix Axel; 19.05.2012

Нет встроенной функции. Но один легко сделать с array_map().

$array = array(
    array(
        "categoryID"   => 1,
        "CategoryName" => 2,
        "EventName"    => 3,
        "VendorName"   => 4
    ),
    array(
        "categoryID"   => 5,
        "CategoryName" => 6,
        "EventName"    => 7,
        "VendorName"   => 8
    ),
    array(
        "categoryID"   => 9,
        "CategoryName" => 10,
        "EventName"    => 11,
        "VendorName"   => 12
    )
);

$newArray = array_map(function($el) {
    return $el["categoryID"];
}, $array);

var_dump($newArray);
person Madara's Ghost    schedule 18.05.2012

Начиная с PHP 5.5, используйте array_column:

$events = [
    [ 'categoryId' => 1, 'eventId' =>  2, 'eventName' =>  3, 'vendorName' =>  4 ],
    [ 'categoryId' => 5, 'eventId' =>  6, 'eventName' =>  7, 'vendorName' =>  8 ],
    [ 'categoryId' => 9, 'eventId' => 10, 'eventName' => 11, 'vendorName' => 12 ],
];

print_r(array_column($events, 'categoryId'));

См. на сайте 3v4l.

Для версий до 5.5 вы можете рассмотреть возможность использования полифилла.

person bishop    schedule 26.10.2016

Где лисп, когда он нужен? На самом деле в php тоже довольно легко управлять. Просто используйте функцию array_map, как показано ниже.

# bunch o data
$data = array();
$data[0] = array("id" => 100, "name" => 'ted');
$data[1] = array("id" => 200, "name" => 'mac');
$data[2] = array("id" => 204, "name" => 'bub');

# what you want to do to each bit of it
function pick($n) { return($n['id']); }
# what you get after you do that
$map = array_map("pick", $data);
# see for yourself
print_r($map);
person zortacon    schedule 18.05.2012

Вы можете использовать array_filter и передать функцию на основе нужный ключ.

Однако ответ Тадека намного лучше.

person Turnsole    schedule 18.05.2012