Array
Radash 使用 TypeScript 编写,提供开箱即用的完整功能。 大多数 Radash 函数都是确定性 和 纯函数(Pure Function)
1
| import { isArray, isFunction } from './typed'
|
array.ts 引入了 isArray
和 isFunction
两个方法用于数组类型的判断。
alphabetical
按属性的字母顺序对对象数组进行排序。
给定一个对象数组和一个用于确定用于排序的属性的回调函数,返回一个新数组,其中对象按字母顺序排序。第三个可选参数允许您按降序而不是默认的升序排序。对于数字排序,请参阅排序函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { alphabetical } from 'radash'
const gods = [ { name: 'Ra', power: 100 }, { name: 'Zeus', power: 98 }, { name: 'Loki', power: 72 }, { name: 'Vishnu', power: 100 }, ]
alphabetical(gods, g => g.name)
alphabetical(gods, g => g.name, 'desc')
|
函数的实现原理是将输入的数组 array 进行排序,然后返回一个新的排序后的数组。同时,函数还支持指定排序的顺序为升序(’asc’)或降序(’desc’)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
export const alphabetical = <T>( array: readonly T[], getter: (item: T) => string, dir: 'asc' | 'desc' = 'asc' ) => { if (!array) return [] const asc = (a: T, b: T) => `${getter(a)}`.localeCompare(getter(b)) const dsc = (a: T, b: T) => `${getter(b)}`.localeCompare(getter(a)) return array .slice() .sort(dir === 'desc' ? dsc : asc) }
|
- 由于函数使用了 readonly 修饰符,这意味着
不能直接修改输入数组 array
(这里鼓励使用不可变数据,官方倡导函数式编程)。为了避免混淆,建议使用 array.slice() 创建一个新的数组进行排序。后面的 readonly 同,就不赘述了
- 函数 getter 函数用于获取每个元素的排序关键字,这对于实现自定义排序非常灵活。例如,如果需要根据某个属性排序,可以使用
getter = (item: T) => item.sortKey
。
- 函数默认使用升序(’asc’)进行排序,如果需要使用降序(’desc’),只需在调用时指定 dir 参数即可,例如
alphabetical(array, getter, 'desc')
。
boil
将项目列表减少到一项
给定一个项目数组,返回赢得比较条件的最终项目。对于更复杂的最小/最大有用。
1 2 3 4 5 6 7 8 9 10
| import { boil } from 'radash'
const gods = [ { name: 'Ra', power: 100 }, { name: 'Zeus', power: 98 }, { name: 'Loki', power: 72 }, ]
boil(gods, (a, b) => (a.power > b.power ? a : b))
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| export const boil = <T>( array: readonly T[], compareFunc: (a: T, b: T) => T ) => { if (!array || (array.length ?? 0) === 0) return null return array.reduce(compareFunc) }
|
cluster
将列表拆分为多个给定大小的列表.
给定一个项目数组和所需的列表大小 (n),返回一个数组数组。每个包含 n(列表大小)项的子数组尽可能均匀地分割。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { cluster } from 'radash'
const gods = [ 'Ra', 'Zeus', 'Loki', 'Vishnu', 'Icarus', 'Osiris', 'Thor', 'Apollo', 'Artemis', 'Athena', ]
cluster(gods, 3)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| export const cluster = <T>(list: readonly T[], size: number = 2): T[][] => { const clusterCount = Math.ceil(list.length / size) return ( new Array(clusterCount) .fill(null) .map((_c: null, i: number) => { return list.slice(i * size, i * size + size) }) ) }
|
counting
创建一个包含项目出现次数的对象
给定一个对象数组和一个身份回调函数来确定如何识别每个对象。返回一个对象,其中键是回调返回的 id 值,每个值都是一个整数,表示该 id 出现了多少次。
1 2 3 4 5 6 7 8 9
| import { counting } from 'radash'
const gods = [ { name: 'Ra', culture: 'egypt' }, { name: 'Zeus', culture: 'greek' }, { name: 'Loki', culture: 'greek' }, ]
counting(gods, g => g.culture)
|
这个函数的目的是计算给定列表中每个元素的身份表示(通过 identity 函数)的计数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| export const counting = <T, TId extends string | number | symbol>( list: readonly T[], identity: (item: T) => TId ): Record<TId, number> => { if (!list) return {} as Record<TId, number>
return list.reduce((acc, item) => { const id = identity(item) acc[id] = (acc[id] ?? 0) + 1 return acc }, {} as Record<TId, number>) }
|
diff
创建两个数组之间的差异的数组
给定两个数组,返回第一个数组中存在但第二个数组中不存在的所有项目的数组。
1 2 3 4 5 6
| import { diff } from 'radash'
const oldWorldGods = ['ra', 'zeus'] const newWorldGods = ['vishnu', 'zeus']
diff(oldWorldGods, newWorldGods)
|
这个函数可以用于实现两个不同集合的差异操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| export const diff = <T>( root: readonly T[], other: readonly T[], identity: (item: T) => string | number | symbol = (t: T) => t as unknown as string | number | symbol ): T[] => { if (!root?.length && !other?.length) return [] if (root?.length === undefined) return [...other] if (!other?.length) return [...root]
const bKeys = other.reduce((acc, item) => { acc[identity(item)] = true return acc }, {} as Record<string | number | symbol, boolean>)
return root.filter(a => !bKeys[identity(a)]) }
|
说实话,刚看到这里也卡了阵 - - 不能逃避,果断格式化,将代码再细分、拆解,果然这样一弄就轻松多了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
const diff = <T>(identity: (item: T) => ( string | number | symbol ) = (t: T) => (t as unknown) as (string | number | symbol) ): T[] {}
|
注:在实际应用中,应该根据具体情况来判断是否需要这种类型的转换,并确保有适当的类型检查和错误处理机制。
flat
将数组的数组展平为一维。
印象中最新 ESNext 貌似增加了该方法,这里也可学习下。
给定一个包含许多数组的数组,返回一个新数组,其中子级的所有项目都出现在顶层。
1 2 3 4
| import { flat } from 'radash'
const gods = [['ra', 'loki'], ['zeus']] flat(gods)
|
通过 reduce 函数遍历多维数组的每一层,将当前层的元素推入 acc 数组,然后返回 acc 数组,从而实现拍平的效果。
1 2 3 4 5 6 7 8 9
| export const flat = <T>(lists: readonly T[][]): T[] => { return lists.reduce((acc, list) => { acc.push(...list) return acc }, []) }
|
注意:
- 请确保 lists 是一个有效的多维数组,否则在 reduce 函数中可能会出现错误。
- 由于 reduce 函数的回调函数会频繁地创建新的数组,因此在性能上可能不是最优的解决方案。
如果对性能有严格要求,可以考虑使用其他数据结构或算法
fork
按条件将数组拆分为两个数组
给定一个项目数组和一个条件,返回两个数组,其中第一个包含通过条件的所有项目,第二个包含未通过条件的所有项目。
1 2 3 4 5 6 7 8 9 10 11
| import { fork } from 'radash'
const gods = [ { name: 'Ra', power: 100 }, { name: 'Zeus', power: 98 }, { name: 'Loki', power: 72 }, { name: 'Vishnu', power: 100 }, ]
const [finalGods, lesserGods] = fork(gods, f => f.power > 90)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| export const fork = <T>( list: readonly T[], condition: (item: T) => boolean ): [T[], T[]] => { if (!list) return [[], []] return list.reduce( (acc, item) => { const [a, b] = acc if (condition(item)) { return [[...a, item], b] } else { return [a, [...b, item]] } }, [[], []] as [T[], T[]] ) }
|
group
对一组项目进行排序
给定一个项目数组,group 将构建一个对象,其中每个键都是属于该组的项目的数组。一般来说,这对于对数组进行分类很有用。
1 2 3 4 5 6 7 8 9 10
| import { group } from 'radash'
const fish = [ { name: 'Marlin', source: 'ocean' }, { name: 'Bass', source: 'lake' }, { name: 'Trout', source: 'lake' }, ]
const fishBySource = group(fish, f => f.source)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| export const group = <T, Key extends string | number | symbol>( array: readonly T[], getGroupId: (item: T) => Key ): Partial<Record<Key, T[]>> => { return array.reduce((acc, item) => { const groupId = getGroupId(item) if (!acc[groupId]) acc[groupId] = [] acc[groupId].push(item) return acc }, {} as Record<Key, T[]>) }
|
::: warning 注意:
这个函数只会创建或更新分组,而不是删除分组。如果需要实现删除分组的功能,可以在 accumulator 中检查对应的分组是否为空,如果为空则删除该分组
:::
intersects
确定两个数组是否有公共项
给定两个项目数组,如果两个数组中都存在任何项目,则返回 true。
1 2 3 4 5 6 7 8 9 10
| import { intersects } from 'radash'
const oceanFish = ['tuna', 'tarpon'] const lakeFish = ['bass', 'trout']
intersects(oceanFish, lakeFish)
const brackishFish = ['tarpon', 'snook']
intersects(oceanFish, brackishFish)
|
函数的实现原理是将 listB 中的元素映射为一个键值对,然后检查 listA 中是否存在具有相同键的元素。如果存在,则两个列表相交;否则,不相交。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| export const intersects = <T, K extends string | number | symbol>( listA: readonly T[], listB: readonly T[], identity?: (t: T) => K ): boolean => { if (!listA || !listB) return false
const ident = identity ?? ((x: T) => x as unknown as K)
const dictB = listB.reduce((acc, item) => { acc[ident(item)] = true return acc }, {} as Record<string | number | symbol, boolean>) return listA.some(value => dictB[ident(value)]) }
|
iterate
迭代回调 n 次。该函数可以用于对一些数据进行迭代处理,例如对数组进行去重、排序等操作。
有点像 forEach 遇到 reduce。对于运行函数 n 次以生成值很有用。 _.iterate 函数采用计数(运行回调的次数)、回调函数和初始值。回调作为减速器运行多次,然后返回累积值。
1 2 3 4 5 6 7 8 9
| import { iterate } from 'radash'
const value = iterate( 4, (acc, idx) => { return acc + idx }, 0 )
|
实现原理是使用一个循环,每次迭代都调用 func 函数,并将结果赋值给 value 变量。循环将持续进行 count 次。最后返回 value 变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| export const iterate = <T>( count: number, func: (currentValue: T, iteration: number) => T, initValue: T ) => { let value = initValue for (let i = 1; i <= count; i++) { value = func(value, i) } return value }
|
last
获取列表中的最后一项
给定一个项目数组,返回最后一个项目,如果不存在项目,则返回默认值。
1 2 3 4 5
| import { last } from 'radash'
const fish = ['marlin', 'bass', 'trout'] const lastFish = last(fish) const lastItem = last([], 'bass')
|
我们平时都会写 arr[arr.length - 1]
那么看下 Radash 源码又有什么不一样吧?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| export const last = <T>( array: readonly T[], defaultValue: T | null | undefined = undefined ) => { return array?.length > 0 ? array[array.length - 1] : defaultValue }
|
使用可选类型可以避免因为检查空数组导致的错误。对比我们平时写的代码多了健全的类型判断,只要注意边界判断,大家都可以写出高质量的代码。
max
从数组中获取最大的项
给定一个项数组和一个获取每个项值的函数,返回具有最大值的项。在内部使用 _.boil。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { max } from 'radash'
const fish = [ { name: 'Marlin', weight: 105, source: 'ocean', }, { name: 'Bass', weight: 8, source: 'lake', }, { name: 'Trout', weight: 13, source: 'lake', }, ]
max(fish, f => f.weight)
|
min & max
获取数组中最小 / 最大的项(由于实现基本相同,这里放一起说了)
同上。给定一个项数组和一个获取每个项值的函数,返回具有最小值的项。在内部使用 _.boil。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { min, max } from 'radash'
const fish = [ { name: 'Marlin', weight: 105, source: 'ocean', }, { name: 'Bass', weight: 8, source: 'lake', }, { name: 'Trout', weight: 13, source: 'lake', }, ]
min(fish, f => f.weight)
max(fish, f => f.weight)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
export function min(array: readonly [number, ...number[]]): number
export function min(array: readonly number[]): number | null
export function min<T>(array: readonly T[], getter: (item: T) => number): T | null export function min<T>(array: readonly T[], getter?: (item: T) => number): T | null { const get = getter ?? ((v: any) => v) return boil(array, (a, b) => (get(a) < get(b) ? a : b)) }
export function max(array: readonly [number, ...number[]]): number export function max(array: readonly number[]): number | null export function max<T>(array: readonly T[], getter: (item: T) => number): T | null export function max<T>(array: readonly T[], getter?: (item: T) => number): T | null { const get = getter ?? ((v: any) => v) return boil(array, (a, b) => (get(a) > get(b) ? a : b)) }
|
merge
合并两个列表,覆盖第一个列表中的项
给定两个项数组和一个标识函数,返回第一个列表中所有与第二个列表匹配的项。
这个函数可以用于合并两个数组,特别是当需要根据某些规则(由 matcher 函数定义)来匹配和合并数组中的元素时非常有用。例如,可以在合并用户列表时使用这个函数,根据用户名进行匹配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { merge } from 'radash'
const gods = [ { name: 'Zeus', power: 92, }, { name: 'Ra', power: 97, }, ]
const newGods = [ { name: 'Zeus', power: 100, }, ]
merge(gods, newGods, f => f.name)
|
使用 lib 经常会看到 mergeOptions 方法,原理差不多,用来合并两个对象,覆盖第一个对象中的项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| export const merge = <T>( root: readonly T[], others: readonly T[], matcher: (item: T) => any ) => { if (!others && !root) return [] if (!others) return root if (!root) return [] if (!matcher) return root
return root.reduce((acc, r) => { const matched = others.find(o => matcher(r) === matcher(o)) if (matched) acc.push(matched) else acc.push(r)
return acc }, [] as T[]) }
|