DEV Community

Cover image for Sorting Laravel Collection in a custom order
Sharan
Sharan

Posted on

Sorting Laravel Collection in a custom order

Currently, Laravel doesn't provide an option to sort the collection in a custom order.

We found some methods to sort the collection.

Method 1 :

We found an algorithm to sort the collection in custom order.

The algorithm takes an array of input IDs that need to be sorted as the first parameter and an array consisting of elements the original array needs to be sorted in.

The algorithm then returns the sorted array of IDs. We then have to push the original collection in the sorted order.

class CustomOrderSorting
{
    protected $A1, $A2, $m, $n;

    public function __construct($A1, $A2)
    {
        $this->A1 = &$A1;
        $this->A2 = &$A2;
        $this->m = count($A1);
        $this->n = count($A2);
    }

    /**
     * @param $arr
     * @param $low
     * @param $high
     * @param $x
     * @param $n
     * @return int
     */
    protected function first(&$arr, $low, $high, $x, $n)
    {
        if ($high >= $low) {
            $mid = (int)($low + ($high - $low) / 2);
            if (($mid == 0 || $x > $arr[$mid - 1]) &&
                $arr[$mid] == $x)
                return $mid;
            if ($x > $arr[$mid])
                return $this->first($arr, ($mid + 1), $high, $x, $n);
            return $this->first($arr, $low, ($mid - 1), $x, $n);
        }
        return -1;
    }

    /**
     * Sort A1[0..m-1] according to the order
     * defined by A2[0..n-1].
     * @param $A1
     * @param $A2
     * @param $m
     * @param $n
     * @return void
     */
    public function sortAccording(): array
    {
        // The temp array is used to store a copy
        // of A1[] and visited[] is used mark the
        // visited elements in temp[].
        $temp = array_fill(0, $this->m, NULL);
        $visited = array_fill(0, $this->m, NULL);
        for ($i = 0; $i < $this->m; $i++) {
            $temp[$i] = $this->A1[$i];
            $visited[$i] = 0;
        }

        // Sort elements in temp
        sort($temp);

        $ind = 0; // for index of output which is sorted A1[]

        // Consider all elements of A2[], find
        // them in temp[] and copy to A1[] in order.
        for ($i = 0; $i < $this->n; $i++) {
            // Find index of the first occurrence
            // of A2[i] in temp
            $f = $this->first($temp, 0, $this->m - 1, $this->A2[$i], $this->m);

            // If not present, no need to proceed
            if ($f == -1) continue;

            // Copy all occurrences of A2[i] to A1[]
            for ($j = $f; ($j < $this->m &&
                $temp[$j] == $this->A2[$i]); $j++) {
                $this->A1[$ind++] = $temp[$j];
                $visited[$j] = 1;
            }
        }

        // Now copy all items of temp[] which
        // are not present in A2[]
        for ($i = 0; $i < $this->m; $i++) {
            if ($visited[$i] == 0)
                $this->A1[$ind++] = $temp[$i];
        }
        return $this->A1;
    }
}
Enter fullscreen mode Exit fullscreen mode

How to use the Custom order sorting algorithm :
For demonstration, I will be using the User model and retrieving all the entries from the table.

$sortOrder = [187, 411, 473, 492, 561, 834, 743, 123];

$users = User::get();

$userKeyByID = $users->keyBy('id'); // Set the individual elements key by the id field
$usersIDKeys = $userKeyByID->keys()->toArray(); // Get the keys (which is id)

$customSortOrder = (new CustomOrderSorting($usersIDKeys, $sortOrder))->sortAccording();

$sortedUsers = collect();

foreach ($customSortOrder as $id) {
    if (isset($userKeyByID[$id])) {
        $sortedUsers->push($userKeyByID[$id]);
    }
}
Enter fullscreen mode Exit fullscreen mode

Method 2 :

Laravel doesn't provide a way to custom sort the collection but it does provide a way to sort the collection in any order using

$collection->sortBy($callback, $options = SORT_REGULAR, $descending = false)
Enter fullscreen mode Exit fullscreen mode

We can use this method to sort the collection in a custom order.

$users = User::get();

$sortOrder = array_flip([187, 411, 473, 492, 561, 834, 743, 123]);

$sortedUser = $users->sortBy(function ($user, $key) use ($sortOrder) {
    return $sortOrder[$user->id] ?? 999999;
})->values();
Enter fullscreen mode Exit fullscreen mode

In this method, we will return the position of the element to sort the order. If the element's id is not in the sorting array, we will return a large value to denote that the value should be listed at last.

Conclusion :

We ran both the methods and took the average time taken to process 500 records.

Results :

Algorithm (method 1) took around 0.02082 seconds.
SortBy (method 2) took around 0.00938 seconds.

Algorithm took nearly 121.96% more time than the conventional sortBy() method.

Top comments (0)