00847-hard-string-join

Back

type Join<P extends string[], D extends string> = P extends [infer R, ...infer rest] ? rest['length'] extends 0 ? R : rest extends string[] ? `${R & string}${D}${Join<rest, D>}` : never : ''
declare function join<D extends string>(delimiter: D): <P extends string[]>(...parts: P) => Join<P, D>

Solution by ouzexi #34253

// 1. 先实现一个类型,这个类型作用将数组`P`中的类型用`D`连接起来
type Join<D extends string, P extends string[]> = P extends [
  infer L extends string,
  ...infer Rest extends string[]
]
  ? `${L}${Rest extends [] ? "" : D}${Join<D, Rest>}`
  : "";
// type Result = Join<'-', ['a', 'b', 'c']>  // a-b-c

// 2. Join函数使用泛型 `D` 来推断分割符字面量, 结果返回一个函数,这个函数需要使用泛型 `List` 来推断参数类型。
declare function join<D extends string>(
  delimiter: D
): <List extends string[]>(...arg: List) => Join<D, List>;

Solution by Vampirelee #32627

type Joined<T, S extends string> = T extends [
  infer F extends string,
  ...infer L extends string[]
]
  ? `${F}${L extends [] ? "" : S}${Joined<L, S>}`
  : "";

declare function join<S extends string>(delimiter: S): <T extends string[]>(...parts: T) => Joined<T,S>

Solution by vangie #32250

type Join<P extends string[], D extends string> = P extends [infer A extends string, ...infer B]
  ? B extends [] ? `${A}` : B extends string[] ? `${A}${D}${Join<B, D>}` : never
  : ''

declare function join<D extends string>(delimiter: D):
  <P extends string[]>(...parts: P) => Join<P, D>;

Solution by chliguiy #31165

type JoinStr<T extends string[], D extends string> = T extends [infer A, ...infer B extends string[]] ? `${A & string}${B['length'] extends 0 ? '' : D}${JoinStr<B, D>}` : ''
declare function join<D extends string>(delimiter: D): <A extends string[]>(...args: A) => JoinStr<A, D>

Solution by dreamluo-plus #30766

type Attach<
  Delimiter extends string, 
  Letters extends string[]
> = Letters extends [infer OnlyItem] 
  ? OnlyItem 
  : Letters extends [infer CurrentLetter extends string, ...infer Rest extends string[]] 
    ? `${CurrentLetter}${Delimiter}${Attach<Delimiter, Rest>}` 
    : ''

declare function join<Delimiter extends string>(
  delimiter: Delimiter
): <Letters extends string[]>(...parts: Letters) => Letters[0] extends undefined 
  ? '' 
  : Letters[1] extends string 
    ? Attach<Delimiter, Letters> : Letters[0]

Solution by simone-paglino #29647

type Join<U extends any[], T extends string> =
  U extends [string, ...infer R] ? `${U[0]}${R extends [] ? '' : T}${Join<R, T>}` : '';
declare function join<T extends string>(delimiter: T): <U extends string[]>(...parts: U) => Join<U, T>;

Solution by smileboyi #28137

type ReadonlyTuple = (string | number)[]

type Join<T extends ReadonlyTuple, U extends string | number, _Acc extends string = ''> =
    T extends [infer Char extends string | number, ...infer Rest extends ReadonlyTuple] ?
        Join<Rest, U, `${_Acc}${Char}${Rest extends [] ? '' : U}`> : _Acc

declare function join<T extends string>(delimiter: T):
    <S extends ReadonlyTuple>(...parts: S) => Join<S, T>

Solution by jjswifty #27540

// your answers

type GetRes<T extends string, Arg extends string[], Res extends string = ''> = Arg extends [infer L extends string, ...infer R extends string[]] ? R['length'] extends 0 ? `${Res}${L}` : GetRes<T, R, `${Res}${L}${T}`> : Res


declare function join<T extends string>(delimiter: T): <P extends string[]>(...parts: P) => GetRes<T,P>

Solution by Mountain-Z #25887

847 - String Join

