How do I sort a collection in EK9?

← Collections and Data Structures · Ref: Q120

EK9 sorts collections through stream pipelines using the | sort operation. Natural sorting uses the <=> operator defined on the element type. Custom sorting uses a comparator function.

SORT IN STREAMS

Sorting is a pipeline stage, not a method call on the collection:

  sorted <- cat items | sort | collect as List of String

This creates a new sorted list. The original list is unchanged.

NATURAL SORT

| sort uses the <=> (comparison) operator defined on the element type:

  cat names | sort > stdout

For built-in types (String, Integer, Float, Date), <=> is already defined. For custom types, you must implement operator <=>.

SORT BY COMPARATOR

| sort by function uses a custom comparator:

  cat books | sort by comparingTitle | collect as List of Book

The comparator is a pure function taking two arguments and returning Integer (-1, 0, or 1).

IMPLEMENTING COMPARISON FOR CUSTOM TYPES

Define operator <=> on your record or class to enable natural sort:

  operator <=> as pure
    -> other as Product
    <- rtn as Integer: name <=> other.name

COMPARATOR AS DYNAMIC FUNCTION

Create comparators inline for one-off sorting:

  comparingPrice <- () extends Comparator of Product as pure function
    r:=? t1.price() <=> t2.price()

This creates a function that compares by price field.

See Q45 for List basics. See Q89 for basic stream pipelines. See Q96 for operator overloading. See Q121 for PriorityQueue (maintained order). See Q125 for head/tail/skip to limit sorted results.

Example

defines module qa.collections.sort

  defines class

    Product
      name as String?
      price as Float?

      default private Product() as pure

      Product() as pure
        ->
          name as String
          price as Float
        this.name :=? name
        this.price :=? price

      name() as pure
        <- rtn as String: String(name)

      price() as pure
        <- rtn as Float: Float(price)

      operator <=> as pure
        -> other as Product
        <- rtn as Integer: name <=> other.name

      operator == as pure
        -> other as Product
        <- rtn as Boolean: name == other.name and price == other.price

      operator $ as pure
        <- rtn as String: `${name} \$${price}`

      operator #? as pure
        <- rtn as Integer: #?name

      override operator ? as pure
        <- rtn as Boolean: name? and price?

  defines function

    comparingPrice() as pure
      ->
        t1 as Product
        t2 as Product
      <-
        rtn as Integer: t1.price() <=> t2.price()

  defines program

    SortCollectionDemo()
      stdout <- Stdout()

      products <- [
        Product("Banana", 1.20),
        Product("Apple", 0.90),
        Product("Cherry", 3.50),
        Product("Date", 5.00)
        ]

      // === NATURAL SORT ===

      // Sort by name (uses operator <=> defined on Product)
      byName <- cat products | sort | collect as List of Product
      stdout.println(`By name: ${byName}`)

      // === SORT BY COMPARATOR ===

      // Sort by price using named comparator
      byPrice <- cat products | sort by comparingPrice | collect as List of Product
      stdout.println(`By price: ${byPrice}`)

      // === SORT WITH DYNAMIC COMPARATOR ===

      // Inline comparator for one-off sort
      reverseByName <- () extends Comparator of Product as pure function
        r:=? t2.name() <=> t1.name()

      descending <- cat products | sort by reverseByName | collect as List of Product
      stdout.println(`Descending: ${descending}`)

Common mistakes

E50060 — EK9 sorts through stream pipelines using '| sort', not method calls like .sort(). Sorting is a pipeline stage, not a method on the collection. See ek9 -h E50060 for details.

Incorrect:

byName <- products.sort()

Correct:

byName <- cat products | sort | collect as List of Product

E07235 — A class with fields must define operator ? for tri-state semantics. Without it, guard expressions and safe access patterns cannot inspect field state. See ek9 -h E07235 for details.

Incorrect:

      //no isSet operator

Correct:

      override operator ? as pure
        <- rtn as Boolean: name? and price?

E50060 — EK9 does not have toString(). Use the $ operator or string interpolation. See ek9 -h E50060 for details.

Incorrect:

stdout.println(byName.toString())

Correct:

stdout.println(`By name: ${byName}`)

E50001 — EK9 does not have Java-style Collections utility classes or method references. 'Collections' is not a known type. Use a named comparator function that takes two parameters and returns Integer. See ek9 -h E50001 for details.

Incorrect:

cat products | sort by Collections.comparing(Product.price) | collect as List of Product

Correct:

cat products | sort by comparingPrice | collect as List of Product
Other ways to ask this
  • How do I order a list in EK9?
  • How does the sort pipeline operation work in EK9?
  • How do I sort by a custom comparator in EK9?

Coming from another language?

Java: Collections.sort() or list.stream().sorted(Comparator.comparing()). Python: sorted(list, key=lambda x: x.field). JavaScript: array.sort((a, b) => a.field - b.field). Rust: vec.sort() or vec.sort_by_key(). Go: sort.Slice() with less function. EK9: cat list | sort or cat list | sort by comparator in a pipeline, comparator defined as pure function or dynamic function extending Comparator of T.

Keywords: order, natural, ascending, comparison, data-structure, pipeline, sort, descending, collection, comparator, stream, list, custom