Package Exports
- @moon7/sort
Readme
๐ @moon7/sort
A lightweight, functional utility library providing composable sorting functions for arrays and collections in JavaScript and TypeScript applications.
โจ Features
- ๐งฑ Basic Comparators - Ascending, descending, and random sorting functions
- ๐ท๏ธ Property-based Sorting - Sort by specific object properties
- ๐งฉ Composable API - Combine multiple sort criteria effortlessly
- ๐งถ Natural Sorting - Intelligent string sorting with proper handling of numbers
- โ๏ธ Chaining - Order, group, and prioritize items with composable functions
๐ง Motivation
Sorting functions in JavaScript compare two values and return a numeric result: negative (-1) when the first value is less than the second, positive (1) when greater, and zero (0) when they're equal.
// A sorting function's type signature
type Comparator<T> = (a: T, b: T) => number;
// Common imperative sorting pattern
numberList.sort((a, b) => a - b);While simple cases are straightforward, complex sorting logic can quickly become unwieldy when written imperatively. This library takes a functional approach, providing composable building blocks that you can combine to create sophisticated sorting behaviors with minimal code.
import { ascending, descending } from "@moon7/sort";
// Simple sorting using ascending/descending comparators
list.sort(ascending);
// How it works:
// const ascending: Comparator<T> = (a, b) => a === b ? 0 : a < b ? -1 : 1;
// const descending: Comparator<T> = (a, b) => a === b ? 0 : a < b ? 1 : -1;Most functions in this library are higher-order functions - they accept other functions as arguments and return new functions, enabling powerful composition patterns.
import { by, descending, naturally, flip } from "@moon7/sort";
// Sort by name, ascending (ascending is default)
list.sort(by(x => x.name));
// Sort by name, descending
list.sort(by(x => x.name, descending));
// Sort by name, using natural string sorting
list.sort(by(x => x.name, naturally));
// Sort by name, using natural string sorting, descending
list.sort(by(x => x.name, flip(naturally)));
// How it works:
// `by` takes a mapping function and an optional comparator, returning a new comparator
// const by = (map, cmp: Comparator<T> = ascending): Comparator<T> => (a, b) => cmp(map(a), map(b));Here's an example where you can combine multiple sorting criteria. Notice how this reads like a clear declaration of your sorting requirements.
import { by, order, naturally, descending } from "@moon7/sort";
// Sort by name naturally, then by age descending, then by lastLogin
list.sort(
order(
by(x => x.name, naturally),
by(x => x.age, descending),
by(x => x.lastLogin),
)
);
// How it works:
// `order` takes multiple comparators and returns a new comparator that applies them in sequenceTraditional imperative approach would require nested if statements or complex logic. With functional composition, we can express the sorting intent declaratively.
๐ฆ Installation
# Using npm
npm install @moon7/sort
# Using yarn
yarn add @moon7/sort
# Using pnpm
pnpm add @moon7/sort๐ Usage
๐ Basic Sorting
import { ascending, descending, preserve, dir, Direction, random, randomly } from '@moon7/sort';
// Sort an array in ascending order
const numbers = [3, 1, 4, 2];
numbers.sort(ascending);
// [1, 2, 3, 4]
// Sort in descending order
numbers.sort(descending);
// [4, 3, 2, 1]
// Sort using a direction enum
numbers.sort(dir(Direction.Ascending)); // ascending
numbers.sort(dir(Direction.Descending)); // descending
// Sort using any truthy values
numbers.sort(dir(true)); // ascending
numbers.sort(dir(0)); // descending
// Sort in random order
// Note: this has bias, not for statistical applications
numbers.sort(random(0.5));
// Same as above, with default probability threshold
numbers.sort(randomly);
// Sort using the identity function, which does nothing
numbers.sort(preserve);
// Sort using the inverse identity function, which reverses the array
numbers.sort(reverse);โ ๏ธ Note: The
random()andrandomlyfunctions produce biased results and are not suitable for statistical or cryptographic applications. For proper random shuffling, use the Fisher-Yates algorithm instead.
The preserve comparator always returns 1, which maintains the original order when used with JavaScript's Array.sort(). It serves as an "identity function" for comparators - useful when working with higher-order functions that require a comparator parameter, but you want to maintain the original order.
The reverse comparator always returns -1, which reverses the original order when used with Array.sort(). This provides a simple way to reverse an array without changing its relative ordering logic otherwise.
Check out the practical examples in the Advanced Sorting section below to see these in action.
๐ Sorting Objects by Properties
import { by, order, descending } from '@moon7/sort';
const people = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
{ name: 'Charlie', age: 30 }
];
// Sort by age (ascending by default)
people.sort(by(p => p.age));
// [
// { name: 'Bob', age: 25 },
// { name: 'Alice', age: 30 },
// { name: 'Charlie', age: 30 }
// ]
// Sort by age descending
people.sort(by(p => p.age, descending));
// Sort by age, then by name descending
people.sort(order(
by(p => p.age),
by(p => p.name, descending)
));
// [
// { name: 'Bob', age: 25 },
// { name: 'Charlie', age: 30 },
// { name: 'Alice', age: 30 }
// ]๐ Natural Sorting
import { natural, naturally, by, Sensitivity } from '@moon7/sort';
const versions = ['v1.10', 'v1.2', 'v1.1'];
// Sort with natural comparison (1.2 comes before 1.10)
versions.sort(natural());
// ['v1.1', 'v1.2', 'v1.10']
// Using pre-configured naturally constant (same as natural() with default settings)
versions.sort(naturally);
// ['v1.1', 'v1.2', 'v1.10']
// Sort strings differently based on their case sensitivity
const names = ['alice', 'Alice', 'bob', 'Bob'];
names.sort(natural(Sensitivity.Case));
// ['alice', 'Alice', 'bob', 'Bob']
// Sort objects with string properties using natural sort
const files = [
{ name: 'file10.txt' },
{ name: 'file2.txt' }
];
files.sort(by(f => f.name, naturally));
// [
// { name: 'file2.txt' },
// { name: 'file10.txt' }
// ]โ๏ธ Advanced Sorting
import { where, nullable, group, flip, conditional, preserve } from '@moon7/sort';
// Sort active items first, then by name
const items = [
{ name: 'Task 1', active: false },
{ name: 'Task 2', active: true },
{ name: 'Task 3', active: false },
{ name: 'Task 4', active: true },
];
items.sort(where(x => x.active, by(x => x.name)));
// [
// { name: 'Task 2', active: true },
// { name: 'Task 4', active: true },
// { name: 'Task 1', active: false },
// { name: 'Task 3', active: false },
// ]
// Preserve original order when sorting
const nums = [3, 1, 4, 2];
nums.sort(preserve);
// [3, 1, 4, 2] (unchanged)
// Reverse original order when sorting
nums.sort(reverse);
// [2, 4, 1, 3] (reversed)
// Group by category, but preserve original order within each group
const categoryItems = [
{ id: 1, category: 'A' },
{ id: 2, category: 'B' },
{ id: 3, category: 'A' },
];
categoryItems.sort(group(item => item.category, ascending, preserve));
// [
// { id: 1, category: 'A' },
// { id: 3, category: 'A' },
// { id: 2, category: 'B' },
// ]
// Sort with null values first
const products = [
{ name: 'Product A', price: 10 },
{ name: 'Product B', price: null }
];
products.sort(nullable(p => p.price));
// [
// { name: 'Product B', price: null },
// { name: 'Product A', price: 10 }
// ]
// Group items by status, then sort by date within groups
const tasks = [
{ status: 'pending', created: new Date(2023, 1, 1) },
{ status: 'active', created: new Date(2023, 2, 1) },
{ status: 'active', created: new Date(2023, 1, 15) }
];
tasks.sort(group(
x => x.status,
// active at the top, archived at the bottom
by(status => ['active', 'pending', 'archived'].indexOf(status)),
// within each group, sort by created
by(x => x.created)
));
// [
// { status: 'active', created: new Date(2023, 1, 15) },
// { status: 'active', created: new Date(2023, 2, 1) },
// { status: 'pending', created: new Date(2023, 1, 1) }
// ]
// Conditional sorting
const numbers = [-5, -2, 3, 1];
numbers.sort(conditional(
(a, b) => a < 0 && b < 0, // If both numbers are negative
descending, // Sort negative numbers in descending order
ascending // Sort other numbers in ascending order
));
// [-2, -5, 1, 3]๐ API Reference
The library provides these key functions:
| API | Description |
|---|---|
| ๐ Core | |
sort(items, cmp?) |
Creates a sorted copy of an iterable |
Direction.Ascending |
Enum value representing ascending sort order |
Direction.Descending |
Enum value representing descending sort order |
Sensitivity.Base |
Enum value for different bases unequal, cases/accents equal |
Sensitivity.Accent |
Enum value for accents/bases unequal, cases equal |
Sensitivity.Case |
Enum value for cases/bases unequal, accents equal |
Sensitivity.Variant |
Enum value for all variations considered unequal |
| ๐งฑ Basic Comparators | |
ascending |
Compares values in ascending order |
descending |
Compares values in descending order |
preserve |
Identity comparator that preserves original order |
reverse |
Comparator that reverses the original order |
dir(isAscending) |
Creates a comparator for a specific direction |
| ๐ฒ Shuffle Comparators | |
random(p) |
Creates a comparator that sorts randomly with given probability |
randomly |
Pre-configured random sort comparator with p=0.5 |
| ๐งถ String Comparators | |
natural(sensitivity?) |
Creates a comparator for natural string sorting |
naturally |
Pre-configured natural sort comparator with default settings |
| ๐งฉ Complex Comparators | |
by(map, cmp?) |
Creates a comparator based on a property or derived value |
order(...fns) |
Combines multiple comparators in sequence |
where(predicate, cmp?) |
Creates a comparator that prioritizes items matching a predicate |
nullable(get, cmp?) |
Creates a comparator that prioritizes null/undefined values |
group(selector, groupOrder?, itemOrder?) |
Groups items and orders both groups and items within groups |
conditional(condition, ifTrue, ifFalse) |
Selects between comparators based on a condition |
flip(fn, ignore?) |
Flips the result of another comparator |
Note that all comparators are functions in the form of (a, b) => number, which is omitted in the table above for brevity. For example, ascending is actually a function ascending(a, b).
Likewise, by(map, cmp?) is a function by(map, cmp?)(a, b), as it is a higher-order comparator. Any parameter that expects a comparator can accept these functions directly.
๐ Related Libraries
| Library | Description | npm |
|---|---|---|
| @moon7/async | Asynchronous utilities for promises, semaphores, and concurrent operations | |
| @moon7/bits | Bit manipulation utilities and binary operations | |
| @moon7/inspect | Runtime type checking with powerful, composable type inspectors | |
| @moon7/result | Functional error handling with Result and Maybe types | |
| @moon7/signals | Reactive programming with Signals, Sources, and Streams |
๐ค Contributing
We welcome contributions from everyone! See our contributing guide for more details on how to get involved. Please feel free to submit a Pull Request.
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ Acknowledgements
Created and maintained by Munir Hussin.