📒 如何实现双向链表
查看详情
在项目中遇到一个问题,源码中使用数组模拟队列,添加使用 unshift
,移除使用 pop
,导致添加元素的时间复杂度为 O(n)
。这里使用双向链表模拟队列,两端均可添加、删除元素,且时间复杂度均为 O(1)
:
/**
* 链表节点
*/
class ListNode<T> {
public next: ListNode<T> = null;
public prev: ListNode<T> = null;
public val: T = undefined;
constructor(val: T) {
this.val = val;
}
}
/**
* 实现双向链表
*/
class LinkedList<T> {
private head: ListNode<T> = null;
private end: ListNode<T> = null;
private _size: number = 0;
/**
* add() 相当于 addLast()
* @param val
* @returns
*/
public add(val: T): boolean {
const node = new ListNode<T>(val);
if (this.head == null) {
// 初始化 head 指针
this.head = node;
}
if (this.end == null) {
// 初始化 end 指针
this.end = node;
} else {
// 把新节点挂到链表最后
this.end.next = node;
// 新节点 prev 指向前一节点
node.prev = this.end;
// end 指针后移一位
this.end = node;
}
// 维护 size
this._size++;
return true;
}
/**
* addFirst() 在链表头部添加
* @param val
* @returns
*/
public addFirst(val: T): boolean {
const node = new ListNode<T>(val);
if (this.head == null) {
// 初始化 head 指针
this.head = node;
} else {
// 把新节点挂到链表头部
this.head.prev = node;
// 新节点 next 指向下一节点
node.next = this.head;
// head 指针前移一位
this.head = node;
}
if (this.end == null) {
// 初始化 end 指针
this.end = node;
}
// 维护 size
this._size++;
return true;
}
/**
* poll() 相当于 pollFirst()
* @returns
*/
public poll(): T {
// 缓存需要删除的节点
const node = this.head;
// head 指向下一节点
this.head = this.head.next;
// 切断与前一节点的联系
this.head.prev = null;
// 维护 size
this._size--;
return node.val;
}
/**
* pollLast() 移除链表尾部元素
* @returns
*/
public pollLast(): T {
// 缓存需要删除的节点
const node = this.end;
// end 指向前一节点
this.end = this.end.prev;
// 切断与后一节点的联系
this.end.next = null;
// 维护 size
this._size--;
return node.val;
}
/**
* 获取链表长度
* @returns
*/
public size(): number {
return this._size;
}
/**
* 序列化为字符串
* @returns
*/
public toString(): string {
let res: T[] = [];
let list = this.head;
while (list != null) {
res.push(list.val);
list = list.next;
}
return `[ ${res.join(" ")} ]`;
}
}
📒 React Hooks 源码分析
React 函数组件通过 renderWithHooks
函数进行渲染,里面有个 workingInProgress
的对象就是当前的 fiber 节点,fiber 节点的 memorizedState
就是保存 hooks 数据的地方。它是一个通过 next
串联的链表。
这个 memorizedState
链表是什么时候创建的呢?确实有个链表创建的过程,也就是 mountXxx。链表只需要创建一次,后面只需要 update。所以第一次调用 useState
会执行 mountState
,后面再调用 useState
会执行 updateState
。
每个 Hook 的 memorizedState
链表节点是通过 mountWorkInProgressHook
函数创建的:
function mountWorkInProgressHook(): Hook {
const hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// This is the first hook in the list
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
函数组件本身是没有挂载、更新的概念的,每次 rerender 就是执行这个函数,但是挂载、更新的逻辑体现在 Hooks 里面,首次执行的时候调用
mountWorkInProgressHook
创建链表节点,后续执行的时候调用updateWorkInProgressHook
访问并更新链表节点
📒 前端工程师如何快速使用一个NLP模型
2017年谷歌提出了Transformer架构模型,2018年底,基于Transformer架构,谷歌推出了bert模型,bert模型一诞生,便在各大11项NLP基础任务中展现出了卓越的性能,现在很多模型都是基于或参考Bert模型进行改造。
如果想了解 Transformer 和 bert,可以看这个视频
⭐️ Git不要只会pull和push,试试这5条提高效率的命令
📒 reduce 方法注意事项
初始值非空 | 初始值为空 | |
---|---|---|
数组非空 | 首次执行回调,accu 为初始值,cur 为数组第一项 | 首次执行回调,accu 为数组第一项,cur 为数组第二项 |
数组为空 | 不执行回调,直接返回初始值 | 报错(建议任何情况下都传递初始值) |
📒 npm 安装依赖默认添加 ^
前缀,当再次执行 npm install 命令时,会自动安装这个包在此大版本下的最新版本。如果想要修改这个功能,可以执行以下命令:
$ npm config set save-prefix='~'
执行完该命令之后,就会把
^
符号改为~
符号。当再次安装新模块时,就从只允许小版本的升级变成了只允许补丁包的升级
如果想要锁定当前的版本,可以执行以下命令:
$ npm config set save-exact true
这样每次
npm install xxx --save
时就会锁定依赖的版本号,相当于加了--save-exact
参数。建议线上的应用都采用这种锁定版本号的方式
既然可以锁定依赖版本,为什么还需要 lcok-file 呢,个人理解锁定依赖只能锁定当前项目中的依赖版本,但是还存在间接依赖,即依赖还有依赖,直接锁定依赖版本无法解决间接依赖的问题,间接依赖版本还是不受控制,需要借助 lock-file 锁定间接依赖的版本。
📒 函数式编程三种形式:
- 函数赋值给变量
- 可作为数组的元素,进而实现 compose 函数组合,或者管道操作
- 函数作为参数
- 常见的有
forEach
、Promise
、setTimeout
等,React 技术栈也有很多 API
- 常见的有
- 函数作为返回值
📒 type-challenges
type-challenges 是一个 TypeScript 类型体操姿势合集。本项目意在于让你更好的了解 TS 的类型系统,编写你自己的类型工具,或者只是单纯的享受挑战的乐趣!