# 34286-Take

# 题目描述

实现 Take<N, Arr>,从元组 Arr 中取头 N 个元素;若 N 是负数,则取末尾 |N| 个。

type T0 = Take<2, [1, 2, 3]>; // [1, 2]
type T1 = Take<3, ['1', 2, true, false]>; // ['1', 2, true]
type T2 = Take<-2, [1, 2, 3]>; // [2, 3]
type T3 = Take<0, [1, 2, 3]>; // []
type T4 = Take<5, [1, 2, 3]>; // [1, 2, 3]
type T5 = Take<3, []>; // []

# 分析

分两种情况:

  1. N ≥ 0:从头往后拆,拆到拿满 N 个或元组空为止。
  2. N < 0:等价于"取末尾 |N| 个"——可以对元组先 reverse、取头 |N| 个、再 reverse 回来;或者直接用 [...infer Init, infer Last] 从末尾拆。

判断 N 是不是负数最稳的办法是 `${N}` extends `-${infer M extends number}`——既一步拿到绝对值,又能把 |N| 送进正数分支。

# 题解

type TakeHead<
  N extends number,
  Arr,
  Acc extends unknown[] = [],
> = Acc['length'] extends N
  ? Acc
  : Arr extends [infer F, ...infer R]
  ? TakeHead<N, R, [...Acc, F]>
  : Acc;

type TakeTail<
  N extends number,
  Arr,
  Acc extends unknown[] = [],
> = Acc['length'] extends N
  ? Acc
  : Arr extends [...infer Init, infer L]
  ? TakeTail<N, Init, [L, ...Acc]>
  : Acc;

type Take<N extends number, Arr> = `${N}` extends `-${infer M extends number}`
  ? TakeTail<M, Arr>
  : N extends number
  ? TakeHead<N, Arr>
  : never;

解读:

  • TakeHead<N, Arr, Acc>:累加器 Acc 每轮 push 头部元素,长度到 N 就停。如果元组先空了就返回 Acc(处理 N > 元组长度 的情况)。
  • TakeTail<N, Arr, Acc>:从 Arr 末尾拆元素,每轮把末尾元素 头插Acc,保持最终顺序正确。
  • 主体根据字符串前缀区分正负号:负号就走 TakeTail 并把 |N| 丢进去,正号走 TakeHead

# 验证

type cases = [
  Take<2, [1, 2, 3]>, // [1, 2]
  Take<3, ['1', 2, true, false]>, // ['1', 2, true]
  Take<-2, [1, 2, 3]>, // [2, 3]
  Take<0, [1, 2, 3]>, // []
  Take<5, [1, 2, 3]>, // [1, 2, 3]
  Take<3, []>, // []
];

# 知识点

  • `${N}` extends `-${infer M extends number}` 既能判断负数又能一步拿到绝对值,是所有带负数参数题目的通用入口,见 冷门-字面量类型和基础类型
  • TakeTail 里的 [L, ...Acc](头插)和 TakeHead 里的 [...Acc, F](尾插)顺序相反——这对应"从哪端拆、累加器朝哪个方向生长",在所有元组题里都要想清楚。
  • Acc['length'] extends N 作为循环出口是元组递归的标配,见 进阶-计数-加减乘除
Last Updated: 2026/4/19 03:05:53