Many many libraries need functions like this, but I bet you'll be hard pressed to do one that actually implements it in the type system when literals are used. Suddenly, we've hit upon a truly useful challenge.

🎥 Video Explanation

Release Date: 2023-04-17 19:00 UTC

String Join

🔢 Code

// ============= Test Cases =============
import type { Equal, Expect } from './test-utils'

const noCharsOutput = join('-')();
type A1 = typeof noCharsOutput;
type B1 = '';
type C1 = Expect<Equal<A1, B1>>;

const oneCharOutput = join('-')('a');
type A2 = typeof oneCharOutput;
type B2 = 'a';
type C2 = Expect<Equal<A2, B2>>;

const noDelimiterOutput = join('')('a', 'b', 'c');
type A3 = typeof noDelimiterOutput;
type B3 = 'abc';
type C3 = Expect<Equal<A3, B3>>;

const twoCharOutput = join('-')('a', 'b');
type A4 = typeof twoCharOutput;
type B4 = 'a-b';
type C4 = Expect<Equal<A4, B4>>;

const hyphenOutput = join('-')('a', 'b', 'c');
type A5 = typeof hyphenOutput;
type B5 = 'a-b-c';
type C5 = Expect<Equal<A5, B5>>;

const hashOutput = join('#')('a', 'b', 'c');
type A6 = typeof hashOutput;
type B6 = 'a#b#c';
type C6 = Expect<Equal<A6, B6>>;

const longOutput = join('-')(
  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'
);
type A7 = typeof longOutput;
type B7 = 'a-b-c-d-e-f-g-h';
type C7 = Expect<Equal<A7, B7>>;

// ============= Your Code Here =============
// @uid11
type Tuple = readonly string[];

/**
 * Tail<['1', '2', '3']> = ['2', '3'].
 */
type Tail<T extends Tuple> =
  T extends readonly [unknown, ...infer Rest]
  ? Rest
  : [];

/**
 * Join<['1', '2'], " - "> = '1 - 2'.
 * Join<['1'], " - "> = '1'.
 * Join<[], 'x'> = ''.
 */
type Join<
  T extends Tuple,
  Separator extends string
> =
  // handle empty tuple
  T extends readonly []
  ? ''
    // handle tuple with one element
  : T extends readonly [infer Head]
    ? Head
    : `${T[0]}${Separator}${Join<Tail<T>, Separator>}`;

declare function join<
  D extends string
>(delimiter: D):
  <P extends Tuple>(...parts: P) => Join<P, D>;

// ============== Alternatives ==============
// @zongc1001
type Join<
  Parts extends readonly string[],
  Delimiter extends string = ''
> =
  Parts extends [
      infer Head,
      ...infer Tail extends readonly string[]
    ]
  ? Head extends string
    ? Tail extends []
      ? Head
      : `${Head}${Delimiter}${Join<Tail, Delimiter>}`
    : never
  : '';

declare function join<Delimiter extends string>(
  delimiter: Delimiter
): <Parts extends readonly string[]>(
  ...parts: Parts
) => Join<Parts, Delimiter>;

➕ More Solutions

For more video solutions to other challenges: see the umbrella list! https://github.com/type-challenges/type-challenges/issues/21338

Solution by dimitropoulos #25350

declare type Join<T extends string, U extends string[]> = U extends [infer F extends string, ...infer R extends string[]] ? (R extends [] ? F : `${F}${T}${Join<T, R>}`) : ``;

declare function join<T extends string>(delimiter: T): <U extends string[]>(...parts: U) => Join<T, U>;

Solution by E-uler #24904

type Join<T extends string[], U extends string, Acc extends string = ''> = T extends [infer First extends string, ...infer Rest extends string[]]
? Rest extends []
  ? `${Acc}${First}`
  : Join<Rest, U,`${Acc}${First}${U}`>
: Acc

declare function join<T extends string>(delimiter: T): <S extends string[]>(...parts: S) => Join<S, T>

Solution by NeylonR #24449

type Join<P extends string[], D extends string> = 
  P extends [infer P0 extends string, ...infer PRest extends [string, ...string[]]] ? (
    `${P0}${D}${Join<PRest, D>}`
  ) : P[0]

