131

I'm using a map in php like so:

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

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

Is it possible to get the index of the value in the function?

Also - if I'm writing code that needs the index, should I be using a for loop instead of a map?

0

11 Answers 11

286

Sure you can, with the help of 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);
Sign up to request clarification or add additional context in comments.

9 Comments

@Gordon yeah you can supply array_map() with an arbitrary number of arguments :)
This is a very risky approach as PHP does not guarantee that keys returned by array_keys will remain in same order as in original array. Thus you might end up mapping keys to wrong values. The safe approach is to use only array_keys as the second argument of array_map and then pass array to closure with use statement.
I frankly don't understand why PHP doesn't have a map function that supplies the key of each element as the second parameter of the callback.
@TimBezhashvyly Can you please flesh that out with a link to PHP's documentation? php.net/manual/en/function.array-keys.php Doesn't say anything about the order not being guaranteed.
I agree that it's better not to use array_keys in this situation. Instead, we can get the indexes using: range(0, count($array)-1)
|
15

When mapping an anonymous function over an anonymous array, there is no way to access the keys:

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

array_reduce doesn't get access to the keys either. array_walk can access keys, but the array is passed by reference, which requires a layer of indirection.

Some solutions are:

Array of pairs

This is bad, since we're changing the original array. Plus the boilerplate "array()" calls increase linearly with the length of the array:

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

Temporary variable

We're acting on the original array, and the boilerplate is constant, but we can easily clobber an existing variable:

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

One-shot function

We can use function scope to prevent clobbering existing names, but have to add an extra layer of "use":

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

Multi-argument one-shot function

We define the function we're mapping in the original scope to prevent the "use" boilerplate):

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

New function

The interesting thing to note is that our last one-shot function has a nice, generic signature and looks a lot like array_map. We might want to give this a name and re-use it:

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

Our application code then becomes:

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

Indirect Array Walk

When writing the above I'd ignored array_walk since it requires its argument to be passed by reference; however, I've since realised that it's easy to work around this using call_user_func. I think this is the best version so far:

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

Comments

15

There is no way to access the index within the array_map callback. If you are working with sequential numeric indices, then an incrementing static variable could be used:

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

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

print_r($mapped);

Resulting in:

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

When using this approach, it's important to use an anonymous function as the callback and to never reuse that anonymous function to avoid referencing the same static variable outside of array_map.

Comments

7

It's a bit of an old thread but as many of you, I'm using array_keys:

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

Edit: Instead of use keyword, you can add two arrays in second parameter of your arrray_map function. I think no explications needed, the code is pretty simple.

1 Comment

array_values($array) can safely be $array. If you aren't going to use array_map()'s return, then you probably should not be calling array_map(). It is most likely that array_walk() is better suited.
2

Very simple:

Only array_map fuction: does not have index key!

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

Now, combine with 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)

Comments

2

As mentioned by earlier posted answers, when keys and values are needed within the callback function's body, it is simplest to pass in the array of keys, then access the values for those keys via the original array.

With older versions that do not have arrow functions, use use() or global (I recommend the former) to allow access to the original array from within the callback.

With modern PHP (7.4+), use arrow function syntax. (Demo)

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

There is also no shame in using a foreach() and modifying the values by reference. (Demo)

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

Comments

2

One simple line that I use to build PDO queries. Works well. Uses anonymous function in this case.

$values = array(4, 6, 3);
$mapped = array_map(function($v,$i) {return $v*2;}, $values, range(0,count($values)-1));
var_dump($mapped);

Comments

0

You can create your own map function using 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);

try: https://3v4l.org/pmFlB

Comments

0

For a fast and open solution (without doubling array using array_keys and similar):

/**
 * 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;
}

Usage examples:

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

Comments

0

You can generate your own index to be used with 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);

As shown above, you may securely create an array of numbers from 0 to the length of your array, same as a index. Put this second array in the array_map and use it values as index in your function.

2 Comments

I don't see where the func constant is defined. You did not test this code. Please test your snippets on 3v4l.org before posting in Stack Overflow.
Calling two functions (range(0, count($values) - 1)) to generate data that is already generated is not a good idea. Just access what is already available with array_keys().
-2

You can use the array_keys() function to get the keys of an array, and then loop through those keys using a foreach loop.

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

1 Comment

You're right, but this is not exactly what the OP asks (the question is about array_map)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.