-1

I have a array with following values.I am trying to create a new array using array php array functions and trying to max avoid foreach. The key we are using for new array is "status" and depending on status we make new array for each mail id.

<?php
[
    {
        "mail_id": "29848947",
        "last_name": "Doe",
        "first_name": "Jon",
        "email": "[email protected]",
        "status": "opened"
    },
    {
        "mail_id": "340980398",
        "last_name": "Doe",
        "first_name": "Jane",
        "email": "[email protected]",
        "status": "sent"
    },
    {
        "mail_id": "877586",
        "last_name": "Dwaye",
        "first_name": "Jhon",
        "email": "[email protected]",
        "status": "clicked"
    },
    {
        "mail_id": "225253463",
        "last_name": "Doe",
        "first_name": "Jon",
        "email": "[email protected]",
        "status": "opened"
    },
    {
        "mail_id": "849849w4",
        "last_name": "Doe",
        "first_name": "Jane",
        "email": "[email protected]",
        "status": "sent"
    }
]
?>

Result or new array as below. I am trying to achieve the below result using any array function like , array_walk_recursive or array_reduce that makes the code look beautiful and compact.

<?php
 [
    [
            "first_name": "Jon",
            "last_name": "Doe",
            "email": "[email protected]",
            "opened": 2,
            "blocked": 0,
            "hard_bounced": 0,
            "soft_bounced": 0,
            "received": 0,
            "clicked": 0
    ],
    [
            "first_name": "Jane",
            "last_name": "Doe",
            "email": "[email protected]",
            "opened": 0,
            "blocked": 0,
            "hard_bounced": 0,
            "soft_bounced": 0,
            "sent": 2,
            "clicked": 0
    ],
    [
        "first_name": "Jhon",
        "last_name": "Dwaye",
        "email": "[email protected]",
        "opened": 0,
        "blocked": 0,
        "hard_bounced": 0,
        "soft_bounced": 0,
        "sent": 0,
        "clicked": 1
    ],
]
1
  • 1
    So... Why don't you use array_reduce? Since this seems to be homework - what have you tried? Commented Jan 4, 2019 at 16:40

3 Answers 3

2

Using array_reduce

Using array_reduce is likely your best bet, as you guessed. It is sort of thinking through this as a loop, without using foreach explicitly. Here's my solution, I think this is pretty compact for what you're looking to accomplish.

$result = array_values(array_reduce($source, function($carry, $event) {
    if(!array_key_exists($event['email'], $carry)) {
        $carry[$event['email']] = [
            "first_name" => $event["first_name"],
            "last_name" => $event["last_name"],
            "email" => $event["email"],
            "opened" => 0,
            "blocked" => 0,
            "hard_bounced" => 0,
            "sent" => 0,
            "clicked" => 0
        ];
    }

    $carry[$event['email']][$event["status"]]++;

    return $carry;
}, []));

Working example: https://3v4l.org/lhlU0


Using array_map

I did take a stab at another solution, just as an exercise. It's not as clean and compact as array_reduce, but sometimes it can be worth at least considering a non-loop approach.

$result = array_map(function($email) use($source) {
    $events = array_values(array_filter($source, function($event) use($email) {
        return $event['email'] == $email;
    }));


    return [
        "first_name" => $events[0]["first_name"],
        "last_name" => $events[0]["last_name"],
        "email" => $email,
        "opened" => count(array_filter($events, function($event) { return $event["status"] == "opened"; })),
        "blocked" => count(array_filter($events, function($event) { return $event["status"] == "blocked"; })),
        "hard_bounced" => count(array_filter($events, function($event) { return $event["status"] == "hard_bounced"; })),
        "soft_bounced" => count(array_filter($events, function($event) { return $event["status"] == "soft_bounced"; })),
        "sent" => count(array_filter($events, function($event) { return $event["status"] == "sent"; })),
        "clicked" => count(array_filter($events, function($event) { return $event["status"] == "clicked"; })),
    ];
}, array_unique(array_column($source, "email")));

Working example: https://3v4l.org/KSGeX

