11 changed files with 295 additions and 4 deletions
@ -0,0 +1,107 @@ |
|||||
|
import { beforeEach, describe, expect, it } from 'vitest'; |
||||
|
|
||||
|
import { createStack, Stack } from '../stack'; |
||||
|
|
||||
|
describe('stack', () => { |
||||
|
let stack: Stack<number>; |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
stack = new Stack<number>(); |
||||
|
}); |
||||
|
|
||||
|
it('push & size should work', () => { |
||||
|
stack.push(1, 2); |
||||
|
|
||||
|
expect(stack.size).toBe(2); |
||||
|
}); |
||||
|
|
||||
|
it('peek should return top element without removing it', () => { |
||||
|
stack.push(1, 2); |
||||
|
|
||||
|
expect(stack.peek()).toBe(2); |
||||
|
expect(stack.size).toBe(2); |
||||
|
}); |
||||
|
|
||||
|
it('pop should remove and return top element', () => { |
||||
|
stack.push(1, 2); |
||||
|
|
||||
|
expect(stack.pop()).toBe(2); |
||||
|
expect(stack.size).toBe(1); |
||||
|
expect(stack.peek()).toBe(1); |
||||
|
}); |
||||
|
|
||||
|
it('pop on empty stack should return undefined', () => { |
||||
|
expect(stack.pop()).toBeUndefined(); |
||||
|
expect(stack.peek()).toBeUndefined(); |
||||
|
}); |
||||
|
|
||||
|
it('clear should remove all elements', () => { |
||||
|
stack.push(1, 2); |
||||
|
|
||||
|
stack.clear(); |
||||
|
|
||||
|
expect(stack.size).toBe(0); |
||||
|
expect(stack.peek()).toBeUndefined(); |
||||
|
}); |
||||
|
|
||||
|
it('toArray should return a shallow copy', () => { |
||||
|
stack.push(1, 2); |
||||
|
|
||||
|
const arr = stack.toArray(); |
||||
|
arr.push(3); |
||||
|
|
||||
|
expect(stack.size).toBe(2); |
||||
|
expect(stack.toArray()).toEqual([1, 2]); |
||||
|
}); |
||||
|
|
||||
|
it('dedup should remove existing item before push', () => { |
||||
|
stack.push(1, 2, 1); |
||||
|
|
||||
|
expect(stack.toArray()).toEqual([2, 1]); |
||||
|
expect(stack.size).toBe(2); |
||||
|
}); |
||||
|
|
||||
|
it('dedup = false should allow duplicate items', () => { |
||||
|
const s = new Stack<number>(false); |
||||
|
|
||||
|
s.push(1, 1, 1); |
||||
|
|
||||
|
expect(s.toArray()).toEqual([1, 1, 1]); |
||||
|
expect(s.size).toBe(3); |
||||
|
}); |
||||
|
|
||||
|
it('remove should delete all matching items', () => { |
||||
|
stack.push(1, 2, 1); |
||||
|
|
||||
|
stack.remove(1); |
||||
|
|
||||
|
expect(stack.toArray()).toEqual([2]); |
||||
|
expect(stack.size).toBe(1); |
||||
|
}); |
||||
|
|
||||
|
it('maxSize should limit stack capacity', () => { |
||||
|
const s = new Stack<number>(true, 3); |
||||
|
|
||||
|
s.push(1, 2, 3, 4); |
||||
|
|
||||
|
expect(s.toArray()).toEqual([2, 3, 4]); |
||||
|
expect(s.size).toBe(3); |
||||
|
}); |
||||
|
|
||||
|
it('dedup + maxSize should work together', () => { |
||||
|
const s = new Stack<number>(true, 3); |
||||
|
|
||||
|
s.push(1, 2, 3, 2); // 去重并重新入栈
|
||||
|
|
||||
|
expect(s.toArray()).toEqual([1, 3, 2]); |
||||
|
expect(s.size).toBe(3); |
||||
|
}); |
||||
|
|
||||
|
it('createStack should create a stack instance', () => { |
||||
|
const s = createStack<number>(true, 2); |
||||
|
|
||||
|
s.push(1, 2, 3); |
||||
|
|
||||
|
expect(s.toArray()).toEqual([2, 3]); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,103 @@ |
|||||
|
/** |
||||
|
* @zh_CN 栈数据结构 |
||||
|
*/ |
||||
|
export class Stack<T> { |
||||
|
/** |
||||
|
* @zh_CN 栈内元素数量 |
||||
|
*/ |
||||
|
get size() { |
||||
|
return this.items.length; |
||||
|
} |
||||
|
/** |
||||
|
* @zh_CN 是否去重 |
||||
|
*/ |
||||
|
private readonly dedup: boolean; |
||||
|
/** |
||||
|
* @zh_CN 栈内元素 |
||||
|
*/ |
||||
|
private items: T[] = []; |
||||
|
|
||||
|
/** |
||||
|
* @zh_CN 栈的最大容量 |
||||
|
*/ |
||||
|
private readonly maxSize?: number; |
||||
|
|
||||
|
constructor(dedup = true, maxSize?: number) { |
||||
|
this.maxSize = maxSize; |
||||
|
this.dedup = dedup; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @zh_CN 清空栈内元素 |
||||
|
*/ |
||||
|
clear() { |
||||
|
this.items.length = 0; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @zh_CN 查看栈顶元素 |
||||
|
* @returns 栈顶元素 |
||||
|
*/ |
||||
|
peek(): T | undefined { |
||||
|
return this.items[this.items.length - 1]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @zh_CN 出栈 |
||||
|
* @returns 栈顶元素 |
||||
|
*/ |
||||
|
pop(): T | undefined { |
||||
|
return this.items.pop(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @zh_CN 入栈 |
||||
|
* @param items 要入栈的元素 |
||||
|
*/ |
||||
|
push(...items: T[]) { |
||||
|
items.forEach((item) => { |
||||
|
// 去重
|
||||
|
if (this.dedup) { |
||||
|
const index = this.items.indexOf(item); |
||||
|
if (index !== -1) { |
||||
|
this.items.splice(index, 1); |
||||
|
} |
||||
|
} |
||||
|
this.items.push(item); |
||||
|
if (this.maxSize && this.items.length > this.maxSize) { |
||||
|
this.items.splice(0, this.items.length - this.maxSize); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
/** |
||||
|
* @zh_CN 移除栈内元素 |
||||
|
* @param itemList 要移除的元素列表 |
||||
|
*/ |
||||
|
remove(...itemList: T[]) { |
||||
|
this.items = this.items.filter((i) => !itemList.includes(i)); |
||||
|
} |
||||
|
/** |
||||
|
* @zh_CN 保留栈内元素 |
||||
|
* @param itemList 要保留的元素列表 |
||||
|
*/ |
||||
|
retain(itemList: T[]) { |
||||
|
this.items = this.items.filter((i) => itemList.includes(i)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @zh_CN 转换为数组 |
||||
|
* @returns 栈内元素数组 |
||||
|
*/ |
||||
|
toArray(): T[] { |
||||
|
return [...this.items]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @zh_CN 创建一个栈实例 |
||||
|
* @param dedup 是否去重 |
||||
|
* @param maxSize 栈的最大容量 |
||||
|
* @returns 栈实例 |
||||
|
*/ |
||||
|
export const createStack = <T>(dedup = true, maxSize?: number) => |
||||
|
new Stack<T>(dedup, maxSize); |
||||
Loading…
Reference in new issue