You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

保持函数式不可变性,避免冒泡排序中的额外slice()调用

优化不可变冒泡排序的类型与性能问题

你的问题本质是类型标注与实际数组实例不匹配导致的多余slice()调用——内部排序过程中创建的数组本身就是普通可变数组(通过[...arr]生成),只是被你标注成了ReadonlyArray<T>,最后用slice()只是为了做类型转换,完全没必要。

下面给两种优化方案,都能去掉末尾的slice():

方案一:调整内部函数的类型标注(更清晰)

直接让内部函数返回普通数组类型T[],而不是ReadonlyArray<T>,这样整个流程的类型更贴合实际创建的数组:

export default function bubbleSort<T>(array: ReadonlyArray<T>, compare: (a: T, b: T) => number): T[] {
  // swap直接返回T[],因为newArr是通过spread创建的普通数组
  const swap = (arr: ReadonlyArray<T>, i: number, j: number): T[] => {
    const newArr = [...arr];
    [newArr[i], newArr[j]] = [newArr[j], newArr[i]];
    return newArr;
  };

  // bubblePass返回[T[], boolean],适配swap的返回类型
  const bubblePass = (arr: ReadonlyArray<T>, n: number): [T[], boolean] => {
    return arr.slice(1, n).reduce(
      ([accArray, swapped], _, i) => {
        if (compare(accArray[i], accArray[i + 1]) > 0) {
          return [swap(accArray, i, i + 1), true];
        }
        // 初始accArray是传入的arr,断言为T[]是安全的,因为我们调用时传入的是普通数组
        return [accArray as T[], swapped];
      },
      // 初始值把arr断言为T[],因为sort函数传入的初始arr是[...array](普通数组)
      [arr as T[], false] as [T[], boolean],
    );
  };

  // sort函数接收和返回都是T[],类型统一
  const sort = (arr: T[], n: number): T[] => {
    if (n <= 1) return arr;
    const [newArr, swapped] = bubblePass(arr, n);
    return swapped ? sort(newArr, n - 1) : newArr;
  };

  // 初始传入的是[...array],本身就是T[],直接返回sort结果即可
  return sort([...array], array.length);
}

方案二:安全的类型断言(更简洁)

如果你不想修改内部函数的类型标注,直接在最后返回时用类型断言即可——因为sort返回的ReadonlyArray<T>实际上是普通数组实例(所有内部创建的数组都是通过[...arr]生成的),所以断言为T[]是完全安全的:

export default function bubbleSort<T>(array: ReadonlyArray<T>, compare: (a: T, b: T) => number): T[] {
  // 保留你原来的内部函数实现,只修改最后一行
  const swap = (arr: ReadonlyArray<T>, i: number, j: number): ReadonlyArray<T> => {
    const newArr = [...arr];
    [newArr[i], newArr[j]] = [newArr[j], newArr[i]];
    return newArr;
  };

  const bubblePass = (arr: ReadonlyArray<T>, n: number): [ReadonlyArray<T>, boolean] => {
    return arr.slice(1, n).reduce(
      ([accArray, swapped], _, i) => {
        if (compare(accArray[i], accArray[i + 1]) > 0) {
          return [swap(accArray, i, i + 1), true];
        }
        return [accArray, swapped];
      },
      [arr, false] as [ReadonlyArray<T>, boolean],
    );
  };

  const sort = (arr: ReadonlyArray<T>, n: number): ReadonlyArray<T> => {
    if (n <= 1) return arr;
    const [newArr, swapped] = bubblePass(arr, n);
    return swapped ? sort(newArr, n - 1) : newArr;
  };

  // 用类型断言替代slice(),去掉多余复制
  return sort([...array], array.length) as T[];
}

为什么这两种方案可行?

  • 函数式不可变性要求的是不修改输入数组,而内部创建的新数组本身可以是可变类型——我们只是在排序过程中不修改这些新数组,而是每次返回新的副本,完全符合不可变原则。
  • 类型层面上,ReadonlyArray<T>是T[]的父类型,普通数组可以被赋值给ReadonlyArray<T>,但反过来需要断言(因为TS不知道你不会修改它),而我们这里明确知道返回的数组是新创建的,不会被内部修改,所以断言是安全的。

内容的提问来源于stack exchange,提问作者Tom Fan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 16:31:13