declare function join<D extends string>(delimiter: D): <P extends string[]>(...parts: P) => 
  P extends [] ? '' : Join<P, D>

Solution by BOCbMOU #24196

type StringJoin<
  S extends string[], 
  T extends string, 
  Acc extends string = ''
> = S extends [infer F extends string, ...infer R extends string[]] 
      ? R extends []
        ? `${Acc}${F}`
        : StringJoin<R, T, `${Acc}${F}${T}`> 
      : Acc;
declare function join<T extends string>(delimiter: T): <S extends string[]>(...parts: S) => StringJoin<S, T>

Solution by snakeUni #23735

type Join<T extends string[], By extends string = '', Result extends string = ''> = T extends [
	infer F,
	...infer R
]
	? R extends string[]
    ?   Join<R, By, F extends string ? `${Result}${Result extends '' ? Result : By}${F}` : Result> 
	  : Result
	: Result

Solution by TKBnice #23115

// your answers
type StringJoin<
  S extends string[], 
  T extends string, 
  R extends string = ''
> = S extends [infer F extends string, ...infer Rest extends string[]] 
      ? Rest extends []
        ? `${R}${F}`
        : StringJoin<Rest, T, `${R}${F}${T}`> 
      : R;
declare function join<T extends string>(delimiter: T): <S extends string[]>(...parts: S) => StringJoin<S, T>

Solution by jxhhdx #22857

type StringJoin<S extends string[], D extends string, R extends string = ''> =
  S extends [infer A extends string, ...infer Rest extends string[]]
    ? StringJoin<Rest, D, `${R}${R extends '' ? '' : D}${A}`>
    : R

declare function join<D extends string> (
  delimiter: D
): <S extends string[]>(...parts: S) => StringJoin<S, D>

Solution by drylint #22329

type JoinStr<T extends string, U extends string[], P extends string = ""> = 
  U extends [infer F extends string, ...infer R extends string[]]
    ? JoinStr<T, R, `${P}${P extends "" ? "" : T}${F}`>
    : P;

declare function join<T extends string>(delimiter: T): <U extends string[]>(...parts: U) => JoinStr<T, U>;

Solution by ivbrajkovic #22257

type StringJoin<T extends string, U extends string[]> = U extends [infer First extends string, infer Second extends string, ...infer Rest extends string[]] ? 
`${First}${T}${StringJoin<T,[Second, ...Rest]>}` : U[0] extends undefined ? '' : U[0];

declare function join<T extends string>(delimiter: T): <U extends string[]>(...parts: U) => StringJoin<T,[...U]>;

Solution by Karamuto #22029

type JoinChars<Chars extends string[] = [], JoinChar extends string = ''> =
  Chars['length'] extends 1
    ? Chars[0]
    : Chars extends [infer First extends string, ...infer Rest extends string[]]
    ? `${First}${JoinChar}${JoinChars<Rest, JoinChar>}`
    : '';

declare function join<JoinChar extends string>(
  joinChar?: JoinChar
): <Chars extends string[]>(...params: Chars) => JoinChars<Chars, JoinChar>;

