00472-hard-tuple-to-enum-object

Back

type Enum<T extends readonly string[], N extends boolean = false> = {
    readonly [P in keyof T as P extends `${infer _ extends number}` ? Capitalize<T[P]> : never]: 
    N extends true 
    ? P extends `${infer U extends number}` ? U : never
    : T[P]
  }

Solution by alex-altay #35184

type Enum<T extends readonly string[], N extends boolean = false> = {
  readonly [P in keyof T as P extends `${number}` ? Capitalize<T[P]> : never]: N extends true ? P extends `${infer R extends number}` ? R : never : T[P]
}

Solution by 2083335157 #35034

// T extends readonly 一定要加readonly,那么才是字面量类型
type MapKey<T extends readonly unknown[]> = T extends readonly [any, ...infer rest] ? MapKey<rest> | rest['length'] : never
type Enum<T extends readonly string[], N extends boolean = false> = {
  readonly [K in MapKey<T> as Capitalize<T[K]>]: N extends true ? K : T[K]
}

Solution by ouzexi #34225

// ============= Your Code Here =============

type GetIndex<T extends readonly any[], S extends string> = T extends readonly [... infer R, infer L] ?
  L extends S ? R["length"] : GetIndex<R, S>
  : never

type Enum<T extends readonly string[], N extends boolean = false> = N extends true ?
  {
    readonly [P in T[number]as Capitalize<P>]: GetIndex<T, P>
  }
  : {
    readonly [P in T[number]as Capitalize<P>]: P
  }

Solution by Steven4857 #33064

type Copy<T> = {
  [P in keyof T]: T[P];
};

type Wrap<
  T extends readonly string[],
  N extends boolean = false,
  Count extends 1[] = []
> = T extends readonly [
  infer L extends string,
  ...infer R extends readonly string[]
]
  ? Readonly<Record<Capitalize<L>, N extends true ? Count["length"] : L>> &
      Wrap<R, N, [...Count, 1]>
  : {};

type Enum<T extends readonly string[], N extends boolean = false> = Copy<Wrap<T, N>>;

Solution by Vampirelee #32620

type Enum<
  T extends readonly string[],
  N extends boolean = false,
  C extends 0[] = []
> = T extends readonly [infer F, ...infer R extends readonly string[]]
  ? Omit<
      {
        readonly [K in F as Capitalize<K & string>]: N extends true
          ? C["length"]
          : F;
      } & Enum<R, N, [...C, 0]>,
      never
    >
  : {};

Solution by vangie #32323

type EnsureArray<T, R = string> = T extends R[] ? T : never;

type Enum<
  T extends readonly string[],
  B extends boolean = false,
  R extends Readonly<Record<string, any>> = Readonly<{}>,
  Index extends unknown[] = []
> = T extends readonly [infer F, ...infer Rest]
  ? Enum<
      Readonly<EnsureArray<Rest>>,
      B,
      Readonly<
        R & {
          [K in F & string as Capitalize<K>]: B extends true
            ? Index["length"]
            : K;
        }
      >,
      [...Index, unknown]
    >
  : R;

Solution by gearonixx #31474

思路和17一样是递归,从第一个开始递归,然后通过额外传递一个从零开始的数组来取长度得到index,每次递归到下一项把数组长度+1。

type Indexof<
  T extends readonly unknown[],
  Key,
  LengthArr extends unknown[] = []
> = T extends readonly[infer First, ...infer Rest]
  ? Key extends First
    ? LengthArr["length"]
    : Indexof<Rest, Key, [...LengthArr, unknown]>
  : never;

type Enum<T extends readonly string[], N extends boolean = false> = {
  readonly [Key in T[number] as Capitalize<Key>]: N extends false ? Key : Indexof<T,Key>;
};

Solution by GrinZero #30621

type Capitalize<S extends string> = S extends `${infer C}${infer Rest}` ? `${Uppercase<C>}${Rest}` :S

type ParseInt<T extends string,Acc extends number[]= []> = Equal<`${Acc['length']}`,T> extends true ? Acc['length'] : ParseInt<T,[...Acc,0]> 

type GetIndex<T,K> = ParseInt<Exclude<{[Key in keyof T as T[Key]]:Key}[K],number>>

type Enum<T extends readonly string[], N extends boolean = false> =  {
    readonly [Key in T[number] as Capitalize<Key>] :N extends false ?  Key : GetIndex<T,Key> 
}

Solution by idebbarh #30437

type GetTupleUntilTarget<T extends readonly string[], S extends string> =
  T extends readonly [infer F, ...infer R extends readonly string[]]
    ? S extends F
      ? []
      : [F, ...GetTupleUntilTarget<R, S>]
    : []

type Enum<T extends readonly string[], N extends boolean = false> =
  N extends false
    ? {
        readonly [P in T[number] as Capitalize<P>]: P
      }
    : {
        readonly [K in T[number] as Capitalize<K>]: GetTupleUntilTarget<T, K>['length']
      }

Solution by jazelly #27909

