From 681b74814e8fc19540b80d4277a36f5efbac4753 Mon Sep 17 00:00:00 2001 From: nikelborm Date: Mon, 11 Aug 2025 14:42:59 +0300 Subject: [PATCH 1/2] improved red black tree --- packages/effect/src/RedBlackTree.ts | 849 ++++++++++++++++++- packages/effect/src/internal/redBlackTree.ts | 14 +- packages/effect/test/RedBlackTree.test.ts | 6 +- 3 files changed, 863 insertions(+), 6 deletions(-) diff --git a/packages/effect/src/RedBlackTree.ts b/packages/effect/src/RedBlackTree.ts index 41209e2c69d..7f45e6cdb77 100644 --- a/packages/effect/src/RedBlackTree.ts +++ b/packages/effect/src/RedBlackTree.ts @@ -59,30 +59,99 @@ export const isRedBlackTree: { (u: unknown): u is RedBlackTree } = RBT.isRedBlackTree +// ✅ behaves as intuitively expected /** * Creates an empty `RedBlackTree`. * * @since 2.0.0 * @category constructors + * + * @example + * + * ```ts + * import { Order, RedBlackTree } from "effect" + * + * // value type is manually specified + * const RBT = RedBlackTree.empty< + * number, // Key type + * string // Value type + * >(Order.number) + * + * console.log(RBT) + * // ^ type is RedBlackTree + * // outputs { _id: "RedBlackTree", values: [] } + * + * // key type was inferred from Order + * const RBT2 = RedBlackTree.empty(Order.string) + * // ^ type is RedBlackTree + * ``` */ export const empty: (ord: Order) => RedBlackTree = RBT.empty +// ✅ behaves as intuitively expected /** * Creates a new `RedBlackTree` from an iterable collection of key/value pairs. * * @since 2.0.0 * @category constructors + * + * @example + * + * ```ts + * import { Order, RedBlackTree } from "effect" + * + * const getRBTFromIterableOrderedByNumber = + * RedBlackTree.fromIterable(Order.number) + * + * const arr = [[6, 1], [9, 2], [6, 3], [7, 4]] as const + * + * // data-last call + * const RBT1 = getRBTFromIterableOrderedByNumber(arr) + * + * console.log(RBT1) + * // ^ type is RedBlackTree<6 | 9 | 7, 1 | 2 | 3 | 4> + * // outputs: { + * // _id: "RedBlackTree", + * // values: [ + * // [ 6, 3 ], [ 6, 1 ], [ 7, 4 ], [ 9, 2 ] + * // ], + * // } + * + * // data-first call + * const RBT2 = RedBlackTree.fromIterable(arr, Order.number) + * // ^ type is RedBlackTree<6 | 9 | 7, 1 | 2 | 3 | 4> + * ``` */ export const fromIterable: { (ord: Order): (entries: Iterable) => RedBlackTree (entries: Iterable, ord: Order): RedBlackTree } = RBT.fromIterable +// ✅ behaves as intuitively expected /** * Constructs a new `RedBlackTree` from the specified entries. * * @since 2.0.0 * @category constructors + * + * @example + * + * ```ts + * import { Order, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make(Order.number)( + * [6, "1"], + * [9, "2"], + * [6, "3"] + * ) + * + * console.log(RBT) + * // ^ type is RedBlackTree + * // outputs: { + * // _id: "RedBlackTree", + * // values: [ [ 6, "3" ], [ 6, "1" ], [ 9, "2" ] ], + * // } + * ``` */ export const make: ( ord: Order @@ -90,6 +159,7 @@ export const make: ( ...entries: Entries ) => RedBlackTree = RBT.make +// ⚠️ Didn't understood the motivation behind making it an iterable that starts iterating from that point, instead of just value getter /** * Returns an iterator that points to the element at the specified index of the * tree. @@ -98,12 +168,54 @@ export const make: ( * * @since 2.0.0 * @category traversing + * + * @example + * + * ```ts + * import { Order, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make(Order.number)( + * [6, "1"], + * [9, "2"], + * [7, "3"], + * [5, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [[ 5, "4" ], [ 6, "1" ], [ 7, "3" ], [ 9, "2" ]], + * // } + * + * // data-first + * const iterableAtExistingIndex = RedBlackTree.at(RBT, 2) + * // ^ type is Iterable<[number, string]> + * + * const log = (iter: Iterable<[any, any]>) => { + * for (const [key, value] of iter) { + * console.log(`key: ${key}, value: ${value}`) + * } + * } + * + * log(iterableAtExistingIndex) + * // Logs: + * // key: 7, value: 3 + * // key: 9, value: 2 + * + * // data-last + * const emptyIterableAtNonexistentIndex = RedBlackTree.at(6)(RBT) + * + * log(emptyIterableAtNonexistentIndex) + * // Logs nothing + * ``` */ export const at: { (index: number): (self: RedBlackTree) => Iterable<[K, V]> (self: RedBlackTree, index: number): Iterable<[K, V]> } = RBT.atForwards +// ⚠️ Doesn't behave as intuitively expected??? /** * Returns an iterator that points to the element at the specified index of the * tree. @@ -112,68 +224,337 @@ export const at: { * * @since 2.0.0 * @category traversing + * + * @example + * + * ```ts + * import { Order, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make(Order.number)( + * [6, "1"], + * [9, "2"], + * [7, "3"], + * [5, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [[ 5, "4" ], [ 6, "1" ], [ 7, "3" ], [ 9, "2" ]], + * // } + * + * // data-first + * const iterableAtExistingIndex = RedBlackTree.atReversed(RBT, 2) + * // ^ type is Iterable<[number, string]> + * + * const log = (iter: Iterable<[any, any]>) => { + * for (const [key, value] of iter) { + * console.log(`key: ${key}, value: ${value}`) + * } + * } + * + * log(iterableAtExistingIndex) + * // Logs: + * // Actual + * // key: 7, value: 3 + * // key: 6, value: 1 + * // key: 5, value: 4 + * // Expected + * // key: 9, value: 2 + * // key: 7, value: 3 + * + * // data-last + * const emptyIterableAtNonexistentIndex = RedBlackTree.atReversed(6)(RBT) + * + * log(emptyIterableAtNonexistentIndex) + * // Logs + * // Actual: + * // nothing + * // Expected + * // nothing + * ``` */ export const atReversed: { (index: number): (self: RedBlackTree) => Iterable<[K, V]> (self: RedBlackTree, index: number): Iterable<[K, V]> } = RBT.atBackwards +// ✅ behaves as intuitively expected /** * Finds all values in the tree associated with the specified key. * * @since 2.0.0 * @category elements + * + * @example + * + * ```ts + * import { Order, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make(Order.number)( + * [6, "1"], + * [9, "2"], + * [6, "3"], + * [5, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [ [ 5, "4" ], [ 6, "3" ], [ 6, "1" ], [ 9, "2" ] ], + * // } + * + * // data-first + * const chunkFound = RedBlackTree.findAll(RBT, 6) + * // ^ type is Chunk + * + * console.log(chunkFound) + * // Logs: { _id: "Chunk", values: [ "1", "3" ] } + * + * // data-last + * const chunkNotFound = RedBlackTree.findAll(12)(RBT) + * + * console.log(chunkNotFound) + * // Logs: { _id: "Chunk", values: [] } + * ``` */ export const findAll: { (key: K): (self: RedBlackTree) => Chunk (self: RedBlackTree, key: K): Chunk } = RBT.findAll +// ✅ behaves as intuitively expected /** * Finds the first value in the tree associated with the specified key, if it exists. * * @category elements * @since 2.0.0 + * + * @example + * + * ```ts + * import { Order, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make(Order.number)( + * [6, "1"], + * [9, "2"], + * [6, "3"], + * [5, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [ [ 5, "4" ], [ 6, "3" ], [ 6, "1" ], [ 9, "2" ] ], + * // } + * + * // data-first + * const optionSomeFound = RedBlackTree.findFirst(RBT, 6) + * // ^ type is Option + * + * console.log(optionSomeFound) + * // Logs: { _id: "Option", _tag: "Some", value: "1" } + * + * // data-last + * const optionNoneFound = RedBlackTree.findFirst(12)(RBT) + * + * console.log(optionNoneFound) + * // Logs: { _id: "Option", _tag: "None" } + * ``` */ export const findFirst: { (key: K): (self: RedBlackTree) => Option (self: RedBlackTree, key: K): Option } = RBT.findFirst +// ✅ behaves as intuitively expected /** * Returns the first entry in the tree, if it exists. * * @since 2.0.0 * @category getters + * + * @example + * + * ```ts + * import { Order, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make(Order.number)( + * [6, "1"], + * [9, "2"], + * [6, "3"], + * [5, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [ [ 5, "4" ], [ 6, "3" ], [ 6, "1" ], [ 9, "2" ] ], + * // } + * + * const optionSomeFound = RedBlackTree.first(RBT) + * // ^ type is Option<[number, string]> + * + * console.log(optionSomeFound) + * // Logs: { _id: "Option", _tag: "Some", value: [ 5, "4" ] } + * + * const optionNoneFound = RedBlackTree.first( + * RedBlackTree.empty(Order.number) + * ) + * + * console.log(optionNoneFound) + * // Logs: { _id: "Option", _tag: "None" } + * ``` */ export const first: (self: RedBlackTree) => Option<[K, V]> = RBT.first +// ✅ behaves as intuitively expected /** * Returns the element at the specified index within the tree or `None` if the * specified index does not exist. * * @since 2.0.0 * @category elements + * + * @example + * + * ```ts + * import { Order, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make(Order.number)( + * [6, "1"], + * [9, "2"], + * [7, "3"], + * [5, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [[ 5, "4" ], [ 6, "1" ], [ 7, "3" ], [ 9, "2" ]], + * // } + * + * // data-first + * const optionSomeFound = RedBlackTree.getAt(RBT, 2) + * // ^ type is Option<[number, string]> + * + * console.log(optionSomeFound) + * // Logs: { _id: "Option", _tag: "Some", value: [ 7, "3" ] } + * + * // data-last + * const optionNoneFound = RedBlackTree.getAt(6)(RBT) + * + * console.log(optionNoneFound) + * // Logs: { _id: "Option", _tag: "None" } + * ``` */ export const getAt: { (index: number): (self: RedBlackTree) => Option<[K, V]> (self: RedBlackTree, index: number): Option<[K, V]> } = RBT.getAt +// ✅ behaves as intuitively expected /** * Gets the `Order` that the `RedBlackTree` is using. * * @since 2.0.0 * @category getters + * + * @example + * + * ```ts + * import { Order, pipe, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make( + * pipe(Order.number, Order.reverse) + * )( + * [6, "1"], + * [9, "2"], + * [7, "3"], + * [5, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [[ 9, "2" ], [ 7, "3" ], [ 6, "1" ], [ 5, "4" ]], + * // } + * + * const numbersDescendingOrder = RedBlackTree.getOrder(RBT) + * // ^ type is Order + * + * console.log([6, 9, 7, 5].sort(numbersDescendingOrder)) + * // Logs: [ 9, 7, 6, 5 ] + * ``` */ export const getOrder: (self: RedBlackTree) => Order = RBT.getOrder +// ✅ behaves as intuitively expected /** * Returns an iterator that traverse entries in order with keys greater than the * specified key. * * @since 2.0.0 * @category traversing + * + * @example + * + * ```ts + * import { Order, pipe, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make( + * pipe(Order.number, Order.reverse) + * )( + * [6, "1"], + * [9, "2"], + * [7, "3"], + * [5, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [[ 9, "2" ], [ 7, "3" ], [ 6, "1" ], [ 5, "4" ]], + * // } + * + * const log = (iter: Iterable<[any, any]>) => { + * for (const [key, value] of iter) { + * console.log(`key: ${key}, value: ${value}`) + * } + * console.log("-") + * } + * + * // data-first + * const iterableWithHalfOfRBT = RedBlackTree.greaterThan(RBT, 7) + * // ^ type is Iterable<[number, string]> + * + * log(iterableWithHalfOfRBT) + * // Logs: + * // key: 6, value: 1 + * // key: 5, value: 4 + * + * // data-last + * const iterableWithFullRBT = RedBlackTree.greaterThan(12)(RBT) + * + * log(iterableWithFullRBT) + * // Logs: + * // key: 9, value: 2 + * // key: 7, value: 3 + * // key: 6, value: 1 + * // key: 5, value: 4 + * + * const emptyIterable = RedBlackTree.greaterThan(5)(RBT) + * + * log(emptyIterable) + * // Logs nothing + * ``` */ export const greaterThan: { (key: K): (self: RedBlackTree) => Iterable<[K, V]> @@ -186,18 +567,132 @@ export const greaterThan: { * * @since 2.0.0 * @category traversing + * + * @example + * + * ```ts + * import { Order, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make(Order.number)( + * [6, "1"], + * [9, "2"], + * [7, "3"], + * [5, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [[ 5, "4" ], [ 6, "1" ], [ 7, "3" ], [ 9, "2" ]], + * // } + * + * const log = (iter: Iterable<[any, any]>) => { + * for (const [key, value] of iter) { + * console.log(`key: ${key}, value: ${value}`) + * } + * console.log("-") + * } + * + * // data-first + * const iterableWithFullRBT = RedBlackTree.greaterThanReversed(RBT, 7) + * // ^ type is Iterable<[number, string]> + * + * log(iterableWithFullRBT) + * // Logs: + * // key: 9, value: 2 + * // key: 7, value: 3 + * // key: 6, value: 1 + * // key: 5, value: 4 + * // Expected + * // key: 9, value: 2 + * + * // data-last + * const emptyIterable = RedBlackTree.greaterThanReversed(12)(RBT) + * + * log(emptyIterable) + * // Logs + * // Actual + * // nothing + * // Expected + * // nothing + * + * const iterableWithHalfOfRBT = RedBlackTree.greaterThanReversed(5)(RBT) + * + * log(iterableWithHalfOfRBT) + * // Logs: + * // Actual: + * // key: 6, value: 1 + * // key: 5, value: 4 + * // Expected: + * // key: 9, value: 2 + * // key: 7, value: 3 + * // key: 6, value: 1 + * ``` */ export const greaterThanReversed: { (key: K): (self: RedBlackTree) => Iterable<[K, V]> (self: RedBlackTree, key: K): Iterable<[K, V]> } = RBT.greaterThanBackwards +// ✅ behaves as intuitively expected /** * Returns an iterator that traverse entries in order with keys greater than or * equal to the specified key. * * @since 2.0.0 * @category traversing + * + * @example + * + * ```ts + * import { Order, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make(Order.number)( + * [6, "1"], + * [9, "2"], + * [7, "3"], + * [5, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [[ 5, "4" ], [ 6, "1" ], [ 7, "3" ], [ 9, "2" ]], + * // } + * + * const log = (iter: Iterable<[any, any]>) => { + * for (const [key, value] of iter) { + * console.log(`key: ${key}, value: ${value}`) + * } + * console.log("-") + * } + * + * // data-first + * const iterableWithHalfOfRBT = RedBlackTree.greaterThanEqual(RBT, 7) + * // ^ type is Iterable<[number, string]> + * + * log(iterableWithHalfOfRBT) + * // Logs: + * // key: 7, value: 3 + * // key: 9, value: 2 + * + * // data-last + * const emptyIterable = RedBlackTree.greaterThanEqual(12)(RBT) + * + * log(emptyIterable) + * // Logs nothing + * + * const iterableWithFullRBT = RedBlackTree.greaterThanEqual(5)(RBT) + * + * log(iterableWithFullRBT) + * // Logs: + * // key: 5, value: 4 + * // key: 6, value: 1 + * // key: 7, value: 3 + * // key: 9, value: 2 + * ``` */ export const greaterThanEqual: { (key: K): (self: RedBlackTree) => Iterable<[K, V]> @@ -210,63 +705,330 @@ export const greaterThanEqual: { * * @since 2.0.0 * @category traversing + * + * @example + * + * ```ts + * import { Order, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make(Order.number)( + * [6, "1"], + * [9, "2"], + * [7, "3"], + * [5, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [[ 5, "4" ], [ 6, "1" ], [ 7, "3" ], [ 9, "2" ]], + * // } + * + * const log = (iter: Iterable<[any, any]>) => { + * for (const [key, value] of iter) { + * console.log(`key: ${key}, value: ${value}`) + * } + * console.log("-") + * } + * + * // data-first + * const iterable1 = RedBlackTree.greaterThanEqualReversed(RBT, 7) + * // ^ type is Iterable<[number, string]> + * + * log(iterable1) + * // Logs: + * // key: 7, value: 3 + * // key: 6, value: 1 + * // key: 5, value: 4 + * + * // data-last + * const emptyIterable = RedBlackTree.greaterThanEqualReversed(12)(RBT) + * + * log(emptyIterable) + * // Logs nothing + * + * const iterable2 = RedBlackTree.greaterThanEqualReversed(5)(RBT) + * + * log(iterable2) + * // Logs: + * // key: 5, value: 4 + * ``` */ export const greaterThanEqualReversed: { (key: K): (self: RedBlackTree) => Iterable<[K, V]> (self: RedBlackTree, key: K): Iterable<[K, V]> } = RBT.greaterThanEqualBackwards +// ✅ behaves as intuitively expected /** - * Finds the item with key, if it exists. + * Checks if an item with a certain key exists. * * @since 2.0.0 * @category elements + * + * @example + * + * ```ts + * import { Order, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make(Order.number)( + * [6, "1"], + * [9, "2"], + * [7, "3"], + * [5, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [[ 5, "4" ], [ 6, "1" ], [ 7, "3" ], [ 9, "2" ]], + * // } + * + * // data-first + * const wasExistingKeyFound = RedBlackTree.has(RBT, 7) + * // ^ type is boolean + * + * console.log(wasExistingKeyFound) + * // Logs: true + * + * // data-last + * const wasNonexistentKeyFound = RedBlackTree.has(12)(RBT) + * + * console.log(wasNonexistentKeyFound) + * // Logs: false + * ``` */ export const has: { (key: K): (self: RedBlackTree) => boolean (self: RedBlackTree, key: K): boolean } = RBT.has +// ✅ behaves as intuitively expected /** * Insert a new item into the tree. * * @since 2.0.0 + * + * @example + * + * ```ts + * import { flow, Order, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make(Order.number)([6, "1"]) + * // ^ RedBlackTree + * + * // data-first + * const RBT1 = RedBlackTree.insert(RBT, 7, "3").pipe( + * (_RBT) => RedBlackTree.insert(_RBT, 7, "14") + * ) + * + * // data-last + * const RBT2 = flow( + * RedBlackTree.insert(5, "4"), + * RedBlackTree.insert(5, "12") + * )(RBT) + * + * // Since RBTs are immutable, updates happen indepedently + * + * console.log(RBT) + * // Logs: { _id: "RedBlackTree", values: [[ 6, "1" ]] } + * + * console.log(RBT1) + * // Logs: { _id: "RedBlackTree", values: [[ 6, "1" ], [ 7, "14" ], [ 7, "3" ]] } + * + * console.log(RBT2) + * // Logs: { _id: "RedBlackTree", values: [[ 5, "12" ], [ 5, "4" ], [ 6, "1" ]] } + * ``` */ export const insert: { (key: K, value: V): (self: RedBlackTree) => RedBlackTree (self: RedBlackTree, key: K, value: V): RedBlackTree } = RBT.insert +// ✅ behaves as intuitively expected /** * Get all the keys present in the tree in order. * * @since 2.0.0 * @category getters + * + * @example + * + * ```ts + * import { Order, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make(Order.number)( + * [6, "1"], + * [9, "2"], + * [7, "3"], + * [6, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [[ 6, "4" ], [ 6, "1" ], [ 7, "3" ], [ 9, "2" ]], + * // } + * + * const keysFound = RedBlackTree.keys(RBT) + * // ^ type is IterableIterator + * + * console.log([...keysFound]) + * // Logs: [ 6, 6, 7, 9 ] + * + * console.log([...RedBlackTree.keys( + * RedBlackTree.empty(Order.number) + * )]) + * // Logs: [] + * ``` */ export const keys: (self: RedBlackTree) => IterableIterator = RBT.keysForward +// ✅ behaves as intuitively expected (after I fixed it) /** * Get all the keys present in the tree in reverse order. * * @since 2.0.0 * @category getters + * + * @example + * + * ```ts + * import { Order, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make(Order.number)( + * [6, "1"], + * [9, "2"], + * [7, "3"], + * [6, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [[ 6, "4" ], [ 6, "1" ], [ 7, "3" ], [ 9, "2" ]], + * // } + * + * const keysFound = RedBlackTree.keysReversed(RBT) + * // ^ type is IterableIterator + * + * console.log([...keysFound]) + * // Logs: [ 9, 7, 6, 6 ] + * + * console.log([...RedBlackTree.keysReversed( + * RedBlackTree.empty(Order.number) + * )]) + * // Logs: [] + * ``` */ export const keysReversed: (self: RedBlackTree) => IterableIterator = RBT.keysBackward +// ✅ behaves as intuitively expected /** * Returns the last entry in the tree, if it exists. * * @since 2.0.0 * @category getters + * + * @example + * + * ```ts + * import { Order, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make(Order.number)( + * [6, "1"], + * [9, "2"], + * [6, "3"], + * [5, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [ [ 5, "4" ], [ 6, "3" ], [ 6, "1" ], [ 9, "2" ] ], + * // } + * + * const optionSomeFound = RedBlackTree.last(RBT) + * // ^ type is Option<[number, string]> + * + * console.log(optionSomeFound) + * // Logs: { _id: "Option", _tag: "Some", value: [ 9, "2" ] } + * + * const optionNoneFound = RedBlackTree.last( + * RedBlackTree.empty(Order.number) + * ) + * + * console.log(optionNoneFound) + * // Logs: { _id: "Option", _tag: "None" } + * ``` */ export const last: (self: RedBlackTree) => Option<[K, V]> = RBT.last +// ❌ Doesn't behave as intuitively expected!!! /** * Returns an iterator that traverse entries in order with keys less than the * specified key. * * @since 2.0.0 * @category traversing + * + * @example + * + * ```ts + * import { Order, pipe, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make( + * pipe(Order.number, Order.reverse) + * )( + * [6, "1"], + * [9, "2"], + * [7, "3"], + * [5, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [[ 9, "2" ], [ 7, "3" ], [ 6, "1" ], [ 5, "4" ]], + * // } + * + * const log = (iter: Iterable<[any, any]>) => { + * for (const [key, value] of iter) { + * console.log(`key: ${key}, value: ${value}`) + * } + * console.log("-") + * } + * + * // data-first + * const iterableWithFullRBT = RedBlackTree.lessThan(RBT, 7) + * // ^ type is Iterable<[number, string]> + * + * log(iterableWithFullRBT) + * // Logs: + * // key: 9, value: 2 + * // key: 7, value: 3 + * // key: 6, value: 1 + * // key: 5, value: 4 + * + * // data-last + * const emptyIterable = RedBlackTree.lessThan(12)(RBT) + * + * log(emptyIterable) + * // Logs nothing + * + * const iterableWithHalfOfRBT = RedBlackTree.lessThan(5)(RBT) + * + * log(iterableWithHalfOfRBT) + * // Logs: + * // key: 6, value: 1 + * // key: 5, value: 4 + * ``` */ export const lessThan: { (key: K): (self: RedBlackTree) => Iterable<[K, V]> @@ -285,12 +1047,64 @@ export const lessThanReversed: { (self: RedBlackTree, key: K): Iterable<[K, V]> } = RBT.lessThanBackwards +// ❌ Doesn't behave as intuitively expected!!! /** * Returns an iterator that traverse entries in order with keys less than or * equal to the specified key. * * @since 2.0.0 * @category traversing + * + * @example + * + * ```ts + * import { Order, pipe, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make( + * pipe(Order.number, Order.reverse) + * )( + * [6, "1"], + * [9, "2"], + * [7, "3"], + * [5, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [[ 9, "2" ], [ 7, "3" ], [ 6, "1" ], [ 5, "4" ]], + * // } + * + * const log = (iter: Iterable<[any, any]>) => { + * for (const [key, value] of iter) { + * console.log(`key: ${key}, value: ${value}`) + * } + * console.log("-") + * } + * + * // data-first + * const RBT1 = RedBlackTree.lessThanEqual(RBT, 7) + * // ^ type is Iterable<[number, string]> + * + * log(RBT1) + * // Logs: + * // key: 7, value: 3 + * // key: 6, value: 1 + * // key: 5, value: 4 + * + * // data-last + * const emptyIterable = RedBlackTree.lessThanEqual(12)(RBT) + * + * log(emptyIterable) + * // Logs nothing + * + * const RBT2 = RedBlackTree.lessThanEqual(5)(RBT) + * + * log(RBT2) + * // Logs: + * // key: 5, value: 4 + * ``` */ export const lessThanEqual: { (key: K): (self: RedBlackTree) => Iterable<[K, V]> @@ -309,11 +1123,44 @@ export const lessThanEqualReversed: { (self: RedBlackTree, key: K): Iterable<[K, V]> } = RBT.lessThanEqualBackwards +// ✅ behaves as intuitively expected /** * Execute the specified function for each node of the tree, in order. * * @since 2.0.0 * @category traversing + * + * @example + * + * ```ts + * import { Order, RedBlackTree } from "effect" + * + * const RBT = RedBlackTree.make(Order.number)( + * [6, "1"], + * [9, "2"], + * [6, "3"], + * [5, "4"] + * ) + * + * console.log(RBT) + * // ^ RedBlackTree + * // Logs: { + * // _id: "RedBlackTree", + * // values: [ [ 5, "4" ], [ 6, "3" ], [ 6, "1" ], [ 9, "2" ] ], + * // } + * + * // data-first + * RedBlackTree.forEach(RBT, console.log) + * // Logs: + * // 5 4 + * // 6 3 + * // 6 1 + * // 9 2 + * + * // data-last + * RedBlackTree.forEach(console.log)(RedBlackTree.empty(Order.number)) + * // Logs nothing + * ``` */ export const forEach: { (f: (key: K, value: V) => void): (self: RedBlackTree) => void diff --git a/packages/effect/src/internal/redBlackTree.ts b/packages/effect/src/internal/redBlackTree.ts index 9076ff9d3b6..d80bdf21a5c 100644 --- a/packages/effect/src/internal/redBlackTree.ts +++ b/packages/effect/src/internal/redBlackTree.ts @@ -457,7 +457,12 @@ const keys = ( self: RBT.RedBlackTree, direction: RBT.RedBlackTree.Direction ): IterableIterator => { - const begin: RedBlackTreeIterator = self[Symbol.iterator]() as RedBlackTreeIterator + let begin: RedBlackTreeIterator = self[Symbol.iterator]() as RedBlackTreeIterator + + if (direction === Direction.Backward) { + while (begin.hasNext) begin.moveNext() + begin = begin.reversed() + } let count = 0 return { [Symbol.iterator]: () => keys(self, direction), @@ -919,7 +924,12 @@ const values = ( self: RBT.RedBlackTree, direction: RBT.RedBlackTree.Direction ): IterableIterator => { - const begin: RedBlackTreeIterator = self[Symbol.iterator]() as RedBlackTreeIterator + let begin: RedBlackTreeIterator = self[Symbol.iterator]() as RedBlackTreeIterator + + if (direction === Direction.Backward) { + while (begin.hasNext) begin.moveNext() + begin = begin.reversed() + } let count = 0 return { [Symbol.iterator]: () => values(self, direction), diff --git a/packages/effect/test/RedBlackTree.test.ts b/packages/effect/test/RedBlackTree.test.ts index c908d83eb40..b90d9b270fb 100644 --- a/packages/effect/test/RedBlackTree.test.ts +++ b/packages/effect/test/RedBlackTree.test.ts @@ -232,10 +232,10 @@ describe("RedBlackTree", () => { it("greaterThan", () => { const tree = pipe( RedBlackTree.empty(Num.Order), - RedBlackTree.insert(1, "a"), - RedBlackTree.insert(0, "b"), - RedBlackTree.insert(-1, "c"), RedBlackTree.insert(-2, "d"), + RedBlackTree.insert(-1, "c"), + RedBlackTree.insert(0, "b"), + RedBlackTree.insert(1, "a"), RedBlackTree.insert(3, "e") ) From bd0dd93a8053862bff23b033ee0a015d2d9d124f Mon Sep 17 00:00:00 2001 From: nikelborm Date: Tue, 14 Oct 2025 13:20:12 +0300 Subject: [PATCH 2/2] wip --- .vscode/settings.json | 3 +- mise.toml | 2 + packages/effect/src/SortedSet.ts | 56 +++++++++++++++- pnpm-lock.yaml | 111 ++++++++++++++++++++++++++++--- 4 files changed, 159 insertions(+), 13 deletions(-) create mode 100644 mise.toml diff --git a/.vscode/settings.json b/.vscode/settings.json index a5826e77e08..d2b5c5dd45f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -44,5 +44,6 @@ "editor.suggestSelection": "recentlyUsed", "editor.wordBasedSuggestions": "matchingDocuments", "editor.parameterHints.enabled": true, - "files.insertFinalNewline": true + "files.insertFinalNewline": true, + "python.defaultInterpreterPath": "/home/nikel/.local/share/mise/installs/python/3.14.0/bin/python" } diff --git a/mise.toml b/mise.toml new file mode 100644 index 00000000000..a3a76a2c4ac --- /dev/null +++ b/mise.toml @@ -0,0 +1,2 @@ +[tools] +pnpm = "latest" diff --git a/packages/effect/src/SortedSet.ts b/packages/effect/src/SortedSet.ts index 2bc50e544ba..8e828ec271c 100644 --- a/packages/effect/src/SortedSet.ts +++ b/packages/effect/src/SortedSet.ts @@ -79,6 +79,22 @@ const fromTree = (keyTree: RBT.RedBlackTree): SortedSet => { /** * @since 2.0.0 * @category refinements + * + * @example + * + * ```ts + * import { Order, SortedSet } from "effect" + * + * const newSortedSet = SortedSet.empty(Order.number) as unknown + * + * const isMyThingASortedSet = SortedSet.isSortedSet(newSortedSet) + * + * if (isMyThingASortedSet) { + * console.log(newSortedSet) + * // ^ type is SortedSet.SortedSet + * // outputs { _id: "SortedSet", values: [] } + * } + * ``` */ export const isSortedSet: { (u: Iterable): u is SortedSet @@ -88,6 +104,19 @@ export const isSortedSet: { /** * @since 2.0.0 * @category constructors + * + * @example + * + * ```ts + * import { Order, SortedSet } from "effect" + * + * const newSortedSet = SortedSet.empty(Order.number) + * // ^ type is SortedSet.SortedSet + * + * console.log(newSortedSet) + * // outputs { _id: "SortedSet", values: [] } + * + * ``` */ export const empty = (O: Order): SortedSet => fromTree(RBT.empty(O)) @@ -96,14 +125,37 @@ export const empty = (O: Order): SortedSet => fromTree(RBT.empty(O)) * * @since 2.0.0 * @category constructors + * + * @example + * + * ```ts + * import { Order, pipe, SortedSet } from "effect" + * + * const reverseNumberOrder = pipe(Order.number, Order.reverse) + * + * const arr = [1, 5, 2, 5] + * + * const mySet = SortedSet.fromIterable(reverseNumberOrder)(arr) + * + * console.log(mySet) + * // ^ type is SortedSet.SortedSet + * // { _id: "SortedSet", values: [ 5, 5, 2, 1 ] } + * ``` */ export const fromIterable: { (ord: Order): (iterable: Iterable) => SortedSet (iterable: Iterable, ord: Order): SortedSet } = Dual.dual( 2, - (iterable: Iterable, ord: Order): SortedSet => - fromTree(RBT.fromIterable(Array.from(iterable).map((k) => [k, true]), ord)) + (iterable: Iterable, ord: Order): SortedSet => { + const RBT_fromIterable = RBT.fromIterable(Array.from(iterable).map((k) => [k, true]), ord) + console.log("RBT_fromIterable", RBT_fromIterable) + + const asd = fromTree(RBT_fromIterable) + console.log("fromTree(RBT_fromIterable)", asd) + + return asd + } ) /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4020cd5cf90..1771d7182ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1149,9 +1149,24 @@ importers: '@effect/workflow': specifier: workspace:* version: link:../packages/workflow + '@types/bun': + specifier: ^1.2.2 + version: 1.2.18(@types/react@19.1.8) + '@types/deep-equal': + specifier: ^1.0.4 + version: 1.0.4 + '@types/functional-red-black-tree': + specifier: ^1.0.6 + version: 1.0.6 + deep-equal: + specifier: ^2.2.3 + version: 2.2.3 effect: specifier: workspace:* version: link:../packages/effect + functional-red-black-tree: + specifier: ^1.0.1 + version: 1.0.1 packages: @@ -2759,6 +2774,9 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/deep-equal@1.0.4': + resolution: {integrity: sha512-tqdiS4otQP4KmY0PR3u6KbZ5EWvhNdUoS/jc93UuK23C220lOZ/9TvjfxdPcKvqwwDVtmtSCrnr0p/2dirAxkA==} + '@types/docker-modem@3.0.6': resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==} @@ -2771,6 +2789,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/functional-red-black-tree@1.0.6': + resolution: {integrity: sha512-h7W9Mjozzx+HA9L7CSKAm3dSCa9vBea2U5kWfDuXedAjOb/Sy7VXWL4qUi7H03IJvwHEJ/vf2P2agw9XNTWZxg==} + '@types/glob@7.1.3': resolution: {integrity: sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==} @@ -3698,6 +3719,10 @@ packages: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} + deep-equal@2.2.3: + resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} + engines: {node: '>= 0.4'} + deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -4013,6 +4038,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-get-iterator@1.1.3: + resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} @@ -4392,6 +4420,9 @@ packages: resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} engines: {node: '>= 0.4'} + functional-red-black-tree@1.0.1: + resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} + functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} @@ -4644,6 +4675,10 @@ packages: is-alphanumerical@1.0.4: resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -5070,6 +5105,7 @@ packages: libsql@0.4.7: resolution: {integrity: sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw==} + cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] lighthouse-logger@1.4.2: @@ -5534,6 +5570,10 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} + engines: {node: '>= 0.4'} + object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -7957,7 +7997,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 24.0.14 + '@types/node': 22.16.4 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -7968,7 +8008,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 24.0.14 + '@types/node': 22.16.4 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -8661,6 +8701,8 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/deep-equal@1.0.4': {} + '@types/docker-modem@3.0.6': dependencies: '@types/node': 22.16.4 @@ -8679,6 +8721,8 @@ snapshots: '@types/estree@1.0.8': {} + '@types/functional-red-black-tree@1.0.6': {} + '@types/glob@7.1.3': dependencies: '@types/minimatch': 6.0.0 @@ -8686,7 +8730,7 @@ snapshots: '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 24.0.14 + '@types/node': 22.16.4 '@types/ini@4.1.1': {} @@ -8740,6 +8784,7 @@ snapshots: '@types/node@24.0.14': dependencies: undici-types: 7.8.0 + optional: true '@types/normalize-package-data@2.4.4': {} @@ -9552,7 +9597,7 @@ snapshots: chrome-launcher@0.15.2: dependencies: - '@types/node': 24.0.14 + '@types/node': 22.16.4 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -9561,7 +9606,7 @@ snapshots: chromium-edge-launcher@0.2.0: dependencies: - '@types/node': 24.0.14 + '@types/node': 22.16.4 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -9740,6 +9785,27 @@ snapshots: deep-eql@5.0.2: {} + deep-equal@2.2.3: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + es-get-iterator: 1.1.3 + get-intrinsic: 1.3.0 + is-arguments: 1.2.0 + is-array-buffer: 3.0.5 + is-date-object: 1.1.0 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 + isarray: 2.0.5 + object-is: 1.1.6 + object-keys: 1.1.1 + object.assign: 4.1.7 + regexp.prototype.flags: 1.5.4 + side-channel: 1.1.0 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + deep-extend@0.6.0: {} deep-is@0.1.4: {} @@ -10031,6 +10097,18 @@ snapshots: es-errors@1.3.0: {} + es-get-iterator@1.1.3: + dependencies: + call-bind: 1.0.8 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + is-arguments: 1.2.0 + is-map: 2.0.3 + is-set: 2.0.3 + is-string: 1.1.1 + isarray: 2.0.5 + stop-iteration-iterator: 1.1.0 + es-module-lexer@1.7.0: {} es-object-atoms@1.1.1: @@ -10505,6 +10583,8 @@ snapshots: hasown: 2.0.2 is-callable: 1.2.7 + functional-red-black-tree@1.0.1: {} + functions-have-names@1.2.3: {} generate-function@2.3.1: @@ -10789,6 +10869,11 @@ snapshots: is-alphabetical: 1.0.4 is-decimal: 1.0.4 + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -11040,7 +11125,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 24.0.14 + '@types/node': 22.16.4 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -11050,7 +11135,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 24.0.14 + '@types/node': 22.16.4 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -11084,7 +11169,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 24.0.14 + '@types/node': 22.16.4 jest-util: 29.7.0 jest-regex-util@29.6.3: {} @@ -11109,7 +11194,7 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 24.0.14 + '@types/node': 22.16.4 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -11820,6 +11905,11 @@ snapshots: object-inspect@1.13.4: {} + object-is@1.1.6: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + object-keys@1.1.1: {} object.assign@4.1.7: @@ -13061,7 +13151,8 @@ snapshots: undici-types@6.21.0: {} - undici-types@7.8.0: {} + undici-types@7.8.0: + optional: true undici@5.29.0: dependencies: