Alex Bailey
Alex Bailey

Reputation: 1512

Which PHP Array Function to Use?

I'm getting super confused between all the different array functions in PHP and I have no idea which is best to use in my scenario.

I have one array that lists user sign ups and one array which lists subscriptions for those users with the date the user subscribed. The user can have multiple subscriptions or not at all. An example would be:

$users = [
    ['user_id' => 1, 'sign_up_date' => '2020-01-01 00:00:00'],
    ['user_id' => 2, 'sign_up_date' => '2020-01-02 00:00:00'],
    ['user_id' => 3, 'sign_up_date' => '2020-01-03 00:00:00'],
    ...
];

$subscriptions = [
    ['user_id' => 1, 'subscription_category' => 'abc', 'sign_up_date' => '2020-01-01 00:01:00'],
    ['user_id' => 1, 'subscription_category' => 'def', 'sign_up_date' => '2020-01-01 00:02:00'],
    ['user_id' => 1, 'subscription_category' => 'ghi', 'sign_up_date' => '2020-01-02 00:03:00'],
    ['user_id' => 2, 'subscription_category' => 'jkl', 'sign_up_date' => '2020-01-02 00:01:00'],
    ['user_id' => 2, 'subscription_category' => 'mno', 'sign_up_date' => '2020-01-03 00:02:00'],
    ...
];

What I'm attempting to find is which users subscribed to any category on their sign up date. So in this case I want to find the number of items in:

$usersWhoSubscribedOnSignUpDate = [1, 2];

What I could do is something like:

$results = [];
foreach ($users as $user) {
    foreach ($subscriptions as $subscription) {
        $signUpDate = date('Y-m-d', strtotime($user['sign_up_date']));
        $subscriptionDate = date('Y-m-d', strtotime($subscription['sign_up_date']));

        if ($subscription['user_id'] === $subscription['user_id'] && $signUpDate === $subscriptionDate) {
            $results[] = subscription['user_id'];
        }
    }
}
$results = array_unique($results);

But that's not very elegant in my eyes and I'm convinced that one of the many PHP array functions could simplify this process somehow.

array_intersect doesn't seem suitable because it doesn't appear to work with multi-level arrays like this.

array_map doesn't seem suitable because it doesn't have the ability to compare two arrays.

array_uintersect_assoc seems like it could be an option however I can't understand what the "additional index check" means or how to sort the data. I believe that the callback function needs to return a comparison between two objects so perhaps this could be sorted by timestamp of the sign_up_date property?

Upvotes: 1

Views: 72

Answers (2)

Stephen R
Stephen R

Reputation: 3917

The first think I would do is modify your arrays so the users are identified by keys, (that's the first two lines). The third line reduces the subscription dates to the earliest for each user, then the fourth finds places where the sign up and earliest subscription dates match.

foreach( $users as $u ) { $signups[$u['user_id']] = date('Y-m-d', strtotime($u['sign_up_date']) ); }
foreach( $subscriptions as $s ) { $subs[$s['user_id']][] = date('Y-m-d', strtotime($s['sign_up_date']) ); }
foreach( $subs as $key => $value ) { $subs[$key] = min( $subs[$key] ); }
$results = array_keys( array_intersect_assoc( $signups, $subs ) );

Upvotes: 0

Alex Howansky
Alex Howansky

Reputation: 53591

You can do this in one iteration of $users and one iteration of $subscriptions, rather than one iteration of $subscriptions per row in $users. This could represent a significant performance improvement as the number of rows increase.

First, build an associative array of user signup dates indexed by user_id, so that you do not need to iterate over it every time you need to look up a date:

$userDates = [];
foreach ($users as $user) {
    $userDates[$user['user_id']] = date('Y-m-d', strtotime($user['sign_up_date']);
}

This will give you:

Array
(
    [1] => 2020-01-01
    [2] => 2020-01-02
    [3] => 2020-01-03
)

Then just iterate over the subscriptions once, looking up that user's signup date as you go:

foreach ($subscriptions as $subscription) {
    if (
        date('Y-m-d', strtotime($subscription['sign_up_date'])) ===
        $userDates[$subscription['user_id']]
    ) {
        ...
    }
}

Or perhaps something like:

array_unique(array_filter(
    $subscriptions,
    function ($subscription) use ($userDates) {
        return date('Y-m-d', strtotime($subscription['sign_up_date'])) ===
            $userDates[$subscription['user_id']];
    }
));

Upvotes: 1

Related Questions