type UseIndex<T extends readonly unknown[], k extends string, U extends unknown[] = []> = T extends [infer L, ...infer R] ? [L] extends [k] ? U['length']
  : UseIndex<R, k, [...U, unknown]> : -1

type Enum<T extends readonly any[], B extends boolean = false> = {
  +readonly [K in T[number]]: B extends true ? UseIndex<T, K> : K
}

Solution by wuxin0011 #27568

type Enum<T extends readonly string[], N extends boolean = false> = {
  readonly [I in keyof T as I extends `${number}` ? Capitalize<T[I]> : never]: N extends true ? StringToNumber<I> : T[I]
}

type StringToNumber<S> = S extends `${infer N extends number}` ? N : never

Solution by Pixoll #26922

type Enum<
  T extends readonly string[],
  N extends boolean = false
 > = 
  T extends readonly [
    ...infer Init extends string[],
    infer Last extends string
  ]
    ? Omit<
        { 
          readonly [K in Capitalize<Last>]: 
            N extends true
              ? Init["length"] 
              : Last 
        } & Enum<Init, N>,
        never
      >
    : {}

Solution by dsvictor94 #26334

type Enum<T extends readonly string[], N extends boolean = false> =
  { readonly [P in keyof T as P extends `${number}` ? Capitalize<T[P]> : never]:
    N extends true ?
    P extends `${infer I extends number}` ? I : never :
    T[P] };

// old way
// type IndexOf<T extends readonly any[], U, _Counter extends any[] = []> = T extends readonly [infer F, ...infer R] ? (F extends U ? _Counter[`length`] : IndexOf<R, U, [..._Counter, F]>) : -1;
// type IsUniqueKeys<T extends readonly string[]> = T extends readonly [infer F extends string, ...infer R extends string[]] ? (-1 extends IndexOf<R, Capitalize<F>> & IndexOf<R, Uncapitalize<F>> ? IsUniqueKeys<R> : false) : true;

// type Enum<T extends (IsUniqueKeys<T> extends true/*Check Unique(optional)*/ ? readonly string[] : never), N extends boolean = false> = Readonly<{ [P in T[number]as Capitalize<P>]: N extends true ? IndexOf<T, P> : P }>;

Solution by E-uler #24780

type Enum<T extends readonly string[], N extends boolean = false> = {
  readonly [K in keyof T as K extends `${number}` ? Capitalize<T[K]> : never]: 
    N extends false ? T[K] : K extends `${infer I extends number}` ? I : never
}

Solution by flavianh #24718

// 你的答案


type TupleToIndex<T extends readonly any[]> = T extends readonly [
  infer F,
  ...infer O
] ? TupleToIndex<O>| O['length']
:never 

type Enum<T extends readonly string[], N extends boolean = false> = {
 readonly [ K in TupleToIndex<T> as ​Capitalize<T[K]>]: N extends true? K : T[K]
}

Solution by walker-hzx #24676

// 你的答案
type Enum<
  T extends readonly string[], 
  N extends boolean = false       
> = {
  [
    Key in keyof T 
      as Key extends `${infer I extends number}`
    ? Capitalize<T[I]>
        : never
  ]: N extends false
    ? T[Key]
    : Key extends `${infer I extends number}`
      ? I
      : never
}

Solution by jxhhdx #23915

// your answers
type Enum<
  T extends readonly string[],
  B extends boolean = false,
> = {
  readonly [
      Key in keyof T as Key extends `${infer I extends number}`
        ? Capitalize<T[I]>
        : never
  ]: B extends false
    ? T[Key]
    : Key extends `${infer I extends number}`
      ? I
      : never
}

Solution by snakeUni #23589

type TupleToEnumObject<
	T extends any[],
	S extends boolean = false,
	Result extends object = {},
	All extends any[] = []
> = T extends [infer F extends string | number | symbol, ...infer R]
	? TupleToEnumObject<
			R,
			S,
			Result & {
				readonly [K in F extends string ? Capitalize<F> : F]: S extends false
					? K extends T[number]
						? K
						: K extends string
						? Uncapitalize<K>
						: K
					: All['length']
			},
			[...All, 0]
	  >
	: Result

Solution by TKBnice #22864

type Enum<
  T extends readonly string[],
  B extends boolean = false,
> = {
  readonly [
      Key in keyof T as Key extends `${infer I extends number}`
        ? Capitalize<T[I]>
        : never
  ]: B extends false
    ? T[Key]
    : Key extends `${infer I extends number}`
      ? I
      : never
}

Solution by drylint #22259

type Merge<T,U> = {
  [key in keyof (T&U)]: key extends keyof T ? T[key] : key extends keyof U ? U[key] : never
};
type EnumAttribute<T extends string, N extends boolean, I extends number> = {
  readonly [key in Capitalize<T>]: N extends true ? I : T
};

type Enum<T extends readonly string[], N extends boolean = false, C extends unknown[]=[]> = 
[...T] extends [infer F extends string, ...infer Rest extends readonly string[]] ?
 Merge<EnumAttribute<F, N, C['length']>, Enum<Rest, N, [...C, 0]>> : {};

