Can I set the keys of an array using array functions like array_map
Asked Answered
D

4

6

I really like the functional programming style of using array map to create an array of objects from another array of objects.

$newObjects = array_map(
  function($oldObject) {
    return new NewObject($oldObject);
  },
  $oldObjects
);

Which all works fine but I would really like to be able to set the indices of the array so that they are the ids of the original objects for easier search and retrieval from the array but I cannot think how to do it other then which is not as elegant.

$newObjects = array();
foreach ($oldObjects as $oldObject) {
  $newObjects[$oldObject->getId()] = new NewObject($oldObject);
}

Is there a way I can do this?

Dort answered 16/6, 2015 at 10:0 Comment(2)
Devil's advocate comment: as much as I love map/reduce/walk etc. sometimes a foreach is the most straightforward, readable solution :)Liu
Yes i agree, horses for courses and all that. Looking at the suggestion below the foreach has a lot more going for it in terms of style and readabilityDort
M
3

That is - array_reduce() is exactly what you need:

class Bar 
{
        protected $id;

        public function __construct($id)
        {
                $this->id = $id;
        }

        public function getId()
        {
                return $this->id;
        }
}

class Foo
{
        protected $bar;

        public function __construct(Bar $bar)
        {
                $this->bar = $bar;
        }
}

$oldObjects = [new Bar('x'), new Bar('y'), new Bar('z')];

$newObjects = array_reduce($oldObjects, function($current, Bar $obj) {
        $current[$obj->getId()] = new Foo($obj);
        return $current;
}, []);

This will do all in-place without having to spend memory on additional arrays like for array_combine()

However, I would suggest to use such constructs when they're necessary. Using this just because it "looks better" might be not a good idea - as plain loops are in most cases just more readable.

Minne answered 16/6, 2015 at 12:18 Comment(2)
Hmm, interesting. Have to look a bit more into that to properly get how it works (never really used array_reduce much). I agree very much about this not necessarily being the best approach and on balance I think that either the foreach or the array_combine options provide the best balance or readability and elegance. This is more of a exercise in understanding than demanding to find a functional approach that is better than the foreach one (although it would be nice)Dort
In the very bottom all those options boil down to the loop, so with this it's possible just to hide it under the hoodMinne
W
1

What if you use array_walk and a temporary array with your new indices.

    $array = ['A', 'B', 'C', 'D'];
    $reIndexedTemp = [];

    array_walk(
        $array,
        function ($item, $key) use (&$reIndexedTemp) {
            // here you can have your logic to assemble your new index
            $reIndexedTemp[$key + 100] = $item;
        }
    );

    //$array = $reIndexedTemp;

    var_dump($array, $reIndexedTemp);

output (without the commented line) :

array(4) {
  [0] =>
  string(1) "A"
  [1] =>
  string(1) "B"
  [2] =>
  string(1) "C"
  [3] =>
  string(1) "D"
}
array(4) {
  [100] =>
  string(1) "A"
  [101] =>
  string(1) "B"
  [102] =>
  string(1) "C"
  [103] =>
  string(1) "D"
}
Weatherworn answered 16/6, 2015 at 10:16 Comment(3)
Hmm, I think that is less elegant! I didn't want to use a temporary array. Also the $key is just the original key which is not relevant to be honest.Dort
Im not a fan of the temp array :) , but all those array functions that i could think of are relaying on the array indices for the obvious reason. And manipulating one middle of a processing the array itself doesn't seems to be a good idea. if you don't need the key from the original array at all then you can ignore the array_walk method and keep the array_map but i'm afraid the temporary array still will be required!Weatherworn
It is not a temporary array. It stores the output you need. Unfortunately array_walk() modifies the array it receives as argument and doesn't return any useful value. It cannot be used directly in a functional programming style :-( If you need to use this processing flow in several places think about wrapping the code above in a function that returns the "temporary" array (and fulfill your desire for functional programming style.)Sunlight
L
1

I think a foreach is probably the most readable solution in this case, but you can use array_map() with array_combine() to achieve what you want. Something like:

// empty array to store the old object ids
$ids = [];

// map over old objects, inheriting $id 
// from parent scope by reference 
$objs = array_map(function($oldObject) use (&$ids) {
    $ids[] = $oldObject->getId();
    return new NewObject($oldObject);
}, $oldObjects);

// combine id and object arrays
$newObjects = array_combine($ids, $objs);

Hope this helps :)

Liu answered 16/6, 2015 at 11:1 Comment(2)
Ah yes, that is a bit neater than mine. Although for some reason I don't like the external array (i have an irrational dislike of objects passed by reference!)Dort
Haha, fair enough! It looks a bit gnarly alright. More 'functional' languages do the same thing much more transparently. PHP isn't the prettiest language I guess, but we still love it... right??Liu
D
0

Looking around - Looking for array_map equivalent to work on keys in associative arrays

Suggests it might work using array_combine

So I guess it would be

$newObjects = array_combine(
  array_map(
    function($oldObject) {
      return $oldObject->getId();
    },
    $oldObjects
  ),
  array_map(
    function($oldObject) {
      return new NewObject($oldObject);
    },
    $oldObjects
  )
);

Hmm probably the best, just this side of overblown but definately a lot more complex than the foreach

Dort answered 16/6, 2015 at 10:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.