Skip to content

Commit

Permalink
add shift
Browse files Browse the repository at this point in the history
  • Loading branch information
Symmetronic committed Mar 13, 2021
1 parent 115642c commit 88b1af7
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 16 deletions.
123 changes: 107 additions & 16 deletions src/min-max-range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export {

import {
Coordinates,
MultiDimRange,
Range,
Range1D,
Range2D,
Expand All @@ -19,11 +20,14 @@ import {
import {
abs,
alternative,
asArray,
asEmptyRange,
asMultiDimRange,
asNumber,
asRange,
asRange1D,
asRange2D,
assert,
isArray,
isCouple,
isNumber,
Expand All @@ -43,7 +47,7 @@ import {
export function bottomLeft(range: Range2D): Coordinates {
return pipe(
asRange2D,
(range: Range2D) => [asNumber(min(range[0])), asNumber(min(range[1]))],
(range: Range2D) => [min(range[0]), min(range[1])],
)(range);
}

Expand All @@ -55,7 +59,7 @@ export function bottomLeft(range: Range2D): Coordinates {
export function bottomRight(range: Range2D): Coordinates {
return pipe(
asRange2D,
(range: Range2D) => [asNumber(max(range[0])), asNumber(min(range[1]))],
(range: Range2D) => [max(range[0]), min(range[1])],
)(range);
}

Expand All @@ -70,13 +74,16 @@ export function first(range: Range): undefined | number | number[] {
alternative(
pipe(
asRange1D,
(range: Range1D) => asNumber(range[0]),
(range: Range1D) => range[0],
),
pipe(
asMultiDimRange,
map((el: Range1D) => asNumber(first(el))),
),
ret(undefined),
pipe(
asEmptyRange,
ret(undefined),
),
),
)(range);
}
Expand All @@ -98,7 +105,7 @@ export function isEmptyRange(value: any): boolean {
export function isMultiDimRange(value: any): boolean {
return (
isArray(value) && value.length >= 2
&& (value as Array<any>).every((el) => isRange1D(el))
&& (value as any[]).every((el) => isRange1D(el))
);
}

Expand All @@ -117,7 +124,7 @@ export function isRange(value: any): boolean {
* @returns True if value is one-dimensional range, otherwise false.
*/
export function isRange1D(value: any): boolean {
return isCouple(value) && (value as Array<any>).every((el) => isNumber(el));
return isCouple(value) && (value as any[]).every((el) => isNumber(el));
}

/**
Expand All @@ -126,7 +133,7 @@ export function isRange1D(value: any): boolean {
* @returns True if value is two-dimensional range, otherwise false.
*/
export function isRange2D(value: any): boolean {
return isCouple(value) && (value as Array<any>).every((el) => isRange1D(el));
return isCouple(value) && (value as any[]).every((el) => isRange1D(el));
}

/**
Expand All @@ -140,13 +147,16 @@ export function last(range: Range): undefined | number | number[] {
alternative(
pipe(
asRange1D,
(range: Range1D) => asNumber(range[1]),
(range: Range1D) => range[1],
),
pipe(
asMultiDimRange,
map((el: Range1D) => asNumber(last(el))),
),
ret(undefined),
pipe(
asEmptyRange,
ret(undefined),
),
),
)(range);
}
Expand All @@ -168,7 +178,10 @@ export function length(range: Range): number | number[] {
asMultiDimRange,
map((el: Range1D) => asNumber(length(el))),
),
ret(0),
pipe(
asEmptyRange,
ret(0),
),
),
)(range);
}
Expand All @@ -190,7 +203,10 @@ export function max(range: Range): undefined | number | number[] {
asMultiDimRange,
map((el: Range1D) => asNumber(max(el))),
),
ret(undefined),
pipe(
asEmptyRange,
ret(undefined),
),
),
)(range);
}
Expand All @@ -212,7 +228,10 @@ export function mean(range: Range): undefined | number | number[] {
asMultiDimRange,
map((el: Range1D) => asNumber(mean(el))),
),
ret(undefined),
pipe(
asEmptyRange,
ret(undefined),
),
),
)(range);
}
Expand All @@ -234,7 +253,10 @@ export function min(range: Range): undefined | number | number[] {
asMultiDimRange,
map((el: Range1D) => asNumber(min(el))),
),
ret(undefined),
pipe(
asEmptyRange,
ret(undefined),
),
),
)(range);
}
Expand All @@ -256,7 +278,73 @@ export function reverse(range: Range): Range {
asMultiDimRange,
map((el: Range1D) => asRange1D(reverse(el))),
),
ret([]),
pipe(
asEmptyRange,
ret([]),
),
),
)(range);
}

/**
* Move range by a specified delta.
* @param range Range to move.
* @param delta Delta to move range by. Has to have equal length as range in
* case of multi-dimensional ranges.
* @returns Range moved by delta.
*/
export function shift(
range: Range,
delta: number | number[],
): Range {
return pipe(
asRange,
alternative(
/* One-dimensional range. */
pipe(
asRange1D,
(range: Range1D) => pipe(
asNumber,
(delta: number) => [
asNumber(first(range)) + delta,
asNumber(last(range)) + delta,
],
)(delta),
),
/* Multi-dimensional range. */
pipe(
asMultiDimRange,
(range: MultiDimRange) => alternative(
/* Delta is number. */
pipe(
asNumber,
(delta: number) => (
map((el: Range1D) => asRange1D(shift(el, delta)))(range)
),
),
/* Delta is array. */
pipe(
asArray,
assert(
(delta: any[]) => range.length === delta.length,
(delta: any[]) => (
`Invalid delta ${delta} has length unequal to length of range
${range}.`
),
),
(delta: any[]) => (
range.map((el: Range1D, i: number) =>
asRange1D(shift(el, asNumber(delta[i])))
)
),
),
)(delta),
),
/* Empty range. */
pipe(
asEmptyRange,
ret([]),
),
),
)(range);
}
Expand All @@ -273,13 +361,16 @@ export function sort(range: Range): Range {
alternative(
pipe(
asRange1D,
(range: Range1D) => [asNumber(min(range)), asNumber(max(range))],
(range: Range1D) => [min(range), max(range)],
),
pipe(
asMultiDimRange,
map((el: Range1D) => asRange1D(sort(el))),
),
ret([]),
pipe(
asEmptyRange,
ret([]),
),
),
)(range);
}
Expand Down
12 changes: 12 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ export function alternative(
};
}

/**
* Transform value to array or throw error.
* @param value Value to transform.
* @returns Value as array.
*/
export function asArray(value: any): any[] {
return assert(
isArray,
(value: any) => `Cannot convert value ${value} to array.`,
)(value) as any[];
}

/**
* Transform value to empty range or throw error.
* @param value Value to transform.
Expand Down
56 changes: 56 additions & 0 deletions test/min-max-range.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
mean,
min,
reverse,
shift,
sort,
topLeft,
topRight,
Expand All @@ -28,6 +29,8 @@ import {
RANGE_2D,
} from './mocks';

const arrayOfLength = (length: number) => Array.apply(null, Array(length));

describe('min-max-range', () => {
describe('bottomLeft', () => {
it('throws an error if input is no two-dimensional range', () => {
Expand Down Expand Up @@ -335,6 +338,59 @@ describe('min-max-range', () => {
});
});

describe('shift', () => {
it('throws an error if input is no range', () => {
for (const INVALID_RANGE of INVALID_RANGES) {
expect(() => {
shift(INVALID_RANGE, 3);
}).toThrowError();
}
});

it('throws an error if range is multi-dimensional and delta has unequal length', () => {
expect(() => {
shift(
MULTI_DIM_RANGE,
arrayOfLength(MULTI_DIM_RANGE.length + 1).map(() => 1),
);
}).toThrowError();
});

it('throws an error if range is multi-dimensional and delta is no number or array of numbers', () => {
expect(() => {
shift(
MULTI_DIM_RANGE,
arrayOfLength(MULTI_DIM_RANGE.length)
.map(() => 'foo') as unknown as number[],
);
}).toThrowError();
});

it('returns empty range for empty range', () => {
expect(shift(EMPTY_RANGE, 7)).toEqual(EMPTY_RANGE);
});

it('shifts values of one-dimensional range', () => {
expect(shift(RANGE_1D, -2)).toEqual([
RANGE_1D[0] - 2,
RANGE_1D[1] - 2,
]);
});

it('shifts values of multi-dimensional range', () => {
for (const MULTI_DIM_RANGE of MULTI_DIM_RANGES) {
expect(shift(MULTI_DIM_RANGE, 5))
.toEqual(MULTI_DIM_RANGE.map(r => [r[0] + 5, r[1] + 5]));
expect(
shift(
MULTI_DIM_RANGE,
arrayOfLength(MULTI_DIM_RANGE.length).map((_, i) => -2 * i),
)
).toEqual(MULTI_DIM_RANGE.map((r, i) => [r[0] - 2 * i, r[1] - 2 * i]));
}
});
});

describe('sort', () => {
it('throws an error if input is no range', () => {
for (const INVALID_RANGE of INVALID_RANGES) {
Expand Down
2 changes: 2 additions & 0 deletions test/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export const INVALID_RANGES: Range[] = [
'foo' as unknown as Range,
null as unknown as Range,
undefined as unknown as Range,
false as unknown as Range,
true as unknown as Range,
[[]] as unknown as Range,
{ length: 3 } as unknown as Range,
((x: any) => x) as unknown as Range,
Expand Down
Loading

0 comments on commit 88b1af7

Please sign in to comment.