Skip to content

Commit

Permalink
feat: support delete and insert
Browse files Browse the repository at this point in the history
  • Loading branch information
unional committed Jul 8, 2023
1 parent e76c689 commit 036094b
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 64 deletions.
5 changes: 5 additions & 0 deletions .changeset/gorgeous-walls-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"type-plus": minor
---

Support `Delete` and `Insert` for `SplitAt`.
56 changes: 33 additions & 23 deletions type-plus/ts/array/array_index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,35 +25,45 @@ export type IndexAt<
Fail = never,
Upper = A['length'],
Lower = 0
> = IsEqual<
A['length'],
0,
Fail,
Integer<
N,
StrictNumberType<
A['length'],
// A: array
N,
// A: tuple
Negative<
N,
GreaterThan<Abs<N>, A['length']> extends true ? Lower : Subtract<A['length'], Abs<N>>,
GreaterThan<A['length'], N> extends true ? N : Upper
>
>,
// N: number or float
IsAny<
> = IsNever<A, Fail, IndexAt._<A, N, Fail, Upper, Lower>>

export namespace IndexAt {
export type _<
A extends Array<unknown>,
N extends number,
Fail = never,
Upper = A['length'],
Lower = 0
> = IsEqual<
A['length'],
0,
Fail,
Integer<
N,
number,
StrictNumberType<
A['length'],
// A: array
N,
// A: tuple
Negative<
N,
GreaterThan<Abs<N>, A['length']> extends true ? Lower : Subtract<A['length'], Abs<N>>,
GreaterThan<A['length'], N> extends true ? N : Upper
>
>,
// N: number or float
IsAny<
N,
// TODO: handle tuple to union of indexes
N
number,
StrictNumberType<
N,
// TODO: handle tuple to union of indexes
N
>
>
>
>
>
}