const hyphenOutput2 = join('')('a', 'b', 'c');
const oneCharOutput = join('-')('a');
const hyphenOutput = join('-')('a', 'b', 'c');
const hashOutput = join('#')('a', 'b', 'c');
const twoCharOutput = join('-')('a', 'b');
const longOutput = join('-')('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
const noCharsOutput = join('-')();

Solution by zqiangxu #21704

type Join<D extends string, P extends string[]> = 
  P extends []
    ? ''
    : P extends [infer First]
      ? First
      : P extends [infer First extends string, ...infer Rest extends string[]]
        ? `${First}${D}${Join<D, Rest>}`
        : ''

declare function join<D extends string>(delimiter: D): <P extends string[]>(...parts: P) => Join<D, P>

key point for me: <P extends string[]>(...parts: P) => ... I should put generic type before second function's argument bracket.

Solution by zhaoyao91 #21310

type Join<T extends Array<string>, U extends string | number> = T extends [
  infer H extends string,
  ...infer L extends string[]
]
  ? `${H}${L extends [] ? "" : U}${Join<L, U>}`
  : "";
declare function join<T extends string>(
  delimiter: T
): <R extends string[]>(...parts: R) => Join<R, T>;

Solution by so11y #21228

// your answers
type DeepJoin<T extends string,S> = S extends [infer F,...infer R] ?  F extends string ? R extends [string,...infer R1] ?
`${F}${T}${DeepJoin<T,R>}` : `${F}` : never : ''

declare function join<T extends string>(delimiter: T): <S extends string[]>(...parts: S) => DeepJoin<T,S>

Solution by YqxLzx #21146

// your answers
type JoinStr<D extends string, Vals extends any[], Ret extends string = ''> = Vals extends [infer First extends string,...infer Rest extends string[]] ? Rest extends readonly [] ? `${Ret}${First}`: JoinStr<D, Rest, `${Ret}${First}${D}`>: Ret
// declare function join<D extends string>(delimiter: D): <Vals extends any[]>(...parts: Vals) => JoinStr<D, Vals> // 没有想明白为什么 Vals 为 any[] 的时候,推导出来的时 string 而不是字面量类型? 有 大佬可以帮忙解释下吗 ? type rr = JoinStr<'#', ['a', 'b', 'c']> 这个却是能够正常的推导出来 
declare function join<D extends string>(delimiter: D): <Vals extends string[]>(...parts: Vals) => JoinStr<D, Vals>

Solution by Rebornjiang #20709


type Joined<TDel extends string, TVals> = TVals extends [infer T1 extends string, ...infer TR]
  ? `${T1}${TR extends [] ? '' : TDel}${Joined<TDel, TR>}`
  : '';

declare function join<TDel extends string>(delimiter: TDel): <TVals extends string[]>(...parts: [...TVals]) => Joined<TDel, TVals>;

Solution by mdakram28 #20654

declare function join<SEP extends string>(delimiter: SEP): <T extends string[]>(...parts: T) => Join<T, SEP>

type Join<T extends string[], SEP extends string> = T extends
  [infer F extends string, ...infer O extends string[]]
    ? `${F}${O extends [] ? '' : `${SEP}${Join<O, SEP>}`}`
    : ''

Solution by pengzhanbo #20479

type Join<T extends string, K extends string[], F extends boolean = true> = K extends [infer L extends string, ...infer R extends string[]]
  ? `${F extends true ? '' : T}${L}${Join<T, R, false>}`
  : ''

declare function join<T extends string>(delimiter: T): <K extends string[]>(...parts: K) => Join<T, K>

Solution by chhu1 #20452

type JoinString<T extends string[], TSep extends string> = 
    T extends [infer TFirst extends string, ...infer TRest extends string[]]
    ? `${TFirst}${TRest extends [] ? '' : `${TSep}${JoinString<TRest, TSep>}`}`
    : ''

declare function join<T extends string>(sep: T) : <TArgs extends string[] = []>(...args: TArgs) => JoinString<TArgs, T>

Solution by knazeri #19118

type JoinString<A extends string[], T extends string, Result extends string = ''> = A extends [infer F extends string, ...infer R extends string[]]
  ? JoinString<R, T, `${Result}${Result extends '' ? '' : T}${F}`>
  : Result;

declare function join<T extends string>(delimiter: T): <A extends string[] = []>(...parts: A) => JoinString<A, T>;

Solution by CaoXueLiang #19061

// your answers
type JoinString<Args extends string[] = [], D extends string = '', Result extends string = ''> = Args extends [infer R, ...infer U]
                                                                                                ? U extends string[]
                                                                                                    ? R extends string 
                                                                                                        ? JoinString<U, D, `${Result extends '' ? `${Result}` : `${Result}${D}`}${R}`>
                                                                                                        : JoinString<U, D, `${Result}`>
                                                                                                    : `${Result}`
                                                                                                : Result

declare function join<T extends string>(delimiter: T): <Arr extends string[]>(...parts: Arr) => JoinString<Arr, T>;

Solution by jiaaoMario #17414