Though I would argue that those count(array_filter(... calls should be abstracted out to a separate function:

function countEvents($events, $status) {
    return count(array_filter($events, function($event) use($status) { 
        return $event["status"] == $status; 
    }));
}

So now in the above return array you can just countEvents($events, "opened") for example. Will make it a good deal cleaner.

Sign up to request clarification or add additional context in comments.

Comments

1

I don't really understand why don't want to use foreach, the main code body is the same, something like that should do the work.

I assume that the data are in a multidimensional array and that the name of the array is $old_records;

-> With Foreach

$new_records = [];

foreach ($old_records as $old_record) {

    if(!array_key_exists($old_record["email"], $new_records)) {
        $new_records[$old_record["email"]] = [
            "opened"       => 0,
            "blocked"      => 0,
            "hard_bounced" => 0,
            "soft_bounced" => 0,
            "received"     => 0,
            "clicked"      => 0,
            "sent"         => 0,
        ];
    }

    $new_record = &$new_records[$old_record["email"]];

    $new_record["first_name"] = $old_record["first_name"];
    $new_record["last_name"] = $old_record["last_name"];
    $new_record["email"] = $old_record["email"];

    if(!array_key_exists($old_record["status"], $new_record)) {
        $new_record[$old_record["status"]] = 0;
    }

    $new_record[$old_record["status"]]++;
}

-> With array_reduce

function format($carry, $item) {

    if (empty($carry)) {
        $carry = [];
    }

    if ( ! array_key_exists($item[ "email" ], $carry)) {
        $carry[ $item[ "email" ] ] = [
            "opened"       => 0,
            "blocked"      => 0,
            "hard_bounced" => 0,
            "soft_bounced" => 0,
            "received"     => 0,
            "clicked"      => 0,
            "sent"         => 0,
        ];
    }

    $new_record = &$carry[ $item[ "email" ] ];

    $new_record[ "first_name" ] = $item[ "first_name" ];
    $new_record[ "last_name" ]  = $item[ "last_name" ];
    $new_record[ "email" ]      = $item[ "email" ];

    if ( ! array_key_exists($item[ "status" ], $new_record)) {
        $new_record[ $item[ "status" ] ] = 0;
    }

    $new_record[ $item[ "status" ] ] ++;

    return $carry;
}

array_reduce($old_records, "format");

@Note: I've used the email as a key to merge the data and set some default values for the statuses because in the example you return 0 with certain not present status.

Comments

0

My resulting key orders are slightly different:

<?php
$json =<<<JSON
[
    {
        "mail_id": "29848947",
        "last_name": "Doe",
        "first_name": "Jon",
        "email": "[email protected]",
        "status": "opened"
    },
    {
        "mail_id": "340980398",
        "last_name": "Doe",
        "first_name": "Jane",
        "email": "[email protected]",
        "status": "sent"
    },
    {
        "mail_id": "877586",
        "last_name": "Dwaye",
        "first_name": "Jhon",
        "email": "[email protected]",
        "status": "clicked"
    },
    {
        "mail_id": "225253463",
        "last_name": "Doe",
        "first_name": "Jon",
        "email": "[email protected]",
        "status": "opened"
    },
    {
        "mail_id": "849849w4",
        "last_name": "Doe",
        "first_name": "Jane",
        "email": "[email protected]",
        "status": "sent"
    }
]
JSON;

Method:

$data = json_decode($json, true);

$status_keys = [
    'opened',
    'blocked',
    'hardbouced', 
    'softbounced',
    'sent',
    'clicked'
];

$skel = array_fill_keys($status_keys, 0);

foreach($data as $item) {
    $email  = $item['email'];
    $status = $item['status'];
    unset($item['status'], $item['mail_id']);

    if(!isset($result[$email]))
        $result[$email] = array_merge($item, $skel);

    $result[$email][$status]++;
}
asort($result);
echo json_encode(array_values($result), JSON_PRETTY_PRINT);

Output:

[
    {
        "last_name": "Doe",
        "first_name": "Jane",
        "email": "[email protected]",
        "opened": 0,
        "blocked": 0,
        "hardbouced": 0,
        "softbounced": 0,
        "sent": 2,
        "clicked": 0
    },
    {
        "last_name": "Doe",
        "first_name": "Jon",
        "email": "[email protected]",
        "opened": 2,
        "blocked": 0,
        "hardbouced": 0,
        "softbounced": 0,
        "sent": 0,
        "clicked": 0
    },
    {
        "last_name": "Dwaye",
        "first_name": "Jhon",
        "email": "[email protected]",
        "opened": 0,
        "blocked": 0,
        "hardbouced": 0,
        "softbounced": 0,
        "sent": 0,
        "clicked": 1
    }
]

Comments

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.