/**
* Is N an out of bound index of A.
Expand Down
57 changes: 43 additions & 14 deletions type-plus/ts/array/array_plus.split_at.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,37 @@ import { testType } from '../index.js'
import type { SplitAt } from './array_plus.js'

test('behavior of array.splice(start)', () => {
const a = [1, 2, 3]
const r = a.splice(1)
const a = [1, 2, 3, 4, 5]
const r = a.splice(0)

expect(a).toEqual([1])
expect(r).toEqual([2, 3])
expect(a).toEqual([])
expect(r).toEqual([1, 2, 3, 4, 5])

const a1 = [1, 2, 3, 4, 5]
const r1 = a1.splice(1)

expect(a1).toEqual([1])
expect(r1).toEqual([2, 3, 4, 5])

const a2 = [1, 2, 3]
const a2 = [1, 2, 3, 4, 5]
const r2 = a2.splice(-1)

expect(a2).toEqual([1, 2])
expect(r2).toEqual([3])
expect(a2).toEqual([1, 2, 3, 4])
expect(r2).toEqual([5])
})

test('behavior of array.splice(start) where start is out of bound', () => {
const a1 = [1, 2, 3]
const r1 = a1.splice(5)
const a1 = [1, 2, 3, 4, 5]
const r1 = a1.splice(6)

expect(a1).toEqual([1, 2, 3])
expect(a1).toEqual([1, 2, 3, 4, 5])
expect(r1).toEqual([])

const a2 = [1, 2, 3]
const r2 = a2.splice(-4)
const a2 = [1, 2, 3, 4, 5]
const r2 = a2.splice(-6)

expect(a2).toEqual([])
expect(r2).toEqual([1, 2, 3])
expect(r2).toEqual([1, 2, 3, 4, 5])
})

test('behavior of array.splice(start, deleteCount)', () => {
Expand All @@ -52,7 +58,7 @@ test('behavior of array.splice(start, deleteCount, ...items)', () => {
expect(r2).toEqual([2, 3])
})

it('split array to two', () => {
it('split tuple to two', () => {
testType.equal<SplitAt<[1, 2, 3, 4, 5], 0>, [[], [1, 2, 3, 4, 5]]>(true)
testType.equal<SplitAt<[1, 2, 3, 4, 5], 1>, [[1], [2, 3, 4, 5]]>(true)
testType.equal<SplitAt<[1, 2, 3, 4, 5], 2>, [[1, 2], [3, 4, 5]]>(true)
Expand All @@ -76,3 +82,26 @@ it('split array type to two same array', () => {
testType.equal<SplitAt<number[], 0>, [number[], number[]]>(true)
testType.equal<SplitAt<string[], 1>, [string[], string[]]>(true)
})

it(`split tuple with delete count`, () => {
testType.equal<SplitAt<[1, 2, 3, 4, 5], 2, 2>, [[1, 2, 5], [3, 4]]>(true)

testType.equal<
SplitAt<['angel', 'clown', 'drum', 'mandarin', 'sturgeon'], 3, 1>,
[['angel', 'clown', 'drum', 'sturgeon'], ['mandarin']]
>(true)
})

it('allows delete to go beyond the length of the array', () => {
testType.equal<SplitAt<[1, 2, 3, 4, 5], 4, 1>, [[1, 2, 3, 4], [5]]>(true)
testType.equal<SplitAt<[1, 2, 3, 4, 5], 4, 2>, [[1, 2, 3, 4], [5]]>(true)
})


it('support replace', () => {
testType.equal<SplitAt<[1, 2, 3, 4, 5], 0, 1>, [[2, 3, 4, 5], [1]]>(true)
testType.equal<SplitAt<[1, 2, 3, 4, 5], 0, 1, ['a']>, [['a', 2, 3, 4, 5], [1]]>(true)
testType.equal<SplitAt<[1, 2, 3, 4, 5], 0, 1, ['a', 'b']>, [['a', 'b', 2, 3, 4, 5], [1]]>(true)

testType.equal<SplitAt<[1, 2, 3, 4, 5], 2, 2, ['a', 'b']>, [[1, 2, 'a', 'b', 5], [3, 4]]>(true)
})
75 changes: 52 additions & 23 deletions type-plus/ts/array/array_plus.split_at.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,74 @@
import type { NeverType } from '../never/never_type.js'
import type { IsTuple } from '../tuple/tuple_type.js'
import type { IndexAt } from './array_index.js'
import type { ArrayType } from './array_type.js'

/**
* Splits an array into two at the specified `Index`.
* ⚗️ *transform*
*
* Splits array or tuple `A` into two at the specified `Index`.
*
* If the `Index` is out of bounds,
* it will set to the boundary value, similar to `array.splice()`.
* it will set to the boundary value.
*
* It is the type level `splice()`.
*
* @example
* ```ts
* SplitAt<[1, 2, 3, 4, 5], 0> // [[], [1, 2, 3, 4, 5]]
* SplitAt<[1, 2, 3, 4, 5], -5> // [[], [1, 2, 3, 4, 5]]
*
* SplitAt<[1, 2, 3, 4, 5], 2> // [[1, 2], [3, 4, 5]]
* SplitAt<[1, 2, 3, 4, 5], -3> // [[1, 2], [3, 4, 5]]
*
* SplitAt<[1, 2, 3, 4, 5], 4> // [[1, 2, 3, 4], [5]]
* SplitAt<[1, 2, 3, 4, 5], -1> // [[1, 2, 3, 4], [5]]
* SplitAt<[1, 2, 3, 4, 5], 2, 2> // [[1, 2, 5], [3, 4]]
*
* SplitAt<[1, 2, 3, 4, 5], 5> // [[1, 2, 3, 4, 5], []]
* SplitAt<[1, 2, 3, 4, 5], 2, 2, ['a', 'b']> // [[1, 2, 'a', 'b', 5], [3, 4]]
*
* // out of bound resets to boundary
* SplitAt<[1, 2, 3, 4, 5], 6> // [[1, 2, 3, 4, 5], []]
* SplitAt<[1, 2, 3, 4, 5], -6> // [[], [1, 2, 3, 4, 5]]
* ```
*/
export type SplitAt<A extends unknown[], Index extends number> = ArrayType<
export type SplitAt<
A extends unknown[],
Index extends number,
DeleteCount extends number | never = never,
Insert extends unknown[] | never = never
> = ArrayType<
A,
[A, A],
ArraySplitAtDevice<A, [], IndexAt<A, Index>>
SplitAt._<A, [], [], IndexAt._<A, Index>, DeleteCount, Insert>
>

