Oasis's Cloud

一个人的首要责任,就是要有雄心。雄心是一种高尚的激情,它可以采取多种合理的形式。
—— 《一个数学家的辩白》

泛型数据结构

作者:oasis


泛型数据结构需要有通用的处理方式,迭代器模式提供了统一的数据处理接口,从而更好的处理泛型数据结构。

什么是泛型数据结构

泛型数据结构是指可以存储不同类型数据的数据结构,例如数组、链表、集合、映射等。这些数据结构通过泛型参数来指定存储的元素类型,使得同一个数据结构可以处理不同类型的数据。

// 不同类型的数组
const numbers: number[] = [1, 2, 3]
const strings: string[] = ['a', 'b', 'c']
const users: User[] = [{ name: 'Alice' }, { name: 'Bob' }]

// 泛型集合
class Set<T> {
    private items: T[] = []
    add(item: T): void {
        this.items.push(item)
    }
}

为什么需要通用的处理方式

不同的数据结构有不同的内部实现和访问方式。如果没有统一的接口,我们需要为每种数据结构编写特定的处理代码:

// 没有统一接口的问题
function processArray<T>(arr: T[]): void {
    for (let i = 0; i < arr.length; i++) {
        console.log(arr[i])
    }
}

function processSet<T>(set: Set<T>): void {
    // Set 没有索引访问,需要不同的处理方式
    for (const item of set) {
        console.log(item)
    }
}

function processMap<K, V>(map: Map<K, V>): void {
    // Map 需要遍历键值对,又是另一种方式
    for (const [key, value] of map) {
        console.log(key, value)
    }
}

这种方式的问题在于: - 每种数据结构都需要单独的处理逻辑 - 代码重复,难以维护 - 无法编写通用的算法函数

迭代器模式:统一的数据处理接口

迭代器模式提供了一种统一的方式来遍历不同的数据结构,无论其内部实现如何。在 TypeScript 中,迭代器模式通过 Iterable 接口和 Iterator 接口实现。

Iterable 接口

实现了 Iterable 接口的对象可以被 for...of 循环遍历:

interface Iterable<T> {
    [Symbol.iterator](): Iterator<T>
}

Iterator 接口

Iterator 接口定义了遍历数据的方式:

interface Iterator<T> {
    next(): IteratorResult<T>
}

interface IteratorResult<T> {
    value: T
    done: boolean
}

TypeScript 中的迭代器实现

数组的迭代器

数组天然实现了 Iterable 接口:

const arr = [1, 2, 3]

// 使用 for...of 遍历
for (const item of arr) {
    console.log(item)  // 1, 2, 3
}

// 手动使用迭代器
const iterator = arr[Symbol.iterator]()
console.log(iterator.next())  // { value: 1, done: false }
console.log(iterator.next())  // { value: 2, done: false }
console.log(iterator.next())  // { value: 3, done: false }
console.log(iterator.next())  // { value: undefined, done: true }

自定义迭代器

我们可以为自定义的数据结构实现迭代器:

class NumberRange implements Iterable<number> {
    constructor(
        private start: number,
        private end: number
    ) {}

    [Symbol.iterator](): Iterator<number> {
        let current = this.start
        const end = this.end

        return {
            next(): IteratorResult<number> {
                if (current <= end) {
                    return { value: current++, done: false }
                }
                return { value: undefined, done: true }
            }
        }
    }
}

// 使用自定义迭代器
const range = new NumberRange(1, 5)
for (const num of range) {
    console.log(num)  // 1, 2, 3, 4, 5
}

生成器函数实现迭代器

使用生成器函数可以更简洁地实现迭代器:

class NumberRange {
    constructor(
        private start: number,
        private end: number
    ) {}

    *[Symbol.iterator](): Generator<number> {
        for (let i = this.start; i <= this.end; i++) {
            yield i
        }
    }
}

const range = new NumberRange(1, 5)
for (const num of range) {
    console.log(num)  // 1, 2, 3, 4, 5
}

迭代器模式的优势

1. 统一的遍历接口

通过迭代器模式,我们可以用相同的方式处理不同的数据结构:

// 通用的处理函数
function processAll<T>(iterable: Iterable<T>): void {
    for (const item of iterable) {
        console.log(item)
    }
}

// 可以处理任何实现了 Iterable 接口的数据结构
processAll([1, 2, 3])                    // 数组
processAll(new Set([1, 2, 3]))           // 集合
processAll(new Map([['a', 1], ['b', 2]])) // 映射
processAll(new NumberRange(1, 5))        // 自定义数据结构

2. 延迟计算

迭代器可以按需生成值,而不是一次性生成所有值:

function* fibonacci(): Generator<number> {
    let a = 0, b = 1
    while (true) {
        yield a
        ;[a, b] = [b, a + b]
    }
}

const fib = fibonacci()
console.log(fib.next().value)  // 0
console.log(fib.next().value)  // 1
console.log(fib.next().value)  // 1
console.log(fib.next().value)  // 2
// 只在需要时才计算下一个值

3. 组合和转换

迭代器可以轻松组合和转换:

// 映射转换
function* map<T, U>(
    iterable: Iterable<T>,
    fn: (item: T) => U
): Generator<U> {
    for (const item of iterable) {
        yield fn(item)
    }
}

// 过滤
function* filter<T>(
    iterable: Iterable<T>,
    predicate: (item: T) => boolean
): Generator<T> {
    for (const item of iterable) {
        if (predicate(item)) {
            yield item
        }
    }
}

// 组合使用
const numbers = [1, 2, 3, 4, 5]
const doubled = map(numbers, x => x * 2)
const evens = filter(doubled, x => x % 2 === 0)

for (const num of evens) {
    console.log(num)  // 4, 8
}

总结

迭代器模式为泛型数据结构提供了统一的数据处理接口,使得我们可以:

通过迭代器模式,我们可以在保持类型安全的同时,编写更加通用和可复用的代码。