Similar to as javascript array.
But you would need to specify the types of the parameters and also a return type. Yes you could configure your setup to not care about these things, but then that would defeat the object of using typescript.
export function shuffle<T>(array: T[]): T[] {
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle.
while (currentIndex != 0) {
// Pick a remaining element.
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}
return array;
};
You can get different options of shuffle from this question.
Videos
» npm install @types/shuffle-array
» npm install array-shuffle
Recursive conditional types and variadic tuple types make it possible, but still a bit hairy, to do the sort of type manipulation you're talking about.
I think the following is the same general approach that you take in your answer, although the exact details are a bit different. I tried to make the types as "simple" as I could, although I'll leave it up to others to judge how successful that endeavor was:
I'll define a type function called FirstHalf<T> that returns the first half of a tuple T. If T has an odd number of elements then FirstHalf<T> will end in the middle element as well. So FirstHalf<[1,2,3,4]> should be [1,2], and FirstHalf<[1,2,3,4,5]> should be [1,2,3]:
type FirstHalf<T extends any[]> = T extends [infer F, ...infer M, any] ?
[F, ...FirstHalf<M>] : T
And then LastHalf<T> will return the last half of a tuple type T by producing the the complement of FirstHalf<T>. That means if there are an odd number of elements in T, the middle element will not be in LastHalf<T>. So LastHalf<[1,2,3,4]> should be [3,4], and LastHalf<[1,2,3,4,5]> should be [4,5]:
type LastHalf<T extends any[]> = T extends [...FirstHalf<T>, ...infer E] ? E : never;
Then, Interleave<T, U> will interleave the elements from T with elements from U, so Interleave<[1,2,3],["A","B","C"]> should be [1,"A",2,"B",3,"C"]. If either T or U has elements left they will just be at the end, so Interleave<[1,2],["A","B","C","D","E"]> should be [1,"A",2,"B","C","D","E"]:
type Interleave<T extends any[], U extends any[]> =
T extends [infer H, ...infer R] ? [H, ...Interleave<U, R>] : U
(Note that this is achieved by switching the role of T and U in the recursive call.)
Finally, Shuffle<T> will split T into its FirstHalf and LastHalf and Interleaves the halves together:
type Shuffle<T extends any[]> = Interleave<FirstHalf<[...T]>, LastHalf<[...T]>>;
I didn't change the implementation of your shuffle() function; yes, you need type assertions or the like to convince the compiler that the implementation is safe. There's almost no chance the compiler would be able to follow that any array manipulation will turn T into Shuffle<T>. In the following I use a single call-signature overloaded function to avoid needing assertions inside the body:
function shuffle<T extends readonly any[]>(array: T): Shuffle<[...T]>;
function shuffle(array: any[]) {
if (array.length === 0 || array.length === 1) {
return array.slice();
}
const rest = array.slice();
const middle = rest.splice(Math.ceil(array.length / 2), 1)[0];
const first = rest.shift();
return [first, middle, ...shuffle(rest)];
};
And let's test it:
const shuffled = shuffle([1, 2, 3, 4, 5, 6, 7, 8] as const);
// const shuffled: [1, 5, 2, 6, 3, 7, 4, 8]
console.log(shuffled); // [1, 5, 2, 6, 3, 7, 4, 8]
const oddShuffled = shuffle([1, 2, 3, 4, 5, 6, 7] as const);
// const oddShuffled: [1, 5, 2, 6, 3, 7, 4]
console.log(oddShuffled); // [1, 5, 2, 6, 3, 7, 4]
Yep, looks good!
Playground link to code
As promised, here's the solution I came up with.
type SplitEven<T extends readonly any[], Start extends any[] = [], End extends any[] = []> =
T['length'] extends 0 | 1 ? [[...Start, ...T], End] :
T extends readonly [infer S, ...infer M, infer E] ? SplitEven<M, [...Start, S], [E, ...End]> :
never;
type Shuffle<T extends readonly any[]> =
T['length'] extends 0 | 1
? T
: SplitEven<T> extends infer A
? A extends readonly [readonly [infer AH, ...infer AR], readonly [infer BH, ...infer BR]]
? [AH, BH, ...Shuffle<[...AR, ...BR]>]
: never
: never;
function shuffle<T extends readonly any[]>(array: T): Shuffle<T> {
if (array.length === 0 || array.length === 1) {
return array.slice() as Shuffle<T>;
}
const rest = array.slice();
const middle = rest.splice(Math.ceil(array.length / 2), 1)[0];
const first = rest.shift();
return [first, middle, ...shuffle(rest)] as Shuffle<T>;
}
const array = [1, 2, 3, 4, 5, 6, 7, 8] as const;
const shuffled = shuffle(array);
type Shuffled = typeof shuffled; // [1, 5, 2, 6, 3, 7, 4, 8]
console.log(shuffled);
TypeScript playground
Both the function and the type work with odd-length arrays, too.
The only thing left is figure out whether I can avoid the as Shuffle<T> in the return statements. I suspect the second one is beyond TSC's capability since it would need to know how rest, middle, and first were determined, but I'm surprised the first one wasn't inferred.