/**
* Splits an array into two at the specified `Index`.
* The device does not work on negative index nor out of bound index.
*/
export type ArraySplitAtDevice<
A extends unknown[],
B extends unknown[],
Index extends number
> = Index extends B['length']
? [B, A]
: A extends [infer Head, ...infer Tail]
? ArraySplitAtDevice<Tail, [...B, Head], Index>
: never
export namespace SplitAt {
export type _<
A extends unknown[],
B extends unknown[],
C extends unknown[],
Index extends number,
DeleteCount,
Insert extends unknown[],
> = 0 extends A['length']
? IsTuple<Insert, [[...Insert, ...B], C], [B, C]>
: (Index extends B['length']
? _D<A, B, C, DeleteCount, Insert>
: (A extends [infer Head, ...infer Tail]
? _<Tail, [...B, Head], [], Index, DeleteCount, Insert>
: 'unexpected: A does not extends [Head, ...Tail]'))

export type _D<
A extends unknown[],
B extends unknown[],
C extends unknown[],
DeleteCount,
Insert extends unknown[],
> = NeverType<
DeleteCount,
[B, A],
DeleteCount extends C['length']
? IsTuple<Insert, [[...B, ...Insert, ...A], C], [[...B, ...A], C]>
: (A extends [infer Head, ...infer Tail]
? _D<Tail, B, [...C, Head], DeleteCount, Insert>
: IsTuple<Insert, [[...Insert, ...B], C], [B, C]>
)
>
}

28 changes: 24 additions & 4 deletions type-plus/ts/array/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -394,12 +394,28 @@ ArrayPlus.Reverse<[1, 2, 3]> // [3, 2, 1]
### [`ArrayPlus.SplitAt`](./array_plus.split_at.ts#L22)
> `ArrayPlus.SplitAt<A, Index>`
`ArrayPlus.SplitAt<A, Index>`
Splits the array or tuple at `Index`.
⚗️ *transform*
Splits array or tuple `A` into two at the specified `Index`.
If the `Index` is out of bounds,
it will set to the boundary value.
It is the type level `splice()`.
```ts
ArrayPlus.SplitAt<[1, 2, 3], 1> // [[1, 2], [3]]
SplitAt<[1, 2, 3, 4, 5], 2> // [[1, 2], [3, 4, 5]]
SplitAt<[1, 2, 3, 4, 5], -3> // [[1, 2], [3, 4, 5]]

SplitAt<[1, 2, 3, 4, 5], 2, 2> // [[1, 2, 5], [3, 4]]

SplitAt<[1, 2, 3, 4, 5], 2, 2, ['a', 'b']> // [[1, 2, 'a', 'b', 5], [3, 4]]

// out of bound resets to boundary
SplitAt<[1, 2, 3, 4, 5], 6> // [[1, 2, 3, 4, 5], []]
SplitAt<[1, 2, 3, 4, 5], -6> // [[], [1, 2, 3, 4, 5]]
```
### [`ArrayPlus.Some`](./array.some.ts#L23)
Expand Down Expand Up @@ -440,6 +456,10 @@ while some common ones are exposed at top-level.
Here are the list of array methods and their corresponding type-level functions, if available.
✅ means it is implemented.
✴️ means it is implemented with reduced functionality
🧬 means there is a built-in mechanism or type for it.
- ✅ `at`: [`ArrayPlus.At`](#arrayplusat)
- ✅ `concat`: [`Concat` | `ArrayPlus.Concat`](#arrayplusconcat) (`[...A, ...B]`)
- 🚧 `copyWithin`: `CopyWithin<A, Target, Start, End>`
Expand All @@ -464,7 +484,7 @@ Here are the list of array methods and their corresponding type-level functions,
- 🚧 `slice`:
- ✴️ `some`: [`Some` | `ArrayPlus.Some`](#arrayplussome)
- 🚧 `sort`:
- 🚧 `splice`:
- ✴️ `splice`: [`ArrayPlus.SplitAt`](#arrayplussplitat)
- 🧬 `unshift`: `[T, ...A]`
- 🧬 `values`: `keyof A`
Expand Down

0 comments on commit 036094b

Please sign in to comment.