import { defaultArrayComparer, sort } from "../../arrays"; import { ComparerFunction } from "../../collections/comparison"; import { PipeBody, PipeFunction } from "../../pipables"; import { tryGetCountDirect } from "./count"; import { asLinq, LinqWrapper } from "../linqWrapper"; import { IntermediateLinqWrapper } from "../internal"; import { BuiltInLinqTraits, TryGetCountDirectSymbol, TryUnwrapUnorderedSymbol } from "../traits"; import { SequenceElementSelector } from "./typing"; import { unwrapUnorderedLinqWrapper } from "../internal"; export interface OrderedLinqWrapperBase { thenBy(keySelector: SequenceElementSelector, comparer?: ComparerFunction): LinqWrapper; thenByDescending(keySelector: SequenceElementSelector, comparer?: ComparerFunction): LinqWrapper; } export interface OrderedLinqWrapper extends LinqWrapper, OrderedLinqWrapperBase { } export function orderBy(keySelector: SequenceElementSelector, comparer?: ComparerFunction): PipeBody, OrderedLinqWrapper> { return target => resetOrderClause(target, { selector: keySelector, comparer, descending: false }); } orderBy satisfies PipeFunction; export function orderByDescending(keySelector: SequenceElementSelector, comparer?: ComparerFunction): PipeBody, OrderedLinqWrapper> { return target => resetOrderClause(target, { selector: keySelector, comparer, descending: true }); } orderByDescending satisfies PipeFunction; export function order(comparer?: ComparerFunction): PipeBody, OrderedLinqWrapper> { return target => resetOrderClause(target, { comparer, descending: false }); } order satisfies PipeFunction; export function orderDescending(comparer?: ComparerFunction): PipeBody, OrderedLinqWrapper> { return target => resetOrderClause(target, { comparer, descending: true }); } orderDescending satisfies PipeFunction; function resetOrderClause(wrapper: LinqWrapper, clause: OrderClause): OrderedLinqWrapper { const unwrapped = wrapper instanceof OrderedLinqWrapperImpl // Discard old sorting ? wrapper.__state.iterable : unwrapUnorderedLinqWrapper(wrapper); return new OrderedLinqWrapperImpl({ iterable: unwrapped as Iterable, orderClauses: [clause], }); } function appendOrderClause(wrapper: OrderedLinqWrapperImpl, clause: OrderClause): OrderedLinqWrapper { const state = wrapper.__state; return new OrderedLinqWrapperImpl({ ...state, orderClauses: [...state.orderClauses, clause], }); } interface OrderClause { selector?: (element: T) => TKey; comparer?: ComparerFunction; descending: boolean; } interface OrderedIteratorInfo { iterable: Iterable; // eslint-disable-next-line @typescript-eslint/no-explicit-any orderClauses: Array>; } class OrderedLinqWrapperImpl extends IntermediateLinqWrapper> implements OrderedLinqWrapperBase, BuiltInLinqTraits { public thenBy(keySelector: SequenceElementSelector, comparer?: ComparerFunction): LinqWrapper { return appendOrderClause(this, { selector: keySelector, comparer, descending: false }); } public thenByDescending(keySelector: SequenceElementSelector, comparer?: ComparerFunction): LinqWrapper { return appendOrderClause(this, { selector: keySelector, comparer, descending: true }); } public [TryGetCountDirectSymbol](): number | undefined { return asLinq(this.__state.iterable).$(tryGetCountDirect()); } public [TryUnwrapUnorderedSymbol](): Iterable { return this.__state.iterable; } public override *[Symbol.iterator](): Iterator { const { iterable, orderClauses } = this.__state; // TODO calculate sorted indicies instead. const buffer: Array<[value: T, key: unknown]> = []; for (const e of iterable) { buffer.push([e, undefined]); } // perf consideration /* eslint-disable @typescript-eslint/prefer-for-of */ for (let i = 0; i < orderClauses.length; i++) { // stable sort but reverse the order of clauses const { selector, comparer = defaultArrayComparer, descending } = orderClauses[orderClauses.length - i - 1]; const signFactor = descending ? -1 : 1; if (selector) { // fill keys for (let i = 0; i < buffer.length; i++) buffer[i][1] = selector(buffer[i][0]); sort(buffer, (x, y) => signFactor * comparer(x[1], y[1])); } else { // compare by elements sort(buffer, (x, y) => signFactor * comparer(x[0], y[0])); } } for (let i = 0; i < buffer.length; i++) yield buffer[i][0]; /* eslint-enable @typescript-eslint/prefer-for-of */ } }