Introduce "stable sort by weight" functions to sort arrays (and objects)

Created on 28 June 2016, over 8 years ago
Updated 30 January 2023, almost 2 years ago

Follow-up / Split-off from πŸ› uasort() does NOT preserve sort order, but core assumes so (Element::children, css sort, ...) Needs work

Use cases

  1. (most common *) An array of arrays, where each item may have a weight in $item[$weight_key].
  2. (less common) An array of arrays, where each item may have multiple weights in $item[$weight_key_0], $item[$weight_key_1], etc.
  3. (less common) An array of objects, where each object may have a weight method e.g. $weight = $item->getWeight().
  4. (less common) An array of items of whichever type, with a callback to determine a weight for each item. E.g. $weight = $weight_callback($item);

(*) The most common example for the first case is render arrays with $item['#weight']. However, these should not be dealt with here, because there are special peculiarities we don't want to worry about. Esp the distinction of element children vs element properties.
#2522712-26: Simplify Element::children() stable sort. β†’

Existing implementations, previous attempts

Currently there are different sort implementations for different instances of these use cases, often with uasort() and a comparison function.
One problem with this is that uasort() is not stable.
The other problem is that comparison functions are slow. Really slow. Compared to other solutions.
Benchmark results can be found in #2466097-91: uasort() does NOT preserve sort order, but core assumes so (Element::children, css sort, ...) β†’ ff.

Scope of this issue: Dedicated sort-by-weight

The goal of this issue is to provide dedicated reusable functions (static methods) for stable sorting based on item weights.
These should be simple, fast, and generic.
Example implementations can be found in https://github.com/donquixote/stable/tree/drupal-org-2466097-95-sort-keys

This issue is only about introducing the new functions + unit tests.
Actually using them should happen in another issue.
This way we keep the git history clean, and the issue readable, avoiding distractions.

This can happen in 8.1.x, because it will not BC-break anything.

DX questions

How do we want to distinguish asort() vs sort() ? (Preserve keys or not)
Distinct methods? Or an additional parameter?

How can the caller enable or disable (relatively expensive) checks such as is_array(), or possibly method_exists() etc? Currently in the proposed methods, the caller can use e.g. sortMixedByWeightKey() to force an is_array() check, or sortArraysByWeightKey() to omit such a check. But this brings a clutter of different methods.

Do we want to return a sorted array, or modify the first parameter by-reference, like the native PHP sort functions?

Do we want an options to make the sort non-stable? Just to get rid of comparison callbacks..

Do we want a fluid API?
This would make some of the API design questions above easier. But it would result in a bigger library than what we are ready to put in core.

Fluid API? Injectable sorts?

If we want a fluid API (which is not decided yet), there are two options:

// First option:
$items_sorted = StableSort::begin()
  ->byKey('weight', SORT_NUMERIC)
  ->sort($items_unsorted);

// Second option:
$items_sorted = StableSort::begin($items_unsorted)
  ->byKey('weight', SORT_NUMERIC)
  ->sort();

Only the first option leads us to reusable, injectable sorts. E.g.

$sort = StableSort::begin()
  ->byKey('weight', SORT_NUMERIC);

foreach ($items_all as $k => $items) {
  $items_all[$k] = $sort->sort($items);
}

Why a new issue?

From #2466097-97:

This way, newcomers or people who want to understand the git history don't have to read 80 comments about an approach that was already discarded.

πŸ“Œ Task
Status

Needs work

Version

10.1 ✨

Component
BaseΒ  β†’

Last updated about 3 hours ago

Created by

πŸ‡©πŸ‡ͺGermany donquixote

Live updates comments and jobs are added and updated live.
  • needs profiling

    It may affect performance, and thus requires in-depth technical reviews and profiling.

Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • The Needs Review Queue Bot β†’ tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

    Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

    Consult the Drupal Contributor Guide β†’ to find step-by-step guides for working with issues.

Production build 0.71.5 2024