Solution by Karamuto #22000

type Enum<T extends readonly string[], N extends boolean = false> = {
  readonly [K in keyof T as K extends `${infer F extends number}` ? Capitalize<T[K]> : never]: 
    N extends true ? K extends `${infer F extends number}` ? F : never
                   : T[K];
}

Solution by yeyunjianshi #21757

type MergeProperty<T extends Record<PropertyKey, any>> = { [key in keyof T]: T[key] };

type Enum<T extends readonly string[], N extends boolean = false, Arr extends number[] = []> = MergeProperty<
  T extends readonly [
    infer First extends string,
    ...infer Rest extends string[]
  ]
    ? { readonly [Key in Capitalize<First>]: N extends true ? Arr['length'] : First } & Enum<Rest, N, [...Arr, 1]>
    : {}
>;

// TEST
const OperatingSystem = ['macOS', 'Windows', 'Linux'] as const;
const Command = ['echo', 'grep', 'sed', 'awk', 'cut', 'uniq', 'head', 'tail', 'xargs', 'shift'] as const;

type R1 = Enum<[]>;
type R2 = Enum<typeof OperatingSystem>;
type R3 = Enum<typeof OperatingSystem, true>;
type R4 = Enum<typeof Command, true>;
type R5 = Enum<typeof Command>;

Solution by zqiangxu #21624

type Merge<T extends Record<PropertyKey, any>> = { [K in keyof T]: T[K] }
type Enum<
  T extends readonly string[],
  N extends boolean = false,
  E extends object = {},
  I extends unknown[] = []
> = T extends readonly [infer F extends string, ...infer R extends string[]]
  ? N extends true
    ? Merge<{
      readonly [P in Capitalize<F>]: I['length']
    } & Enum<R, N, E, [...I, unknown]>>
    : Merge<{
      readonly [P in Capitalize<F>]: F
    } & Enum<R, N, E>>
  : E

Solution by milletlovemouse #21560

// your answers
type StrToNum<S extends string,A extends any[] = []> = `${A['length']}` extends S ? A['length'] : A extends [...infer Rest] ? StrToNum<S,[...Rest,1]> : A
type Enum<T extends readonly string[], N extends boolean = false> = {
  readonly [K in keyof T as T[K] extends string ? Capitalize<T[K]> : never] : N extends false ? T[Exclude<K,number>] : StrToNum<Exclude<K,number>>
} 

Solution by YqxLzx #21045

// TODO
type PascalCase<T extends string> = Capitalize<T>

type StringToNumber<T> = T extends `${infer N extends number}` ? N : never

type Enum<T extends readonly string[], N extends boolean = false> = {
  [P in keyof T as T[P] extends string ? PascalCase<T[P]> : never]: 
    N extends true 
      ? StringToNumber<P> 
      : T[StringToNumber<P>]
}

This solution passes all cases, though PascalCase is not fully implemented, but I think it's not the focus of this question.

Solution by zhaoyao91 #20619

// your answers
type GetElmIdx<T extends readonly any[], K, Ret extends any[] = []> = T extends readonly [infer First, ...infer Rest] ? Equal<First, K> extends true ? Ret['length'] : GetElmIdx<Rest,K, [...Ret, unknown]> : Ret['length']

type Enum<T extends readonly string[], N extends boolean = false> = {
  readonly [K in T[number]as Capitalize<K>]: N extends false ? K : GetElmIdx<T, K>
}

Solution by Rebornjiang #20599

type FindIndexInArray<key extends string, T extends readonly unknown[], N extends any[] = []> = [...T] extends [infer F, ...infer R]
  ? key extends F
    ? N['length']
    : FindIndexInArray<key, R, [...N, 1]>
  : -1;

type Enum<T extends readonly string[], N extends boolean = false> = {
  readonly [key in T[number] as key extends `${infer F}${infer R}` ? `${Uppercase<F>}${R}` : never]: N extends true ? FindIndexInArray<key, T> : key;
};

Solution by CaoXueLiang #19039

type Enum<T extends readonly string[], N extends boolean = false, Count extends any[] = [], Result extends Record<string, number | string> = {}> =
  T extends readonly [infer S extends string, ...infer R extends string[]]
    ? Enum<R, N, [...Count, any], Result & Record<Capitalize<S>, N extends true ? Count['length'] : S>>
    : { readonly [P in keyof Result]: Result[P] };

Solution by BulatDashiev #16903

// your answers
type Enum<
  T extends readonly string[], 
  N extends boolean = false, 
  R extends {} = {}
> = T extends readonly [...infer Head extends string[], infer Tail extends string]
  ? Enum<Head, N, R & Record<Capitalize<Tail>, N extends true ? Head['length']: Tail>>
  : { readonly [P in keyof R]: R[P]; }
;

Got this idea from astak16, that if I process from the tail, I can get the ordinal number without counting it myself. I feel that mine is the neatest solution among those that I have read.

Solution by alexfung888 #16490