Card 03/ 08
Sorting Orders by Date, Then by Customer
Six orders sorted twice: a stable second sort keeps each customer's orders by date, and a composite key does it in one pass.
Sorting by two keys with two sorts works only when the second sort is stable, meaning items with equal keys keep the order they went in. This example shows that idea on six shop orders, which a report must list by customer and, within each customer, by date.
The orders are 1001 from Chen on 2 March, 1002 from Abara on 1 March, 1003 from Chen on 1 March, 1004 from Bose on 2 March, 1005 from Abara on 2 March and 1006 from Bose on 1 March. Sorting them by customer alone groups them, but leaves each group in whatever order the orders arrived: a run printed Bose's 2 March order before the 1 March one. The fix is to sort by the minor key, the date, first, and then by the major key, the customer, with a stable sort.
| Position | As placed | After sorting by date | After then sorting by customer |
|---|---|---|---|
| 1 | 1001 Chen, 2 Mar | 1002 Abara, 1 Mar | 1002 Abara, 1 Mar |
| 2 | 1002 Abara, 1 Mar | 1003 Chen, 1 Mar | 1005 Abara, 2 Mar |
| 3 | 1003 Chen, 1 Mar | 1006 Bose, 1 Mar | 1006 Bose, 1 Mar |
| 4 | 1004 Bose, 2 Mar | 1001 Chen, 2 Mar | 1004 Bose, 2 Mar |
| 5 | 1005 Abara, 2 Mar | 1004 Bose, 2 Mar | 1003 Chen, 1 Mar |
| 6 | 1006 Bose, 1 Mar | 1005 Abara, 2 Mar | 1001 Chen, 2 Mar |
The second sort compares customers only, so Bose's two orders tie, and a stable sort keeps them in the order the first sort left them: 1006 on 1 March, then 1004 on 2 March. An unstable sort could put them either way round, and the date order the first sort built would be lost for that customer.
The listings below do the same in each language with the built-in sort, which is stable for objects in all three, and then get the same result in a single sort with a composite key: a key made of the customer and then the date, compared customer first.
Sorting six orders by date, then stably by customer, and by a composite key
from operator import attrgetter
from typing import NamedTuple
class Order(NamedTuple):
number: int
customer: str
date: str
def show(orders: list[Order]) -> str:
return " ".join(f"{o.number}:{o.customer}:{o.date[-2:]}" for o in orders)
orders = [
Order(1001, "Chen", "2026-03-02"),
Order(1002, "Abara", "2026-03-01"),
Order(1003, "Chen", "2026-03-01"),
Order(1004, "Bose", "2026-03-02"),
Order(1005, "Abara", "2026-03-02"),
Order(1006, "Bose", "2026-03-01"),
]
print("customer only:", show(sorted(orders, key=attrgetter("customer"))))
by_date = sorted(orders, key=attrgetter("date"))
print("by date:", show(by_date))
print("then by customer:", show(sorted(by_date, key=attrgetter("customer"))))
print("composite key:", show(sorted(orders, key=lambda o: (o.customer, o.date))))customer only: 1002:Abara:01 1005:Abara:02 1004:Bose:02 1006:Bose:01 1001:Chen:02 1003:Chen:01
by date: 1002:Abara:01 1003:Chen:01 1006:Bose:01 1001:Chen:02 1004:Bose:02 1005:Abara:02
then by customer: 1002:Abara:01 1005:Abara:02 1006:Bose:01 1004:Bose:02 1003:Chen:01 1001:Chen:02
composite key: 1002:Abara:01 1005:Abara:02 1006:Bose:01 1004:Bose:02 1003:Chen:01 1001:Chen:02import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class OrdersCode {
record Order(int number, String customer, String date) {}
static String show(List<Order> orders) {
return orders.stream()
.map(o -> o.number() + ":" + o.customer() + ":" + o.date().substring(8))
.collect(Collectors.joining(" "));
}
public static void main(String[] args) {
List<Order> orders = List.of(
new Order(1001, "Chen", "2026-03-02"),
new Order(1002, "Abara", "2026-03-01"),
new Order(1003, "Chen", "2026-03-01"),
new Order(1004, "Bose", "2026-03-02"),
new Order(1005, "Abara", "2026-03-02"),
new Order(1006, "Bose", "2026-03-01"));
List<Order> customerOnly = new ArrayList<>(orders);
customerOnly.sort(Comparator.comparing(Order::customer));
System.out.println("customer only: " + show(customerOnly));
List<Order> sorted = new ArrayList<>(orders);
sorted.sort(Comparator.comparing(Order::date));
System.out.println("by date: " + show(sorted));
sorted.sort(Comparator.comparing(Order::customer));
System.out.println("then by customer: " + show(sorted));
List<Order> composite = new ArrayList<>(orders);
composite.sort(Comparator.comparing(Order::customer).thenComparing(Order::date));
System.out.println("composite key: " + show(composite));
}
}customer only: 1002:Abara:01 1005:Abara:02 1004:Bose:02 1006:Bose:01 1001:Chen:02 1003:Chen:01
by date: 1002:Abara:01 1003:Chen:01 1006:Bose:01 1001:Chen:02 1004:Bose:02 1005:Abara:02
then by customer: 1002:Abara:01 1005:Abara:02 1006:Bose:01 1004:Bose:02 1003:Chen:01 1001:Chen:02
composite key: 1002:Abara:01 1005:Abara:02 1006:Bose:01 1004:Bose:02 1003:Chen:01 1001:Chen:02interface Order {
number: number;
customer: string;
date: string;
}
const byField =
(field: "customer" | "date") =>
(a: Order, b: Order): number =>
a[field] < b[field] ? -1 : a[field] > b[field] ? 1 : 0;
function show(orders: Order[]): string {
return orders.map((o) => `${o.number}:${o.customer}:${o.date.slice(-2)}`).join(" ");
}
const orders: Order[] = [
{ number: 1001, customer: "Chen", date: "2026-03-02" },
{ number: 1002, customer: "Abara", date: "2026-03-01" },
{ number: 1003, customer: "Chen", date: "2026-03-01" },
{ number: 1004, customer: "Bose", date: "2026-03-02" },
{ number: 1005, customer: "Abara", date: "2026-03-02" },
{ number: 1006, customer: "Bose", date: "2026-03-01" },
];
console.log(`customer only: ${show([...orders].sort(byField("customer")))}`);
const byDate = [...orders].sort(byField("date"));
console.log(`by date: ${show(byDate)}`);
console.log(`then by customer: ${show([...byDate].sort(byField("customer")))}`);
console.log(`composite key: ${show([...orders].sort((a, b) => byField("customer")(a, b) || byField("date")(a, b)))}`);customer only: 1002:Abara:01 1005:Abara:02 1004:Bose:02 1006:Bose:01 1001:Chen:02 1003:Chen:01
by date: 1002:Abara:01 1003:Chen:01 1006:Bose:01 1001:Chen:02 1004:Bose:02 1005:Abara:02
then by customer: 1002:Abara:01 1005:Abara:02 1006:Bose:01 1004:Bose:02 1003:Chen:01 1001:Chen:02
composite key: 1002:Abara:01 1005:Abara:02 1006:Bose:01 1004:Bose:02 1003:Chen:01 1001:Chen:02All three printed the same four lines. The highlighted lines are the date sort, the customer sort that follows it, and the composite key. In Python the composite key is a tuple, which Python compares item by item; in Java it is Comparator.comparing(...).thenComparing(...); in TypeScript it is a comparator that falls through to the date when the customers compare equal, because 0 || x is x.
Prefer the composite key when you write both sorts at once, since it sorts once and does not depend on stability. Sort twice when the data arrives already sorted by the minor key, as a log arrives sorted by time, or when the user picks the major key at run time by clicking a column; then the second sort must be stable, and heapsort or quicksort would not do.
← → move through sections and cards