From fff8edf5e06c9da7c04a550cb988a805cd6fd908 Mon Sep 17 00:00:00 2001 From: AlexAegis Date: Sun, 10 Dec 2023 22:55:44 +0100 Subject: [PATCH] feat: fought day 5 part 2 and won, but the battlefield is soaked with blood --- solutions/typescript/2023/05/src/p2.ts | 190 +++++-------- solutions/typescript/2023/08/src/parse.ts | 12 +- .../src/math/common/interval.class.spec.ts | 133 ++++++++- .../lib/src/math/common/interval.class.ts | 265 +++++++++++++++--- .../src/model/tree/pair-tree.class.spec.ts | 2 - 5 files changed, 427 insertions(+), 175 deletions(-) diff --git a/solutions/typescript/2023/05/src/p2.ts b/solutions/typescript/2023/05/src/p2.ts index 26e37dd16..461fa8336 100644 --- a/solutions/typescript/2023/05/src/p2.ts +++ b/solutions/typescript/2023/05/src/p2.ts @@ -1,7 +1,6 @@ import { Interval, task } from '@alexaegis/advent-of-code-lib'; import packageJson from '../package.json'; -import { mapSeed } from './p1.js'; -import { findRange, parse, type Range } from './parse.js'; +import { parse, type Range } from './parse.js'; export const getRangeEnd = (range: Range): number => { return range.destinationRange + range.rangeLength; @@ -13,53 +12,69 @@ export const getRangeEnd = (range: Range): number => { * */ export const refract = (left: Range[], right: Range[]): Range[] => { - const middle = Interval.merge(left.map((l) => l.to)); - console.log('middle', middle.toString()); - // first, this is the set of sections that are mapping to another region - const results = right.flatMap((r) => { - const intersecitons = middle.filterMap((m) => Interval.intersection(m, r.from)); - - return intersecitons.filterMap((intersection) => { - const res = intersection - ? { - from: intersection, - slope: r.slope, - sourceRangeStart: intersection.low, - destinationRange: intersection.low + r.slope, - rangeLength: - intersection.length - (intersection.highQualifier === 'closed' ? 1 : 0), - to: new Interval(intersection.low + r.slope, intersection.high + r.slope, { - highQualifier: intersection.highQualifier, - lowQualifier: intersection.lowQualifier, - }), - } - : undefined; + const middleColumn = Interval.merge(left.map((l) => l.to)); + console.log('middle\t\t', middleColumn.toString()); + console.log('rightFrom\t', right.map((r) => r.from).toString()); + console.log('rightTo\t\t', right.map((r) => r.to).toString()); - console.log( - 'INTERSEC', - r.from.toString(), - '->', - r.to.toString(), - 'intersection:', - intersection?.toString() ?? 'NONE', - '=>', - res?.to.toString(), - ); + // first, this is the set of sections that are mapping to another region + const reachableRightMappers = right.flatMap((rightMapper) => { + const reachableRightMappers = middleColumn.filterMap((middleSection) => + Interval.intersection(middleSection, rightMapper.from), + ); + return reachableRightMappers.map((reachableRightMapper) => { + const res = { + from: reachableRightMapper, + slope: rightMapper.slope, + sourceRangeStart: reachableRightMapper.low, + destinationRange: reachableRightMapper.low + rightMapper.slope, + rangeLength: + reachableRightMapper.length - + (reachableRightMapper.highQualifier === 'closed' ? 1 : 0), + to: new Interval( + reachableRightMapper.low + rightMapper.slope, + reachableRightMapper.high + rightMapper.slope, + { + highQualifier: reachableRightMapper.highQualifier, + lowQualifier: reachableRightMapper.lowQualifier, + }, + ), + }; + + //console.log( + // 'INTERSEC', + // rightMapper.from.toString(), + // '->', + // rightMapper.to.toString(), + // 'intersection:', + // reachableRightMapper?.toString() ?? 'NONE', + // '=>', + // res?.to.toString(), + //); return res; }); }); const nonMapping = Interval.complement( - results.map((r) => r.from), - middle.map((m) => Interval.open(m.low, m.high)), - ).filter((i) => i.isFinite() && i.length > 0); - + reachableRightMappers.map((r) => r.from), + middleColumn.map( + (m) => + new Interval(m.low, m.high, { + lowQualifier: 'open', + highQualifier: 'open', + }), + ), + ).filter((i) => i.isFinite() && i.length > 0); //.map(i => i.as); + + // const nonMapping = Interval.complement(reachableRightMappers.map((r) => r.from)).flatMap((i) => + // middleColumn.filterMap((m) => i.trim(m)), + // ); console.log( 'nonMapping', nonMapping.map((nm) => nm.toString()), ); - results.push( + reachableRightMappers.push( ...nonMapping.map((interval) => ({ from: interval, to: interval, @@ -69,7 +84,7 @@ export const refract = (left: Range[], right: Range[]): Range[] => { sourceRangeStart: interval.low, })), ); - return results; + return reachableRightMappers; }; export const p2 = (input: string): number => { @@ -83,97 +98,20 @@ export const p2 = (input: string): number => { slope: 0, })); - console.log( - 'seedRanges\n', - seedRanges.map((r) => `\t\tfrom: ${r.from.toString()} to: ${r.to.toString()}`).join('\n'), - ); - - const seedToSoilRefraction = refract(seedRanges, data.seedToSoilMap); - - console.log( - 'seedToSoilRefraction\n', - seedToSoilRefraction - .map((r) => `\t\tfrom: ${r.from.toString()} to: ${r.to.toString()}`) - .join('\n'), - ); - - const soilToFertilizerRefraction = refract(seedToSoilRefraction, data.soilToFertilizerMap); - - const fertilizerToWaterRefraction = refract( - soilToFertilizerRefraction, + const maps = [ + data.seedToSoilMap, + data.soilToFertilizerMap, data.fertilizerToWaterMap, - ); - - const waterToLightRefraction = refract(fertilizerToWaterRefraction, data.waterToLightMap); - - const lightToTemperatureRefraction = refract( - waterToLightRefraction, + data.waterToLightMap, data.lightToTemperatureMap, - ); - - const temperatureToHumidityRefraction = refract( - lightToTemperatureRefraction, data.temperatureToHumidityMap, - ); - - const humidityToLocationRefraction = refract( - temperatureToHumidityRefraction, data.humidityToLocationMap, - ); - - const result = humidityToLocationRefraction.map((range) => range.sourceRangeStart).min(); - - const seedMapper = mapSeed(data); - const mapped = seedMapper(result); - console.log('result', result, 'mapped', mapped); + ]; - const msoil = findRange(mapped, seedToSoilRefraction); - const mfertilizer = findRange(msoil, soilToFertilizerRefraction); - const mwater = findRange(mfertilizer, fertilizerToWaterRefraction); - const mlight = findRange(mwater, waterToLightRefraction); - const mtemperature = findRange(mlight, lightToTemperatureRefraction); - const mhumidity = findRange(mtemperature, temperatureToHumidityRefraction); - const mlocation = findRange(mhumidity, humidityToLocationRefraction); - - const mrsoil = findRange(mapped, humidityToLocationRefraction); - const mrfertilizer = findRange(mrsoil, temperatureToHumidityRefraction); - const mrwater = findRange(mrfertilizer, lightToTemperatureRefraction); - const mrlight = findRange(mrwater, waterToLightRefraction); - const mrtemperature = findRange(mrlight, fertilizerToWaterRefraction); - const mrhumidity = findRange(mrtemperature, soilToFertilizerRefraction); - const mrlocation = findRange(mrhumidity, seedToSoilRefraction); - - const soil = findRange(result, seedToSoilRefraction); - const fertilizer = findRange(soil, soilToFertilizerRefraction); - const water = findRange(fertilizer, fertilizerToWaterRefraction); - const light = findRange(water, waterToLightRefraction); - const temperature = findRange(light, lightToTemperatureRefraction); - const humidity = findRange(temperature, temperatureToHumidityRefraction); - const location = findRange(humidity, humidityToLocationRefraction); - - const rsoil = findRange(result, humidityToLocationRefraction); - const rfertilizer = findRange(rsoil, temperatureToHumidityRefraction); - const rwater = findRange(rfertilizer, lightToTemperatureRefraction); - const rlight = findRange(rwater, waterToLightRefraction); - const rtemperature = findRange(rlight, fertilizerToWaterRefraction); - const rhumidity = findRange(rtemperature, soilToFertilizerRefraction); - const rlocation = findRange(rhumidity, seedToSoilRefraction); - - console.log( - 'location', - location, - rlocation, - mlocation, - mrlocation, - seedMapper(location), - seedMapper(rlocation), - seedMapper(mlocation), - seedMapper(mrlocation), - ); - return 0; + return maps + .reduce((acc, next) => refract(acc, next), seedRanges) + .map((range) => range.to.low) + .min(); }; -await task(p2, packageJson.aoc, 'example.1.txt'); // 84470622 ~4.36ms - -// await task(p2, packageJson.aoc); // 84470622 ~4.36ms -// is too high 4194961753 +await task(p2, packageJson.aoc); // 26714516 ~4.36ms diff --git a/solutions/typescript/2023/08/src/parse.ts b/solutions/typescript/2023/08/src/parse.ts index d59844ac1..81047275f 100644 --- a/solutions/typescript/2023/08/src/parse.ts +++ b/solutions/typescript/2023/08/src/parse.ts @@ -14,8 +14,16 @@ export const parse = (input: string): [Graph, string[]] => { const graphNode = graph.nodes.getOrAdd(key, (key) => new GraphNode(key, 0)); const leftNode = graph.nodes.getOrAdd(leftGate, (key) => new GraphNode(key, 0)); const rightNode = graph.nodes.getOrAdd(rightGate, (key) => new GraphNode(key, 0)); - graphNode.neighbours.set(Direction.WEST, { from: graphNode, to: leftNode }); - graphNode.neighbours.set(Direction.EAST, { from: graphNode, to: rightNode }); + graphNode.neighbours.set(Direction.WEST, { + from: graphNode, + to: leftNode, + direction: Direction.WEST, + }); + graphNode.neighbours.set(Direction.EAST, { + from: graphNode, + to: rightNode, + direction: Direction.EAST, + }); graph.nodes.set(key, graphNode); } diff --git a/solutions/typescript/libs/lib/src/math/common/interval.class.spec.ts b/solutions/typescript/libs/lib/src/math/common/interval.class.spec.ts index 97a80b5ca..6e6e0bc66 100644 --- a/solutions/typescript/libs/lib/src/math/common/interval.class.spec.ts +++ b/solutions/typescript/libs/lib/src/math/common/interval.class.spec.ts @@ -282,7 +282,7 @@ describe('Interval', () => { describe('collectAllPoints', () => { it('should destructure a list of intervals into thier individual points, sorted', () => { - const points = Interval.collectAllPoints([ + const points = Interval.collectAllSignificantPoints([ Interval.closed(1, 4), Interval.closed(2, 5), ]); @@ -313,6 +313,62 @@ describe('Interval', () => { expect(points.length).toEqual(4); }); + + it('should destructure an empty interval into no points', () => { + const points = Interval.collectAllSignificantPoints([Interval.openClosed(1, 1)]); + expect(points.length).toEqual(0); + }); + + it('should destructure a single interval into two points', () => { + const points = Interval.collectAllSignificantPoints([Interval.closed(1, 2)]); + expect(points.length).toEqual(2); + + expect(points[0]).toEqual({ + value: 1, + lowQualifier: INTERVAL_ENDPOINT_CLOSED_QUALIFIER, + highQualifier: INTERVAL_ENDPOINT_OPEN_QUALIFIER, + originalDesignation: 'low', + }); + expect(points[1]).toEqual({ + value: 2, + lowQualifier: INTERVAL_ENDPOINT_OPEN_QUALIFIER, + highQualifier: INTERVAL_ENDPOINT_CLOSED_QUALIFIER, + originalDesignation: 'high', + }); + }); + }); + + describe('sorting qualified numbers', () => { + it('should, sort numbers first, then original destinations, then qualifiers', () => { + const first: QualifiedNumber = { + originalDesignation: 'low', + value: 0, + lowQualifier: 'closed', + highQualifier: 'open', + }; + const second: QualifiedNumber = { + value: 0, + originalDesignation: 'high', + highQualifier: 'open', + lowQualifier: 'closed', + }; + const third: QualifiedNumber = { + value: 4, + originalDesignation: 'low', + highQualifier: 'closed', + lowQualifier: 'open', + }; + const fourth: QualifiedNumber = { + originalDesignation: 'high', + value: 4, + lowQualifier: 'open', + highQualifier: 'closed', + }; + + const points: QualifiedNumber[] = [first, third, second, fourth]; + points.sort(Interval.compareQualifiedNumber); + expect(points).toEqual([first, second, third, fourth]); + }); }); describe('mergeQualifiedNumbers', () => { @@ -371,6 +427,27 @@ describe('Interval', () => { }), ]); }); + + it('should not merge qualifiers that would become empty', () => { + const interval = Interval.mergeQualifiedNumbers([ + { + value: 1, + originalDesignation: 'low', + lowQualifier: 'open', + highQualifier: 'closed', + }, + { + value: 1, + originalDesignation: 'high', + lowQualifier: 'closed', + highQualifier: 'open', + }, + ]); + + console.log(interval); + + expect(interval.length).toEqual(0); + }); }); describe('complement', () => { @@ -380,12 +457,12 @@ describe('Interval', () => { expect(complement[0]).toEqual( new Interval(Number.NEGATIVE_INFINITY, 0, { lowQualifier: 'open', - highQualifier: 'closed', + highQualifier: 'open', }), ); expect(complement[1]).toEqual( new Interval(4, Number.POSITIVE_INFINITY, { - lowQualifier: 'closed', + lowQualifier: 'open', highQualifier: 'open', }), ); @@ -397,15 +474,61 @@ describe('Interval', () => { expect(complement[0]).toEqual( new Interval(Number.NEGATIVE_INFINITY, 0, { lowQualifier: 'open', - highQualifier: 'closed', + highQualifier: 'open', }), ); expect(complement[1]).toEqual( new Interval(4, Number.POSITIVE_INFINITY, { - lowQualifier: 'closed', + lowQualifier: 'open', highQualifier: 'open', }), ); }); + + it('should return the two edges when trying to completement an open interval within its closed counterpart ', () => { + const complement = Interval.complement([Interval.open(0, 4)], [Interval.closed(0, 4)]); + console.log(complement); + expect(complement.length).toEqual(2); + expect(complement[0]).toEqual(Interval.closed(0, 0)); + expect(complement[1]).toEqual(Interval.closed(4, 4)); + }); + + it('should return nothing when trying to completement an interval within itself', () => { + const complement = Interval.complement( + [Interval.closed(0, 4)], + [Interval.closed(0, 4)], + ); + console.log(complement); + expect(complement.length).toEqual(0); + }); + + it('should return nothing when trying to completement intervals, completely filling the within interval', () => { + const complement = Interval.complement( + [Interval.closed(0, 2), Interval.openClosed(2, 4)], + [Interval.closed(0, 4)], + ); + expect(complement.length).toEqual(0); + }); + + it('should return two intervals for a completely enclosed interval', () => { + const complement = Interval.complement( + [Interval.closed(79, 92)], + [Interval.closed(50, 98)], + ); + expect(complement.length).toEqual(2); + expect(complement[0]).toEqual(Interval.closedOpen(50, 79)); + expect(complement[1]).toEqual(Interval.openClosed(92, 98)); + }); + + it('should return three intervals for two completely enclosed intervals', () => { + const complement = Interval.complement( + [Interval.closed(55, 67), Interval.closed(79, 92)], + [Interval.closed(50, 98)], + ); + expect(complement.length).toEqual(3); + expect(complement[0]).toEqual(Interval.closedOpen(50, 55)); + expect(complement[1]).toEqual(Interval.open(67, 79)); + expect(complement[2]).toEqual(Interval.openClosed(92, 98)); + }); }); }); diff --git a/solutions/typescript/libs/lib/src/math/common/interval.class.ts b/solutions/typescript/libs/lib/src/math/common/interval.class.ts index fabcc2b6e..e4ed8d2ac 100644 --- a/solutions/typescript/libs/lib/src/math/common/interval.class.ts +++ b/solutions/typescript/libs/lib/src/math/common/interval.class.ts @@ -260,40 +260,111 @@ export class Interval implements IntervalLike, IntervalQualifier { within?: Interval[] | undefined, ): Interval[] { let points: QualifiedNumber[] = []; + points; if (within) { - points = Interval.collectAllPoints(within); + points = Interval.collectAllSignificantPoints(within); } - points.push(...Interval.collectAllPoints(intervals).map(Interval.invertQualifiedNumber)); + points.push( + ...Interval.collectAllSignificantPoints(intervals).map(Interval.invertQualifiedNumber), + ); + points.sort(Interval.compareQualifiedNumber); + + console.log('all points to be merged', points); + + const me = Interval.mergeQualifiedNumbers(points); + console.log('meee', me); + return me.filter((m) => !m.isEmpty()); + // return within ? within.flatMap((w) => me.filterMap((m) => m.trim(w))) : me; + } + + /** + * An interval is empty if both its high and low values are the same, and + * it's not a closed interval + */ + static isEmpty(this: void, interval: IntervalLike): boolean { + return ( + interval.low === interval.high && + (interval.lowQualifier === INTERVAL_ENDPOINT_OPEN_QUALIFIER || + interval.highQualifier === INTERVAL_ENDPOINT_OPEN_QUALIFIER) + ); + } + + isEmpty(): boolean { + return Interval.isEmpty(this); + } + + /** + * Returns this interval trimmed into another, using the lowest high value + * and the highest low value. + * + * If the interval is completely enveloped, it is simply returned. + * + * If there is no intersection, undefined is returned. + */ + static trim(this: void, interval: Interval, within: Interval): Interval | undefined { + if (interval.isAfterOf(within) || interval.isBeforeOf(within)) { + return undefined; + } else if (within.envelops(interval)) { + return interval; + } else { + let low = interval.low; + let lowQualifier = interval.lowQualifier; + if (interval.low < within.low) { + low = within.low; + lowQualifier = within.lowQualifier; + } + + let high = interval.high; + let highQualifier = interval.highQualifier; + if (interval.high > within.high) { + high = within.high; + highQualifier = within.highQualifier; + } + + return new Interval(low, high, { lowQualifier, highQualifier }); + } + } - return Interval.mergeQualifiedNumbers(points); + /** + * Returns this interval trimmed into another, using the lowest high value + * and the highest low value. + * + * If the interval is completely enveloped, it is simply returned. + * + * If there is no intersection, undefined is returned. + */ + trim(within: Interval): Interval | undefined { + return Interval.trim(this, within); } static invertQualifiedNumber(this: void, qualifiedNumber: QualifiedNumber): QualifiedNumber { return { value: qualifiedNumber.value, originalDesignation: Interval.invertDesignation(qualifiedNumber.originalDesignation), - highQualifier: Interval.invertQualifier(qualifiedNumber.highQualifier), - lowQualifier: Interval.invertQualifier(qualifiedNumber.lowQualifier), + highQualifier: qualifiedNumber.highQualifier, + lowQualifier: qualifiedNumber.lowQualifier, }; } - static collectAllPoints(this: void, intervals: Interval[]): QualifiedNumber[] { + static collectAllSignificantPoints(this: void, intervals: Interval[]): QualifiedNumber[] { const result: QualifiedNumber[] = []; for (const interval of intervals) { - result.push( - { - originalDesignation: 'low', - value: interval.low, - lowQualifier: interval.lowQualifier, - highQualifier: Interval.invertQualifier(interval.lowQualifier), - }, - { - originalDesignation: 'high', - value: interval.high, - lowQualifier: Interval.invertQualifier(interval.highQualifier), - highQualifier: interval.highQualifier, - }, - ); + if (!interval.isEmpty()) { + result.push( + { + originalDesignation: 'low', + value: interval.low, + lowQualifier: interval.lowQualifier, + highQualifier: Interval.invertQualifier(interval.lowQualifier), + }, + { + originalDesignation: 'high', + value: interval.high, + lowQualifier: Interval.invertQualifier(interval.highQualifier), + highQualifier: interval.highQualifier, + }, + ); + } } return result.sort(Interval.compareQualifiedNumber); } @@ -308,6 +379,7 @@ export class Interval implements IntervalLike, IntervalQualifier { qualifiedNumbers.sort(Interval.compareQualifiedNumber); } + console.log('qualifiedNumbers', qualifiedNumbers); const intervalStartStack: QualifiedNumber[] = []; if (qualifiedNumbers[0]?.originalDesignation === 'high') { intervalStartStack.push({ @@ -319,23 +391,30 @@ export class Interval implements IntervalLike, IntervalQualifier { } for (const qualifiedNumber of qualifiedNumbers) { if (qualifiedNumber.originalDesignation === 'low') { + console.log('000ASHGS222AFA'); + intervalStartStack.push(qualifiedNumber); } else if ( qualifiedNumber.originalDesignation === 'high' && intervalStartStack.length > 0 ) { + console.log('1111ASHGS222AFA'); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const matchingNumber = intervalStartStack.shift()!; - result.push( - new Interval(matchingNumber.value, qualifiedNumber.value, { - lowQualifier: matchingNumber.lowQualifier, - highQualifier: qualifiedNumber.highQualifier, - }), - ); + const matchingLow = intervalStartStack.shift()!; + const next = new Interval(matchingLow.value, qualifiedNumber.value, { + lowQualifier: matchingLow.lowQualifier, + highQualifier: qualifiedNumber.highQualifier, + }); + console.log('ASDASDW@222', next); + if (!next.isEmpty()) { + result.push(next); + } } } const last = qualifiedNumbers.at(-1); if (last?.originalDesignation === 'low') { + console.log('LASTINIFIFNIFNIF', last); result.push( new Interval(last.value, Number.POSITIVE_INFINITY, { lowQualifier: last.lowQualifier, @@ -369,14 +448,14 @@ export class Interval implements IntervalLike, IntervalQualifier { } /** - * This takes openness into account + * Lowest possible integer number. This takes openness into account. low or low + 1 when open */ lowest(): number { return this.lowQualifier === INTERVAL_ENDPOINT_CLOSED_QUALIFIER ? this.low : this.low + 1; } /** - * This takes openness into account, i + * Highest possible integer number, this takes openness into account, high, or high - 1 when open */ highest(): number { return this.highQualifier === INTERVAL_ENDPOINT_CLOSED_QUALIFIER @@ -384,40 +463,62 @@ export class Interval implements IntervalLike, IntervalQualifier { : this.high - 1; } + /** + * Checks if a single value is above the high value + */ static isTooHigh(interval: IntervalLike, n: number): boolean { - // ? Check for closedness as the high qualifier is open by default return interval.highQualifier === INTERVAL_ENDPOINT_CLOSED_QUALIFIER ? interval.high < n : interval.high <= n; } + /** + * Checks if a single value is not above the high value + */ static isBelowHigh(interval: IntervalLike, n: number): boolean { return !Interval.isTooHigh(interval, n); } + /** + * Checks if a single value is below the low value of the interval + */ static isTooLow(interval: IntervalLike, n: number): boolean { - // ? Check for openness as the low qualifier is closed by default return interval.lowQualifier === INTERVAL_ENDPOINT_OPEN_QUALIFIER ? n <= interval.low : n < interval.low; } + /** + * Checks if a single value is not below the low value of the interval + */ static isAboveLow(interval: IntervalLike, n: number): boolean { return !Interval.isTooLow(interval, n); } + /** + * Checks if a single value is above the high value + */ isTooHigh(n: number): boolean { return Interval.isTooHigh(this, n); } + /** + * Checks if a single value is not above the high value + */ isBelowHigh(n: number): boolean { return Interval.isBelowHigh(this, n); } + /** + * Checks if a single value is below the low value of the interval + */ isTooLow(n: number): boolean { return Interval.isTooLow(this, n); } + /** + * Checks if a single value is not below the low value of the interval + */ isAboveLow(n: number): boolean { return Interval.isAboveLow(this, n); } @@ -433,17 +534,50 @@ export class Interval implements IntervalLike, IntervalQualifier { } /** - * TODO: Openness + * Checks if the first parameter is completely enveloped by the second + */ + static envelops(this: void, a: IntervalLike, b: IntervalLike): boolean { + return ( + Interval.isAboveLow(b, a.low) && + Interval.isAboveLow(b, a.high) && + Interval.isBelowHigh(b, a.low) && + Interval.isBelowHigh(b, a.high) + ); + } + + /** + * Checks if this interal is completely enveloped by the one passed in + */ + envelops(other: IntervalLike): boolean { + return Interval.envelops(this, other); + } + + /** + * Returns if the first parameter is completely above of the second parameter + */ + static isAfterOf(this: void, a: IntervalLike, b: IntervalLike): boolean { + return Interval.isTooHigh(b, a.low) && Interval.isTooHigh(b, a.high); + } + + /** + * Returns if the first parameter is completely above of the second parameter + */ + static isBeforeOf(this: void, a: IntervalLike, b: IntervalLike): boolean { + return Interval.isTooLow(b, a.low) && Interval.isTooLow(b, a.high); + } + + /** + * Checks if an interval is completely below another */ isBeforeOf(other: Interval): boolean { - return this.low <= other.low && this.high <= other.low; + return Interval.isBeforeOf(this, other); } /** - * TODO: Openness + * Checks if an interval is completely above another */ isAfterOf(other: Interval): boolean { - return other.high <= this.low && other.high <= this.high; + return Interval.isAfterOf(this, other); } clone(): Interval { @@ -512,18 +646,25 @@ export class Interval implements IntervalLike, IntervalQualifier { : a.low - b.low; } + /** + * a low designation comes before the high designation + */ + static compareEndpointDesignation( + this: void, + a: IntervalEndpointDesignation, + b: IntervalEndpointDesignation, + ): number { + return a === b ? 0 : a === 'low' ? -1 : 1; + } + /** * Comparator, comparing qualified numbers * * For the low end, closed comes earlier */ static compareQualifiedNumber(this: void, a: QualifiedNumber, b: QualifiedNumber): number { - // Check for openness because the default for the highQualifier - // when it's not defined is CLOSED return a.value === b.value - ? a.lowQualifier === INTERVAL_ENDPOINT_OPEN_QUALIFIER - ? 1 - : -1 + ? Interval.compareEndpointDesignation(a.originalDesignation, b.originalDesignation) : a.value - b.value; } @@ -577,6 +718,50 @@ export class Interval implements IntervalLike, IntervalQualifier { return Interval.equals(this, other); } + /** + * + * @returns a copy of this interval with both endpoint qualifiers being closed + */ + asClosed(): Interval { + return new Interval(this.low, this.high, { + lowQualifier: INTERVAL_ENDPOINT_CLOSED_QUALIFIER, + highQualifier: INTERVAL_ENDPOINT_CLOSED_QUALIFIER, + }); + } + + /** + * + * @returns a copy of this interval with the low endpoint qualifier being closed and the high open + */ + asClosedOpen(): Interval { + return new Interval(this.low, this.high, { + lowQualifier: INTERVAL_ENDPOINT_CLOSED_QUALIFIER, + highQualifier: INTERVAL_ENDPOINT_OPEN_QUALIFIER, + }); + } + + /** + * + * @returns a copy of this interval with both endpoint qualifiers being open + */ + asOpen(): Interval { + return new Interval(this.low, this.high, { + lowQualifier: INTERVAL_ENDPOINT_OPEN_QUALIFIER, + highQualifier: INTERVAL_ENDPOINT_OPEN_QUALIFIER, + }); + } + + /** + * + * @returns a copy of this interval with the low endpoint qualifier being open and the high closed + */ + asOpenClosed(): Interval { + return new Interval(this.low, this.high, { + lowQualifier: INTERVAL_ENDPOINT_OPEN_QUALIFIER, + highQualifier: INTERVAL_ENDPOINT_CLOSED_QUALIFIER, + }); + } + toString(): string { return `${this.lowQualifier === INTERVAL_ENDPOINT_OPEN_QUALIFIER ? '(' : '['}${this.low},${ this.high diff --git a/solutions/typescript/libs/lib/src/model/tree/pair-tree.class.spec.ts b/solutions/typescript/libs/lib/src/model/tree/pair-tree.class.spec.ts index e57d0ddfc..1d1b356d7 100644 --- a/solutions/typescript/libs/lib/src/model/tree/pair-tree.class.spec.ts +++ b/solutions/typescript/libs/lib/src/model/tree/pair-tree.class.spec.ts @@ -22,11 +22,9 @@ describe('pair tree', () => { const leftTree = PairTree.fromNestedPairs([1, [2, 3]]); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const subTree = leftTree.findNode(2)!; - console.log(subTree); const rightTree = PairTree.fromNestedPairs([4, 5]); const newNode = subTree.join(rightTree); expect(newNode.parent).to.not.be.undefined; - console.log(leftTree.toString()); expect(leftTree.toString()).toEqual('[1,[[2,3],[4,5]]]'); }); });