How to convert an array of arrays or objects to an associative array?
Asked Answered
N

4

21

I'm used to perl's map() function where the callback can assign both the key and the value, thus creating an associative array where the input was a flat array. I'm aware of array_fill_keys() which can be useful if all you want to do is create a dictionary-style hash, but what if you don't necessarily want all values to be the same? Obviously all things can be done with foreach iteration, but what other (possibly more elegant) methods exist?

Edit: adding an example to clarify the transformation. Please don't get hung up on the transformation, the question is about transforming a flat list to a hash where we can't assume that all the values will be the same.

$original_array: ('a', 'b', 'c', 'd')
$new_hash: ('a'=>'yes', 'b'=>'no', 'c'=>'yes', 'd'=>'no')

*note: the values in this example are arbitrary, governed by some business logic that is not really relevant to this question. For example, perhaps it's based on the even-oddness of the ordinal value of the key

Real-world Example So, using an answer that was provided here, here is how you could parse through the $_POST to get a list of only those input fields that match a given criteria. This could be useful, for example, if you have a lot of input fields in your form, but a certain group of them must be processed together.

In this case I have a number of input fields that represent mappings to a database. Each of the input fields looks like this: <input name="field-user_email" value="2" /> where each of this type of field is prefixed with "field-".

what we want to do is, first, get a list of only those input fields who actually start with "field-", then we want to create an associative array called $mapped_fields that has the extracted field name as the key and the actual input field's value as the value.

$mapped_fields = array_reduce( preg_grep( '/field-.+/', array_keys( $_POST ) ), function( $hash, $field ){ $hash[substr( $field, 6 )] = $_POST[$field]; return $hash; } );

Which outputs:

Array ( [date_of_birth] => 1 [user_email] => 2 [last_name] => 3 [first_name] => 4 [current_position] => 6 )

(So, just to forestall the naysayers, let me agree that this bit of compact code is arguably a lot less readable that a simple loop that iterates through $_POST and, for each key, checks to see if it has the prefix, and if so, pops it and its value onto an array)

Navarre answered 19/7, 2012 at 14:34 Comment(1)
Can you post a dump of the array (using print_r or var_dump)?Confirmand
S
53

I had the exact same problem some days ago. It is not possible using array_map, but array_reduce does the trick.

$arr = array('a','b','c','d');
$assoc_arr = array_reduce($arr, function ($result, $item) {
    $result[$item] = (($item == 'a') || ($item == 'c')) ? 'yes' : 'no';
    return $result;
}, array());
var_dump($assoc_arr);

result:

array(4) { ["a"]=> string(3) "yes" ["b"]=> string(2) "no" ["c"]=> string(3) "yes" ["d"]=> string(2) "no" }

Saratov answered 19/11, 2012 at 20:53 Comment(4)
Wow, it never occurred to me to use array_reduce in this way - figuring it was always meant to just return a scalar value. In retrospect, it's arguable whether this approach is in any way preferable to the foreach method @minitech mentions, above. But it's very nice to see your alternative! Thanks for posting.Navarre
Clever method. This often comes very handyTransgression
I'm actually agreeing with Tom Auger, it's questionable if one should use this method. Even if you win on the performance side (which I'm not sure of), you loose readability compared to a usual foreach statement. Elegant though ;)Saratov
Wow, this is a great solution for mapping elements into a named array from an object. I have a class with a dozen properties that I need to be put into named array before writing to a database. The problem is I don't need ALL the object properties, just a subset. The "properties that live in the database" array has that list so this works beautifully. function MakePersistent() { $dbData = array_reduce($this->persistentProperties,array($this,'mapFields')); ..write to db here... } function mapFields($result,$property) { $result[$property] = $this->$property; return $result; }.... DONE!Forfeit
P
3

As far as I know, it is completely impossible in one expression, so you may as well use a foreach loop, à la

$new_hash = array();

foreach($original_array as $item) {
    $new_hash[$item] = 'something';
}

If you need it in one expression, go ahead and make a function:

function array_map_keys($callback, $array) {
    $result = array();

    foreach($array as $item) {
        $r = $callback($item);

        $result[$r[0]] = $r[1];
    }

    return $result;
}
Persist answered 19/7, 2012 at 18:9 Comment(0)
F
2

This is a clarification on my comment in the accepted method. Hopefully easier to read. This is from a WordPress class, thus the $wpdb reference to write data:

class SLPlus_Locations {
    private $dbFields = array('name','address','city');

    public function MakePersistent() {
        global $wpdb;
        $dataArray = array_reduce($this->dbFields,array($this,'mapPropertyToField'));
        $wpdb->insert('wp_store_locator',$dataArray);
    }

    private function mapPropertyToField($result,$property) {
        $result[$property] = $this->$property;
        return $result;
    }
}

Obviously there is a bit more to the complete solution, but the parts relevant to array_reduce() are present. Easier to read and more elegant than a foreach or forcing the issue through array_map() plus a custom insert statement.

Nice!

Forfeit answered 8/3, 2013 at 21:29 Comment(1)
Being a bit of a sucker for one-liners and Perl-style obfu code, this has been my go-to solution, even in production projects. Nice one, and thanks for coming back to provide another solution.Navarre
S
1

A good use case of yield operator!

$arr = array('a','b','c','d');

$fct = function(array $items) {
            foreach($items as $letter)
            {
                yield sprintf("key-%s",
                    $letter
                ) => "yes";
            }
        };

$newArr = iterator_to_array($fct($arr));

which gives:

array(4) {
  'key-a' =>
  string(3) "yes"
  'key-b' =>
  string(3) "yes"
  'key-c' =>
  string(3) "yes"
  'key-d' =>
  string(3) "yes"
}
Stace answered 24/4, 2019 at 12:52 Comment(2)
Seems REALLY verbose and convoluted BUT I love the iterator example - I have been programming PHP for 10+ years and have never once used yield. Thanks for that!Navarre
I didn't even know PHP had generators!Notch

© 2022 - 2024 — McMap. All rights reserved.