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") )