PHP: Могу ли я получить индекс в функции array_map?

#php #arrays #loops #dictionary #iteration

#php #массивы #циклы #словарь #итерация

Вопрос:

Я использую карту в php следующим образом:

 function func($v) {
    return $v * 2;
}

$values = array(4, 6, 3);
$mapped = array_map(func, $values);
var_dump($mapped);
  

Возможно ли получить индекс значения в функции?

Также — если я пишу код, которому нужен индекс, должен ли я использовать цикл for вместо map?

Ответ №1:

Конечно, вы можете, с помощью array_keys():

 function func($v, $k)
{
    // key is now $k
    return $v * 2;
}

$values = array(4, 6, 3);
$mapped = array_map('func', $values, array_keys($values));
var_dump($mapped);
  

Комментарии:

1. @Gordon да, вы можете предоставить array_map() произвольное количество аргументов 🙂

2. Это очень рискованный подход, поскольку PHP не гарантирует, что ключи, возвращаемые array_keys , останутся в том же порядке, что и в исходном массиве. Таким образом, вы можете в конечном итоге сопоставить ключи с неправильными значениями. Безопасный подход заключается в том, чтобы использовать только array_keys в качестве второго аргумента array_map , а затем передать массив для закрытия с помощью use инструкции.

3. Честно говоря, я не понимаю, почему в PHP нет функции map, которая предоставляет ключ каждого элемента в качестве второго параметра обратного вызова.

4. @TimBezhashvyly Не могли бы вы, пожалуйста, дополнить это ссылкой на документацию PHP? php.net/manual/en/function. array-keys.php Ничего не говорит о том, что порядок не гарантируется.

5. Я согласен, что в этой ситуации лучше не использовать array_keys. Вместо этого мы можем получить индексы, используя: range(0, count($array) -1)

Ответ №2:

При отображении анонимной функции поверх анонимного массива невозможно получить доступ к ключам:

 array_map(
    function($val) use ($foo) { /* ... */ },
    array(key1 => val1,
          key2 => val2,
          /* ... */));
  

array_reduce также не получает доступа к ключам. array_walk может получать доступ к ключам, но массив передается по ссылке, что требует уровня косвенности.

Некоторые решения:

Массив пар

Это плохо, поскольку мы меняем исходный массив. Плюс стандартные вызовы «array()» линейно увеличиваются с длиной массива:

 array_map(
    function($pair) use ($foo) {
        list($key, $val) = $pair;
        /* ... */
    },
    array(array(key1, val1),
          array(key2, val2),
          /* ... */));
  

Временная переменная

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

 $i_hope_this_does_not_conflict = array(key1 => val1,
                                       key2 => val2,
                                       /* ... */);
array_map(
    function($key, $val) use ($foo) { /* ... */ },
    array_keys($i_hope_this_does_not_conflict),
    $i_hope_this_does_not_conflict);
unset($i_hope_this_does_not_conflict);
  

Одноразовая функция

Мы можем использовать область действия функции, чтобы предотвратить загромождение существующих имен, но для этого нужно добавить дополнительный уровень «использовать»:

 call_user_func(
    function($arr) use ($foo) {
        return array_map(function($key, $val) use ($foo) { /* ... */ },
                         array_keys($arr),
                         $arr);
    },
    array(key1 => val1,
          key2 => val2,
          /* ... */));
  

Одноразовая функция с несколькими аргументами

Мы определяем функцию, которую мы сопоставляем, в исходной области видимости, чтобы предотвратить «использование» шаблона):

 call_user_func(
    function($f, $arr) {
        return array_map($f, array_keys($arr), $arr);
    },
    function($key, $val) use ($foo) { /* ... */ },
    array(key1 => val1,
          key2 => val2,
          /* ... */));
  

Новая функция

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

 function array_mapk($f, $arr) {
    return array_map($f, array_keys($arr), $arr);
}
  

Затем наш код приложения становится:

 array_mapk(
    function($key, $val) use ($foo) { /* ... */ },
    array(key1 => val1,
          key2 => val2,
          /* ... */));
  

Косвенный обход массива

При написании вышеизложенного я проигнорировал array_walk, поскольку он требует, чтобы его аргумент передавался по ссылке; однако с тех пор я понял, что это легко обойти с помощью call_user_func . Я думаю, что это лучшая версия на данный момент:

 call_user_func(
    'array_walk',
    array(key1 => val1,
          key2 => val2,
          /* ... */),
    function($val, $key) use ($foo) { /* ... */ });
  

Ответ №3:

Нет способа получить доступ к индексу в array_map обратном вызове. Если вы работаете с последовательными числовыми индексами, то можно использовать увеличивающуюся статическую переменную:

 $values = ["one", "two", "three"];

$mapped = array_map(function ($value) {
    static $i = 0;
    $result = "Index: $i, Value: $value";
    $i  ;
    return $result;
}, $values);

print_r($mapped);
  

В результате:

 Array
(
    [0] => Index: 0, Value: one
    [1] => Index: 1, Value: two
    [2] => Index: 2, Value: three
)
  

При использовании этого подхода важно использовать анонимную функцию в качестве обратного вызова и никогда не использовать повторно эту анонимную функцию, чтобы избежать ссылки на ту же статическую переменную за пределами array_map .

Ответ №4:

Это немного старая тема, но, как и многие из вас, я использую array_keys :

 array_map(function($id, $name) {
    print '<option value="'.$id.'">'.$name.'</option>';
}, array_keys($array), array_values($array));
  

Редактировать: Вместо use ключевого слова вы можете добавить два массива во второй параметр вашей arrray_map функции. Я думаю, никаких пояснений не требуется, код довольно прост.

Комментарии:

1. array_values($array) может быть безопасно $array . Если вы не собираетесь использовать array_map() ‘s return , то вам, вероятно, не следует вызывать array_map() . Скорее всего, это array_walk() лучше подходит.

Ответ №5:

Очень просто:

Только функция array_map: не имеет ключа индекса!

  $params = [4,6,2,11,20];

 $data = array_map(function($v) { return ":id{$v}";}, $params);

 array (size=5)
  0 => string ':id4' (length=4)
  1 => string ':id6' (length=4)
  2 => string ':id2' (length=4)
  3 => string ':id11' (length=5)
  4 => string ':id20' (length=5)
  

Теперь объедините с array_keys:

 $data = array_map(
    function($k) use ($params) { return ":id{$k}_${params[$k]}"; },
    array_keys($params)
 );

array (size=5)
  0 => string ':id0_4' (length=6)
  1 => string ':id1_6' (length=6)
  2 => string ':id2_2' (length=6)
  3 => string ':id3_11' (length=7)
  4 => string ':id4_20' (length=7)
  

Ответ №6:

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

В более старых версиях, которые не имеют функций со стрелками, используйте use() или global (я рекомендую первое), чтобы разрешить доступ к исходному массиву из обратного вызова.

В современном PHP (7.4 ) используйте синтаксис функции arrow. (Демо)

 $values = [4, 6, 3];
var_export(
    array_map(
        fn($k) => "$k: " . $values[$k] * 2,
        array_keys($values)
    )
);
  

Также нет ничего постыдного в использовании foreach() и изменении значений по ссылке. (Демо)

 $values = [4, 6, 3];
foreach ($values as $k => amp;$v) {
    $v = "$k: " . $v * 2;
}
var_export($values);
  

Ответ №7:

Вы можете создать свою собственную функцию map с помощью foreach :

 <?php

function myCallback($key, $val)
{
    var_dump("myCallback - key: $key, val: $val");
    return $val * 2;
}

function foreachMap($callback, $givenArray) {
    $result = [];
    foreach ($givenArray as $key=>$val) {
        $result[$key] = $callback($key, $val);
    }
    return $result;
}

$values = array(4, 6, 3);
$mapped = foreachMap('myCallback', $values);
var_dump($mapped);

  

попробуйте: https://3v4l.org/pmFlB

Ответ №8:

Для быстрого и открытого решения (без удвоения массива с помощью array_keys и подобных):

 /**
 * Array map alternative to work with values and keys of single array.
 *
 * Callable receives $value and $index of $sourceArray as arguments
 * If keys are not preserved via $preserveKeys - $keyCallback can be used to determinate key
 *
 * @param array $sourceArray
 * @param callable|null $valueCallback
 * @param callable|null $keyCallback
 * @param bool $preserveKeys
 * @return array
 */
function array_map_indexed(
    array $sourceArray,
    ?callable $valueCallback = null,
    ?callable $keyCallback = null,
    bool $preserveKeys = true
): array {
    $newArray = [];

    foreach ($sourceArray as $key => $value) {
        if ($preserveKeys) {
            $newArray[$keyCallback ? $keyCallback($value, $key) : $key] = $valueCallback
                ? $valueCallback($value, $key)
                : $value;
        } else {
            $newArray[] = $valueCallback
                ? $valueCallback($value, $key)
                : $value;
        }
    }

    return $newArray;
}
  

Примеры использования:

 $result = array_map_indexed(
    [
        'a' => 'aValue',
        'b' => 'bValue',
    ],
    function($value, $index) {
        return [$value, $index];
    },
);
//Array ( [a] => Array ( [0] => aValue [1] => a ) [b] => Array ( [0] => bValue [1] => b ) )

$result = array_map_indexed(
    [
        'a' => 'aValue',
        'b' => 'bValue',
    ],
    function($value, $index) {
        return $index.$value;
    },
    null,
    false
);
//Array ( [0] => aaValue [1] => bbValue )

$result = array_map_indexed(
    [
        'a' => 'aValue',
        'b' => 'bValue',
    ],
    null,
    function($value, $index) {
        return $value === 'aValue' ? 'specificKey' : $index;
    },
);
//Array ( [specificKey] => aValue [b] => bValue )
  

Ответ №9:

Вы можете сгенерировать свой собственный индекс для использования с array_map:

 function func($v, $index) {
  return $v * 2;
}

$values = array(4, 6, 3);
$valuesIndex = range(0, count($values) - 1);
$mapped = array_map(func, $values, $valuesIndex);
var_dump($mapped);
  

Как показано выше, вы можете безопасно создать массив чисел от 0 до длины вашего массива, такой же, как индекс.
Поместите этот второй массив в array_map и используйте его значения в качестве индекса в вашей функции.

Комментарии:

1. Я не вижу, где определена func константа. Вы не тестировали этот код. Пожалуйста, протестируйте свои фрагменты на 3v4l.org перед публикацией в Stack Overflow.

2. Вызов двух функций ( range(0, count($values) - 1) ) для генерации данных, которые уже сгенерированы, не является хорошей идеей. Просто получите доступ к тому, что уже доступно с помощью array_keys() .

Ответ №10:

Вы можете использовать функцию array_keys() для получения ключей массива, а затем перебирать эти ключи с помощью цикла foreach.

 $keys = array_keys($array);
foreach($keys as $key){
   // do something with $key
}
  

Комментарии:

1. Вы правы, но это не совсем то, что задает OP (вопрос о array_map )