-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": "jdoe@gmail.com",
        "status": "opened"
    },
    {
        "mail_id": "340980398",
        "last_name": "Doe",
        "first_name": "Jane",
        "email": "janedoe@gmail.com",
        "status": "sent"
    },
    {
        "mail_id": "877586",
        "last_name": "Dwaye",
        "first_name": "Jhon",
        "email": "Jhondw@yahoo.com",
        "status": "clicked"
    },
    {
        "mail_id": "225253463",
        "last_name": "Doe",
        "first_name": "Jon",
        "email": "jdoe@gmail.com",
        "status": "opened"
    },
    {
        "mail_id": "849849w4",
        "last_name": "Doe",
        "first_name": "Jane",
        "email": "janedoe@gmail.com",
        "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": "jdoe@gmail.com",
            "opened": 2,
            "blocked": 0,
            "hard_bounced": 0,
            "soft_bounced": 0,
            "received": 0,
            "clicked": 0
    ],
    [
            "first_name": "Jane",
            "last_name": "Doe",
            "email": "janedoe@gmail.com",
            "opened": 0,
            "blocked": 0,
            "hard_bounced": 0,
            "soft_bounced": 0,
            "sent": 2,
            "clicked": 0
    ],
    [
        "first_name": "Jhon",
        "last_name": "Dwaye",
        "email": "Jhondw@yahoo.com",
        "opened": 0,
        "blocked": 0,
        "hard_bounced": 0,
        "soft_bounced": 0,
        "sent": 0,
        "clicked": 1
    ],
]
Mail4Coder
  • 23
  • 6

3 Answers3

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.

jszobody
  • 28,495
  • 6
  • 61
  • 72
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.

JairSnow
  • 139
  • 1
  • 3
0

My resulting key orders are slightly different:

<?php
$json =<<<JSON
[
    {
        "mail_id": "29848947",
        "last_name": "Doe",
        "first_name": "Jon",
        "email": "jdoe@gmail.com",
        "status": "opened"
    },
    {
        "mail_id": "340980398",
        "last_name": "Doe",
        "first_name": "Jane",
        "email": "janedoe@gmail.com",
        "status": "sent"
    },
    {
        "mail_id": "877586",
        "last_name": "Dwaye",
        "first_name": "Jhon",
        "email": "Jhondw@yahoo.com",
        "status": "clicked"
    },
    {
        "mail_id": "225253463",
        "last_name": "Doe",
        "first_name": "Jon",
        "email": "jdoe@gmail.com",
        "status": "opened"
    },
    {
        "mail_id": "849849w4",
        "last_name": "Doe",
        "first_name": "Jane",
        "email": "janedoe@gmail.com",
        "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": "janedoe@gmail.com",
        "opened": 0,
        "blocked": 0,
        "hardbouced": 0,
        "softbounced": 0,
        "sent": 2,
        "clicked": 0
    },
    {
        "last_name": "Doe",
        "first_name": "Jon",
        "email": "jdoe@gmail.com",
        "opened": 2,
        "blocked": 0,
        "hardbouced": 0,
        "softbounced": 0,
        "sent": 0,
        "clicked": 0
    },
    {
        "last_name": "Dwaye",
        "first_name": "Jhon",
        "email": "Jhondw@yahoo.com",
        "opened": 0,
        "blocked": 0,
        "hardbouced": 0,
        "softbounced": 0,
        "sent": 0,
        "clicked": 1
    }
]
Progrock
  • 7,373
  • 1
  • 19
  • 25