Conditionally sorting for an even distribution of records

Suppose I have 10 Eloquent model records in an Item model such as this (represented as JSON for simplicity):

[
  {id: 1, name: 'Item 1', featured: false},
  {id: 2, name: 'Item 2', featured: true},
  {id: 3, name: 'Item 3', featured: false},
  {id: 4, name: 'Item 4', featured: false},
  {id: 5, name: 'Item 5', featured: false},
  {id: 6, name: 'Item 6', featured: false},
  {id: 7, name: 'Item 7', featured: true},
  {id: 8, name: 'Item 8', featured: false},
  {id: 9, name: 'Item 9', featured: false},
  {id: 10, name: 'Item 10', featured: true},
]

I want to organize this list first by id ascending then distributing the records such that every two records where featured = false is followed by one record where featured = true.

The result would look like this:

[
  {id: 1, name: 'Item 1', featured: false},
  {id: 3, name: 'Item 3', featured: false},
  {id: 2, name: 'Item 2', featured: true},
  {id: 4, name: 'Item 4', featured: false},
  {id: 5, name: 'Item 5', featured: false},
  {id: 7, name: 'Item 7', featured: true},
  {id: 6, name: 'Item 6', featured: false},
  {id: 8, name: 'Item 8', featured: false},
  {id: 10, name: 'Item 10', featured: true},
  {id: 9, name: 'Item 9', featured: false},
]

How would I do this using an Eloquent collection or query?

1 answer

  • answered 2020-11-25 07:26 DigitalDrifter

    Here's a working solution using collections (I'm sure it can be cleaned up quite a bit):

    $result = collect($items)->sortBy('id')->partition(function ($i) {
      return $i['featured'];
    })->pipe(function ($groups) {
       return $groups->last()->chunk(2)->reduce(function ($carry, $item) use ($groups) {
          $item->each(function ($i) use ($carry) {
             $carry->push($i);
          });
          
          if ($groups->first()->isNotEmpty()) {
             $carry->push($groups->first()->shift());
          }
    
          return $carry;
       }, collect());
    });
    

    See it in action here.