tree-processor 0.9.7 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,12 +9,12 @@
9
9
  <div align="center">
10
10
 
11
11
  ![version](https://img.shields.io/npm/v/tree-processor?style=flat-square&label=version)
12
- ![npm downloads (2 months)](https://img.shields.io/badge/downloads-1.3K%2F2mo-brightgreen?style=flat-square)
13
- ![bundle size](https://img.shields.io/badge/bundle-8.4KB-blue?style=flat-square)
12
+ ![npm downloads (2 months)](https://img.shields.io/badge/downloads-1.7K%2F2mo-brightgreen?style=flat-square)
13
+ ![bundle size](https://img.shields.io/badge/bundle-15KB-blue?style=flat-square)
14
14
  ![License](https://img.shields.io/badge/license-MIT-green?style=flat-square)
15
15
  ![coverage](https://img.shields.io/badge/coverage-99%25-brightgreen?style=flat-square)
16
16
 
17
- 一个轻量级的树结构数据处理工具库,使用 TypeScript 编写,支持 tree-shaking,每个格式打包体积约 **8.2-8.5 KB**(ESM: 8.24 KB,CJS: 8.51 KB,UMD: 8.52 KB)。
17
+ 一个轻量级的树结构数据处理工具库,使用 TypeScript 编写,提供50+ API,包括遍历、查找、修改、转换、查询、分析、验证等完整功能。
18
18
 
19
19
 
20
20
  </div>
@@ -26,28 +26,26 @@
26
26
  - [安装](#-安装)
27
27
  - [快速开始](#-快速开始)
28
28
  - [API 文档](#-api-文档)
29
- - [遍历方法](#遍历方法)
30
- - [查找方法](#查找方法)
31
- - [访问方法](#访问方法)
32
- - [修改方法](#修改方法)
33
- - [转换方法](#转换方法)
34
- - [查询方法](#查询方法)
35
- - [验证方法](#验证方法)
29
+ - [遍历操作方法](#遍历操作方法)
30
+ - [条件查找方法](#条件查找方法)
31
+ - [索引访问方法](#索引访问方法)
32
+ - [节点操作方法](#节点操作方法)
33
+ - [格式转换方法](#格式转换方法)
34
+ - [克隆复制方法](#克隆复制方法)
35
+ - [关系查询方法](#关系查询方法)
36
+ - [数据验证方法](#数据验证方法)
37
+ - [聚合分析方法](#聚合分析方法)
36
38
  - [自定义字段名](#自定义字段名)
37
39
  - [测试](#测试)
38
40
  - [开发](#开发)
39
41
 
40
42
  ## ✨ 特性
41
43
 
42
- - **轻量级** - 每个格式打包体积仅 8.2-8.5 KB(ESM: 8.24 KB,CJS: 8.51 KB,UMD: 8.52 KB),对项目体积影响极小
43
- - **支持 Tree-shaking** - 支持按需导入,只打包实际使用的代码,进一步减小打包体积
44
- - **完整的 TypeScript 支持** - 提供完整的类型定义和智能提示,提升开发体验
45
- - **灵活的自定义字段名** - 支持自定义 children 和 id 字段名,适配各种数据结构
46
- - **零依赖** - 无任何外部依赖,开箱即用,无需担心依赖冲突
47
- - **完善的测试覆盖** - 包含 328 个测试用例,测试覆盖率达到 99%+(语句覆盖率 99%,分支覆盖率 98.41%,函数覆盖率 100%,行覆盖率 98.99%),覆盖基础功能、边界情况、异常处理、复杂场景等
48
- - **丰富的 API** - 提供 32+ 个方法,包含类似数组的 API(map、filter、find、some、every等),以及树结构特有的操作(获取父子节点、深度计算、数据验证、格式转换等),涵盖遍历、查找、修改、转换、判断等完整场景
49
-
50
- **已支持的方法:** mapTree、forEachTree、filterTree、findTree、pushTree、unshiftTree、popTree、shiftTree、someTree、everyTree、includesTree、atTree、indexOfTree、atIndexOfTree、dedupTree、removeTree、getParentTree、getChildrenTree、getSiblingsTree、getNodeDepthMap、getNodeDepth、isLeafNode、isRootNode、isEmptyTreeData、isEmptySingleTreeData、isTreeData、isSingleTreeData、isValidTreeNode、isTreeNodeWithCircularCheck、isSafeTreeDepth、convertToArrayTree、convertBackTree、convertToMapTree、convertToLevelArrayTree、convertToObjectTree。每个方法的最后一个参数可以自定义 children 和 id 的属性名。
44
+ - **多格式支持** - 提供 ESM、CJS、UMD 格式,体积仅 14.9-15.2 KB,支持 Tree-shaking,按需导入
45
+ - **零依赖** - 无外部依赖,开箱即用
46
+ - **高性能** - 中等规模树(~120节点)平均执行时间 < 0.03ms
47
+ - **功能完整** - 50+ API,覆盖遍历、查找、修改、转换、查询、分析、验证等完整功能
48
+ - **测试完善** - 447 个测试用例,99%+ 覆盖率
51
49
 
52
50
  ### 💡 使用场景
53
51
 
@@ -153,7 +151,9 @@ const treeData = [
153
151
 
154
152
  ---
155
153
 
156
- ## 遍历方法
154
+ ## 遍历操作方法
155
+
156
+ 遍历树结构数据并对每个节点执行操作的方法。
157
157
 
158
158
  ### mapTree
159
159
 
@@ -202,7 +202,9 @@ console.log(nodeCount) // 节点总数
202
202
 
203
203
  ---
204
204
 
205
- ## 查找方法
205
+ ## 条件查找方法
206
+
207
+ 通过条件或谓词函数查找节点的方法。
206
208
 
207
209
  ### filterTree
208
210
 
@@ -285,7 +287,9 @@ console.log(allHaveName) // 根据实际数据返回 true 或 false
285
287
 
286
288
  ---
287
289
 
288
- ## 访问方法
290
+ ## 索引访问方法
291
+
292
+ 通过位置索引或索引路径访问节点的方法。
289
293
 
290
294
  ### atTree
291
295
 
@@ -345,7 +349,9 @@ console.log(invalidPath) // null
345
349
 
346
350
  ---
347
351
 
348
- ## 修改方法
352
+ ## 节点操作方法
353
+
354
+ 对树结构进行增删改操作的方法(添加、删除、移除节点等)。
349
355
 
350
356
  ### pushTree
351
357
 
@@ -375,26 +381,30 @@ console.log(treeData) // 新节点已添加到 children 数组开头
375
381
 
376
382
  ### popTree
377
383
 
378
- 删除指定节点下的最后一个子节点。返回被删除的节点,如果节点不存在或没有子节点则返回 false
384
+ 删除指定节点下的最后一个子节点。返回被删除的节点,如果节点不存在或没有子节点则返回 null
379
385
 
380
386
  ```javascript
381
387
  // 删除ID为1的节点下的最后一个子节点
382
388
  const removedNode = t.popTree(treeData, 1)
383
- console.log(removedNode) // 返回被删除的节点对象,或 false
389
+ console.log(removedNode) // 返回被删除的节点对象,或 null
384
390
 
385
391
  // 尝试删除不存在的节点下的子节点
386
392
  const popFailed = t.popTree(treeData, 999)
387
- console.log(popFailed) // false
393
+ console.log(popFailed) // null
388
394
  ```
389
395
 
390
396
  ### shiftTree
391
397
 
392
- 删除指定节点下的第一个子节点。返回被删除的节点,如果节点不存在或没有子节点则返回 false
398
+ 删除指定节点下的第一个子节点。返回被删除的节点,如果节点不存在或没有子节点则返回 null
393
399
 
394
400
  ```javascript
395
401
  // 删除ID为1的节点下的第一个子节点
396
402
  const shiftedNode = t.shiftTree(treeData, 1)
397
- console.log(shiftedNode) // 返回被删除的节点对象,或 false
403
+ console.log(shiftedNode) // 返回被删除的节点对象,或 null
404
+
405
+ // 尝试删除不存在的节点下的子节点
406
+ const shiftFailed = t.shiftTree(treeData, 999)
407
+ console.log(shiftFailed) // null
398
408
  ```
399
409
 
400
410
  ### removeTree
@@ -409,23 +419,138 @@ console.log(removeSuccess) // true 表示删除成功,false 表示未找到节
409
419
  console.log(treeData) // 删除后的树结构
410
420
  ```
411
421
 
422
+ ### concatTree
423
+
424
+ 连接多个树结构数据,返回连接后的新树(深拷贝)。
425
+
426
+ ```javascript
427
+ const tree1 = [
428
+ { id: 1, name: 'node1' },
429
+ { id: 2, name: 'node2' }
430
+ ]
431
+ const tree2 = [
432
+ { id: 3, name: 'node3' }
433
+ ]
434
+
435
+ // 连接多个树
436
+ const result = t.concatTree(tree1, tree2)
437
+ console.log(result) // [{ id: 1, name: 'node1' }, { id: 2, name: 'node2' }, { id: 3, name: 'node3' }]
438
+ ```
439
+
440
+ **参数说明:**
441
+ - `...trees`: 多个树结构数据数组(可变参数)
442
+
443
+ **注意事项:**
444
+ - 所有树都会被深拷贝,不会修改原树
445
+ - 支持连接任意数量的树结构
446
+
447
+ ### sortTree
448
+
449
+ 对树结构数据进行排序,递归排序所有层级。
450
+
451
+ ```javascript
452
+ const tree = [
453
+ { id: 3, name: 'node3' },
454
+ { id: 1, name: 'node1' },
455
+ { id: 2, name: 'node2' }
456
+ ]
457
+
458
+ // 按 id 排序
459
+ const sorted = t.sortTree(tree, (a, b) => a.id - b.id)
460
+ console.log(sorted)
461
+ // [{ id: 1, name: 'node1' }, { id: 2, name: 'node2' }, { id: 3, name: 'node3' }]
462
+ ```
463
+
464
+ **参数说明:**
465
+ - `tree`: 树结构数据
466
+ - `compareFn`: 比较函数,与 `Array.sort` 的 `compareFn` 相同(可选)
467
+ - `fieldNames`: 自定义字段名配置(可选)
468
+
469
+ **注意事项:**
470
+ - 递归排序所有层级的节点
471
+ - 返回排序后的新树(深拷贝),不修改原树
472
+ - 如果不提供 `compareFn`,将使用默认排序
473
+
474
+ ### sliceTree
475
+
476
+ 对树结构数据的根节点进行切片操作(类似数组的 `slice`)。
477
+
478
+ ```javascript
479
+ const tree = [
480
+ { id: 1, name: 'node1' },
481
+ { id: 2, name: 'node2' },
482
+ { id: 3, name: 'node3' }
483
+ ]
484
+
485
+ // 切片:获取索引 1 到 3 的节点
486
+ const sliced = t.sliceTree(tree, 1, 3)
487
+ console.log(sliced) // [{ id: 2, name: 'node2' }, { id: 3, name: 'node3' }]
488
+
489
+ // 支持负数索引
490
+ const lastTwo = t.sliceTree(tree, -2)
491
+ console.log(lastTwo) // [{ id: 2, name: 'node2' }, { id: 3, name: 'node3' }]
492
+ ```
493
+
494
+ **参数说明:**
495
+ - `tree`: 树结构数据
496
+ - `start`: 起始索引(包含),可选
497
+ - `end`: 结束索引(不包含),可选
498
+ - `fieldNames`: 自定义字段名配置(可选)
499
+
500
+ **注意事项:**
501
+ - 仅对根节点进行切片,不递归处理子节点
502
+ - 返回切片后的新树(深拷贝),不修改原树
503
+ - 支持负数索引(从末尾开始计算)
504
+ - 子节点结构会被完整保留
505
+
412
506
  ### dedupTree
413
507
 
414
- 树结构对象数组去重方法,根据指定的键去除重复节点。保留第一次出现的节点。
508
+ 树结构对象数组去重方法,根据指定的键去除重复节点。保留第一次出现的节点。支持单字段、多字段联合去重和自定义函数。
509
+
510
+ **参数说明:**
511
+ - `tree`: 树结构数据
512
+ - `dedupKey`: 用于去重的键名,支持三种类型:
513
+ - `string`: 单字段去重(如 `'id'`)
514
+ - `string[]`: 多字段联合去重(如 `['id', 'type']`)
515
+ - `(node: TreeNode) => any`: 自定义函数,返回用于去重的值
516
+ - `fieldNames`: 自定义字段名配置(可选)
415
517
 
416
518
  ```javascript
417
- // 根据 id 字段去重
519
+ // 方式1:单字段去重(原有用法)
418
520
  const uniqueTreeData = t.dedupTree(treeData, 'id')
419
521
  console.log(uniqueTreeData) // 返回去重后的树结构数据
420
522
 
421
- // 根据 name 字段去重
422
- const uniqueByNameTree = t.dedupTree(treeData, 'name')
423
- console.log(uniqueByNameTree) // 返回根据 name 去重后的数据
523
+ // 方式2:多字段联合去重(新功能)
524
+ const tree = [
525
+ {
526
+ id: 1,
527
+ children: [
528
+ { id: 2, type: 'A', name: 'node1' },
529
+ { id: 2, type: 'B', name: 'node2' }, // 保留(id相同但type不同)
530
+ { id: 2, type: 'A', name: 'node3' }, // 去重(id和type都相同)
531
+ ]
532
+ }
533
+ ]
534
+ const uniqueByMultiFields = t.dedupTree(tree, ['id', 'type'])
535
+ // 结果:保留 node1 和 node2,node3 被去重
536
+
537
+ // 方式3:自定义函数去重
538
+ const uniqueByCustom = t.dedupTree(treeData, (node) => node.code)
539
+ // 或更复杂的逻辑
540
+ const uniqueByComplex = t.dedupTree(treeData, (node) => `${node.id}-${node.type}`)
424
541
  ```
425
542
 
543
+ **注意事项:**
544
+ - 如果 dedupKey 值为 `undefined` 或 `null`,节点不会被去重(会全部保留)
545
+ - 多字段联合去重使用字段值的组合来判断重复
546
+ - 递归处理所有层级的子节点
547
+ - **性能优化**:多字段联合去重已优化,使用高效的分隔符连接方式替代 JSON.stringify,提升性能
548
+
426
549
  ---
427
550
 
428
- ## 转换方法
551
+ ## 格式转换方法
552
+
553
+ 在不同数据格式之间转换的方法(数组、Map、对象等格式转换)。
429
554
 
430
555
  ### convertToArrayTree
431
556
 
@@ -699,7 +824,169 @@ console.log(treeFromRecord) // 正确转换为树结构
699
824
 
700
825
  ---
701
826
 
702
- ## 查询方法
827
+ ## 克隆复制方法
828
+
829
+ 复制树结构数据的方法(深拷贝、浅拷贝、子树拷贝等)。
830
+
831
+ ### cloneTree
832
+
833
+ 深拷贝树结构数据,返回完全独立的副本,不修改原树。
834
+
835
+ ```javascript
836
+ const original = [
837
+ { id: 1, name: 'node1', children: [{ id: 2, name: 'node2' }] }
838
+ ]
839
+
840
+ // 深拷贝
841
+ const cloned = t.cloneTree(original)
842
+
843
+ // 修改克隆的树不会影响原树
844
+ cloned[0].name = 'modified'
845
+ console.log(original[0].name) // 'node1'
846
+ console.log(cloned[0].name) // 'modified'
847
+ ```
848
+
849
+ **参数说明:**
850
+ - `tree`: 树结构数据
851
+ - `fieldNames`: 自定义字段名配置(可选)
852
+
853
+ **注意事项:**
854
+ - 递归深拷贝所有层级的节点和子节点
855
+ - 返回的树与原树完全独立,修改不会相互影响
856
+ - 支持自定义字段名配置
857
+
858
+ ### shallowCloneTree
859
+
860
+ 浅拷贝树结构数据(只拷贝第一层,子节点共享引用)。性能比深拷贝更好,适合只需要拷贝顶层结构的场景。
861
+
862
+ ```javascript
863
+ const original = [
864
+ { id: 1, name: 'node1', children: [{ id: 2, name: 'node2' }] }
865
+ ]
866
+
867
+ // 浅拷贝
868
+ const cloned = t.shallowCloneTree(original)
869
+
870
+ // 修改第一层不会影响原树
871
+ cloned[0].name = 'modified'
872
+ console.log(original[0].name) // 'node1'
873
+
874
+ // 但子节点共享引用,修改子节点会影响原树
875
+ cloned[0].children[0].name = 'changed'
876
+ console.log(original[0].children[0].name) // 'changed'
877
+ ```
878
+
879
+ **参数说明:**
880
+ - `tree`: 树结构数据
881
+ - `fieldNames`: 自定义字段名配置(可选)
882
+
883
+ **注意事项:**
884
+ - 只拷贝第一层节点,子节点保持引用共享
885
+ - 性能比深拷贝更好,适合只需要顶层独立的场景
886
+ - 修改子节点会影响原树
887
+
888
+ ### cloneSubtree
889
+
890
+ 从指定节点开始拷贝子树。返回包含目标节点及其所有子节点的深拷贝。支持按任意字段查找节点。
891
+
892
+ ```javascript
893
+ const tree = [
894
+ {
895
+ id: 1,
896
+ name: 'root',
897
+ children: [
898
+ { id: 2, name: 'sub1', children: [{ id: 4, name: 'sub1-1' }] },
899
+ { id: 3, name: 'sub2' }
900
+ ]
901
+ }
902
+ ]
903
+
904
+ // 按 id 字段查找
905
+ const subtree1 = t.cloneSubtree(tree, { id: 2 })
906
+ console.log(subtree1)
907
+ // [{ id: 2, name: 'sub1', children: [{ id: 4, name: 'sub1-1' }] }]
908
+
909
+ // 按 name 字段查找
910
+ const subtree2 = t.cloneSubtree(tree, { name: 'sub1' })
911
+ console.log(subtree2)
912
+ // [{ id: 2, name: 'sub1', children: [{ id: 4, name: 'sub1-1' }] }]
913
+
914
+ // 按其他字段查找(如 code)
915
+ const treeWithCode = [
916
+ {
917
+ id: 1,
918
+ code: 'A001',
919
+ children: [
920
+ { id: 2, code: 'B001', children: [{ id: 4, code: 'C001' }] }
921
+ ]
922
+ }
923
+ ]
924
+ const subtree3 = t.cloneSubtree(treeWithCode, { code: 'B001' })
925
+ console.log(subtree3)
926
+ // [{ id: 2, code: 'B001', children: [{ id: 4, code: 'C001' }] }]
927
+
928
+ // 支持自定义 children 字段名
929
+ const customTree = [
930
+ { nodeId: 1, subNodes: [{ nodeId: 2 }] }
931
+ ]
932
+ const subtree4 = t.cloneSubtree(customTree, { nodeId: 2 }, { children: 'subNodes', id: 'nodeId' })
933
+ console.log(subtree4)
934
+ // [{ nodeId: 2 }]
935
+
936
+ // 修改拷贝的子树不会影响原树
937
+ subtree1[0].name = 'modified'
938
+ console.log(tree[0].children[0].name) // 'sub1'
939
+ ```
940
+
941
+ **参数说明:**
942
+ - `tree`: 树结构数据
943
+ - `target`: 目标节点对象,例如 `{ id: 1 }` 或 `{ name: 'sub1' }` 或 `{ code: 'B001' }`,对象只能包含一个字段
944
+ - `fieldNames`: 自定义字段名配置(可选,用于自定义 `children` 字段名,查找字段由 `target` 对象的键名决定)
945
+
946
+ **注意事项:**
947
+ - 返回包含目标节点的子树(深拷贝)
948
+ - 如果未找到目标节点,返回空数组
949
+ - 递归深拷贝所有子节点
950
+ - 必须传入对象形式,查找字段由对象的键名决定(如 `{ id: 1 }` 表示按 `id` 字段查找,`{ name: 'xxx' }` 表示按 `name` 字段查找)
951
+ - `fieldNames` 参数用于自定义 `children` 字段名,定义 `id` 不生效
952
+
953
+ ### cloneWithTransform
954
+
955
+ 拷贝树结构数据并对每个节点应用转换函数。适合在拷贝的同时修改节点数据。
956
+
957
+ ```javascript
958
+ const tree = [
959
+ { id: 1, name: 'node1', children: [{ id: 2, name: 'node2' }] }
960
+ ]
961
+
962
+ // 拷贝并添加 label 字段
963
+ const cloned = t.cloneWithTransform(tree, (node) => ({
964
+ ...node,
965
+ label: node.name,
966
+ processed: true
967
+ }))
968
+
969
+ console.log(cloned[0].label) // 'node1'
970
+ console.log(cloned[0].processed) // true
971
+ console.log(cloned[0].children[0].label) // 'node2'
972
+ console.log(tree[0].label) // undefined(原树未修改)
973
+ ```
974
+
975
+ **参数说明:**
976
+ - `tree`: 树结构数据
977
+ - `transform`: 转换函数,接收节点并返回转换后的节点
978
+ - `fieldNames`: 自定义字段名配置(可选)
979
+
980
+ **注意事项:**
981
+ - 递归转换所有层级的节点
982
+ - 返回转换后的树(深拷贝),不修改原树
983
+ - 转换函数应该返回新的节点对象
984
+
985
+ ---
986
+
987
+ ## 关系查询方法
988
+
989
+ 获取节点之间关系信息的方法(父子关系、兄弟关系、深度等)。
703
990
 
704
991
  ### getParentTree
705
992
 
@@ -850,7 +1137,9 @@ console.log(depth) // 2
850
1137
 
851
1138
  ---
852
1139
 
853
- ## 验证方法
1140
+ ## 数据验证方法
1141
+
1142
+ 验证树结构数据有效性和节点类型的方法。
854
1143
 
855
1144
  ### isLeafNode
856
1145
 
@@ -902,6 +1191,8 @@ console.log(t.isLeafNode(customNode, fieldNames)) // true
902
1191
 
903
1192
  检查节点是否是根节点(没有父节点)。根节点是树结构数据数组中的顶层节点。
904
1193
 
1194
+ **性能优化**:已优化为单次遍历,避免重复遍历树结构。
1195
+
905
1196
  ```javascript
906
1197
  // 检查根节点
907
1198
  const treeData = [
@@ -1249,6 +1540,435 @@ console.log(t.isSafeTreeDepth(customTree, 2, fieldNames)) // false
1249
1540
  - 防止递归调用栈溢出
1250
1541
  - 性能优化,避免处理过深的树结构
1251
1542
 
1543
+ ---
1544
+
1545
+ ## 聚合分析方法
1546
+
1547
+ 对树结构数据进行聚合、统计和分析的方法。
1548
+
1549
+ ### reduceTree
1550
+
1551
+ 对树结构数据进行归约操作,遍历所有节点并累积结果。
1552
+
1553
+ ```javascript
1554
+ const tree = [
1555
+ { id: 1, value: 10 },
1556
+ { id: 2, value: 20, children: [{ id: 3, value: 30 }] }
1557
+ ]
1558
+
1559
+ // 计算所有节点值的总和
1560
+ const sum = t.reduceTree(tree, (acc, node) => acc + (node.value || 0), 0)
1561
+ console.log(sum) // 60
1562
+
1563
+ // 收集所有节点ID
1564
+ const ids = t.reduceTree(tree, (ids, node) => {
1565
+ ids.push(node.id)
1566
+ return ids
1567
+ }, [])
1568
+ console.log(ids) // [1, 2, 3]
1569
+ ```
1570
+
1571
+ **参数说明:**
1572
+ - `tree`: 树结构数据
1573
+ - `reducer`: 归约函数,接收累加值和当前节点,返回新的累加值
1574
+ - `initialValue`: 初始值
1575
+ - `fieldNames`: 自定义字段名配置(可选)
1576
+
1577
+ **注意事项:**
1578
+ - 按深度优先顺序遍历所有节点
1579
+ - 可以用于实现各种聚合操作
1580
+
1581
+ ---
1582
+
1583
+ ### aggregateTree
1584
+
1585
+ 按分组聚合树结构数据,支持多种聚合操作(求和、平均值、最大值、最小值、计数)。
1586
+
1587
+ ```javascript
1588
+ const tree = [
1589
+ { id: 1, category: 'A', value: 10, score: 80 },
1590
+ { id: 2, category: 'A', value: 20, score: 90 },
1591
+ { id: 3, category: 'B', value: 30, score: 70, children: [{ id: 4, category: 'B', value: 40, score: 85 }] }
1592
+ ]
1593
+
1594
+ // 按 category 分组聚合
1595
+ const result = t.aggregateTree(tree, {
1596
+ groupBy: node => node.category,
1597
+ aggregations: {
1598
+ totalValue: { operation: 'sum', field: 'value' },
1599
+ avgScore: { operation: 'avg', field: 'score' },
1600
+ maxValue: { operation: 'max', field: 'value' },
1601
+ count: { operation: 'count' }
1602
+ }
1603
+ })
1604
+
1605
+ console.log(result)
1606
+ // {
1607
+ // 'A': { totalValue: 30, avgScore: 85, maxValue: 20, count: 2 },
1608
+ // 'B': { totalValue: 70, avgScore: 77.5, maxValue: 40, count: 2 }
1609
+ // }
1610
+ ```
1611
+
1612
+ **参数说明:**
1613
+ - `tree`: 树结构数据
1614
+ - `options`: 聚合选项
1615
+ - `groupBy`: 分组函数,接收节点并返回分组键
1616
+ - `aggregations`: 聚合配置对象,键为结果字段名,值为聚合配置
1617
+ - `operation`: 聚合操作类型('sum' | 'avg' | 'max' | 'min' | 'count')
1618
+ - `field`: 要聚合的字段名(count 操作不需要)
1619
+ - `fieldNames`: 自定义字段名配置(可选)
1620
+
1621
+ **注意事项:**
1622
+ - 支持多种聚合操作同时进行
1623
+ - 递归处理所有层级的节点
1624
+ - count 操作统计节点数量,不需要 field 参数
1625
+
1626
+ ---
1627
+
1628
+ ### groupTree
1629
+
1630
+ 按字段分组树结构数据,返回按字段值分组的节点数组。
1631
+
1632
+ ```javascript
1633
+ const tree = [
1634
+ { id: 1, category: 'A' },
1635
+ { id: 2, category: 'A' },
1636
+ { id: 3, category: 'B', children: [{ id: 4, category: 'B' }] }
1637
+ ]
1638
+
1639
+ // 按 category 字段分组
1640
+ const grouped = t.groupTree(tree, 'category')
1641
+ console.log(grouped)
1642
+ // {
1643
+ // 'A': [{ id: 1, category: 'A' }, { id: 2, category: 'A' }],
1644
+ // 'B': [{ id: 3, category: 'B' }, { id: 4, category: 'B' }]
1645
+ // }
1646
+ ```
1647
+
1648
+ **参数说明:**
1649
+ - `tree`: 树结构数据
1650
+ - `field`: 分组字段名
1651
+ - `fieldNames`: 自定义字段名配置(可选)
1652
+
1653
+ **注意事项:**
1654
+ - 返回的节点是原节点的引用,不是深拷贝
1655
+ - 递归处理所有层级的节点
1656
+
1657
+ ---
1658
+
1659
+ ### groupByTree
1660
+
1661
+ 按条件分组树结构数据,使用自定义函数确定分组键。
1662
+
1663
+ ```javascript
1664
+ const tree = [
1665
+ { id: 1, value: 10 },
1666
+ { id: 2, value: 20 },
1667
+ { id: 3, value: 10, children: [{ id: 4, value: 30 }] }
1668
+ ]
1669
+
1670
+ // 按 value 是否大于等于 20 分组
1671
+ const grouped = t.groupByTree(tree, node => node.value >= 20 ? 'high' : 'low')
1672
+ console.log(grouped)
1673
+ // {
1674
+ // 'low': [{ id: 1, value: 10 }, { id: 3, value: 10 }],
1675
+ // 'high': [{ id: 2, value: 20 }, { id: 4, value: 30 }]
1676
+ // }
1677
+ ```
1678
+
1679
+ **参数说明:**
1680
+ - `tree`: 树结构数据
1681
+ - `groupFn`: 分组函数,接收节点并返回分组键
1682
+ - `fieldNames`: 自定义字段名配置(可选)
1683
+
1684
+ **注意事项:**
1685
+ - 分组键会被转换为字符串
1686
+ - 返回的节点是原节点的引用,不是深拷贝
1687
+
1688
+ ---
1689
+
1690
+ ### sumTree
1691
+
1692
+ 计算树结构数据中某个字段的总和。
1693
+
1694
+ ```javascript
1695
+ const tree = [
1696
+ { id: 1, value: 10 },
1697
+ { id: 2, value: 20, children: [{ id: 3, value: 30 }] }
1698
+ ]
1699
+
1700
+ // 计算 value 字段的总和
1701
+ const total = t.sumTree(tree, 'value')
1702
+ console.log(total) // 60
1703
+ ```
1704
+
1705
+ **参数说明:**
1706
+ - `tree`: 树结构数据
1707
+ - `field`: 字段名
1708
+ - `fieldNames`: 自定义字段名配置(可选)
1709
+
1710
+ **注意事项:**
1711
+ - 缺失或 null/undefined 的值会被视为 0
1712
+ - 递归处理所有层级的节点
1713
+
1714
+ ---
1715
+
1716
+ ### avgTree
1717
+
1718
+ 计算树结构数据中某个字段的平均值。
1719
+
1720
+ ```javascript
1721
+ const tree = [
1722
+ { id: 1, value: 10 },
1723
+ { id: 2, value: 20 },
1724
+ { id: 3, value: 30 }
1725
+ ]
1726
+
1727
+ // 计算 value 字段的平均值
1728
+ const average = t.avgTree(tree, 'value')
1729
+ console.log(average) // 20
1730
+ ```
1731
+
1732
+ **参数说明:**
1733
+ - `tree`: 树结构数据
1734
+ - `field`: 字段名
1735
+ - `fieldNames`: 自定义字段名配置(可选)
1736
+
1737
+ **注意事项:**
1738
+ - 忽略 null 和 undefined 值
1739
+ - 如果所有值都是 null/undefined,返回 0
1740
+
1741
+ ---
1742
+
1743
+ ### maxTree
1744
+
1745
+ 获取树结构数据中某个字段的最大值。
1746
+
1747
+ ```javascript
1748
+ const tree = [
1749
+ { id: 1, value: 10 },
1750
+ { id: 2, value: 30 },
1751
+ { id: 3, value: 20 }
1752
+ ]
1753
+
1754
+ // 获取 value 字段的最大值
1755
+ const max = t.maxTree(tree, 'value')
1756
+ console.log(max) // 30
1757
+ ```
1758
+
1759
+ **参数说明:**
1760
+ - `tree`: 树结构数据
1761
+ - `field`: 字段名
1762
+ - `fieldNames`: 自定义字段名配置(可选)
1763
+
1764
+ **注意事项:**
1765
+ - 只处理数字类型的值
1766
+ - 如果树为空或没有有效值,返回 null
1767
+
1768
+ ---
1769
+
1770
+ ### minTree
1771
+
1772
+ 获取树结构数据中某个字段的最小值。
1773
+
1774
+ ```javascript
1775
+ const tree = [
1776
+ { id: 1, value: 30 },
1777
+ { id: 2, value: 10 },
1778
+ { id: 3, value: 20 }
1779
+ ]
1780
+
1781
+ // 获取 value 字段的最小值
1782
+ const min = t.minTree(tree, 'value')
1783
+ console.log(min) // 10
1784
+ ```
1785
+
1786
+ **参数说明:**
1787
+ - `tree`: 树结构数据
1788
+ - `field`: 字段名
1789
+ - `fieldNames`: 自定义字段名配置(可选)
1790
+
1791
+ **注意事项:**
1792
+ - 只处理数字类型的值
1793
+ - 如果树为空或没有有效值,返回 null
1794
+
1795
+ ---
1796
+
1797
+ ### countTree
1798
+
1799
+ 统计树结构数据中满足条件的节点数量。
1800
+
1801
+ ```javascript
1802
+ const tree = [
1803
+ { id: 1, value: 10 },
1804
+ { id: 2, value: 20 },
1805
+ { id: 3, value: 10, children: [{ id: 4, value: 30 }] }
1806
+ ]
1807
+
1808
+ // 统计所有节点
1809
+ const total = t.countTree(tree)
1810
+ console.log(total) // 4
1811
+
1812
+ // 统计满足条件的节点
1813
+ const count = t.countTree(tree, node => node.value === 10)
1814
+ console.log(count) // 2
1815
+ ```
1816
+
1817
+ **参数说明:**
1818
+ - `tree`: 树结构数据
1819
+ - `conditionFn`: 统计条件函数(可选),不传则统计所有节点
1820
+ - `fieldNames`: 自定义字段名配置(可选)
1821
+
1822
+ **注意事项:**
1823
+ - 不传条件函数时统计所有节点
1824
+ - 递归处理所有层级的节点
1825
+
1826
+ ---
1827
+
1828
+ ### getTreeStats
1829
+
1830
+ 获取树结构数据的综合统计信息。
1831
+
1832
+ ```javascript
1833
+ const tree = [
1834
+ { id: 1, children: [{ id: 2 }, { id: 3, children: [{ id: 4 }] }] }
1835
+ ]
1836
+
1837
+ // 获取统计信息
1838
+ const stats = t.getTreeStats(tree)
1839
+ console.log(stats)
1840
+ // {
1841
+ // totalNodes: 4, // 总节点数
1842
+ // leafNodes: 2, // 叶子节点数
1843
+ // maxDepth: 3, // 最大深度
1844
+ // minDepth: 1, // 最小深度
1845
+ // avgDepth: 2, // 平均深度
1846
+ // levels: 3 // 层级数(等于最大深度)
1847
+ // }
1848
+ ```
1849
+
1850
+ **参数说明:**
1851
+ - `tree`: 树结构数据
1852
+ - `fieldNames`: 自定义字段名配置(可选)
1853
+
1854
+ **注意事项:**
1855
+ - 返回完整的统计信息对象
1856
+ - 空树返回所有值为 0 的统计信息
1857
+
1858
+ ---
1859
+
1860
+ ### analyzeTree
1861
+
1862
+ 全面分析树结构数据,提供详细的统计信息、分布情况、平衡性分析等。
1863
+
1864
+ ```javascript
1865
+ const tree = [
1866
+ { id: 1, children: [{ id: 2 }, { id: 3, children: [{ id: 4 }] }] }
1867
+ ]
1868
+
1869
+ // 全面分析树结构
1870
+ const analysis = t.analyzeTree(tree)
1871
+ console.log(analysis)
1872
+ // {
1873
+ // // 基础统计
1874
+ // totalNodes: 4, // 总节点数
1875
+ // leafNodes: 2, // 叶子节点数
1876
+ // internalNodes: 2, // 内部节点数
1877
+ // maxDepth: 3, // 最大深度
1878
+ // minDepth: 1, // 最小深度
1879
+ // avgDepth: 2, // 平均深度
1880
+ // levels: 3, // 层级数
1881
+ //
1882
+ // // 层级分析
1883
+ // byLevel: { 0: 1, 1: 2, 2: 1 }, // 按层级统计节点数
1884
+ // maxWidth: 2, // 最大宽度(单层最多节点数)
1885
+ // avgWidth: 1.33, // 平均宽度
1886
+ // widthByLevel: { 0: 1, 1: 2, 2: 1 }, // 每层宽度
1887
+ //
1888
+ // // 分支因子分析
1889
+ // avgBranchingFactor: 1.5, // 平均分支因子(平均子节点数)
1890
+ // maxBranchingFactor: 2, // 最大分支因子
1891
+ // minBranchingFactor: 1, // 最小分支因子
1892
+ // branchingFactorDistribution: { 1: 1, 2: 1 }, // 分支因子分布
1893
+ //
1894
+ // // 深度分布
1895
+ // depthDistribution: { 1: 1, 2: 2, 3: 1 }, // 按深度统计节点数
1896
+ //
1897
+ // // 平衡性分析
1898
+ // depthVariance: 0.5, // 深度方差(越小越平衡)
1899
+ // isBalanced: true, // 是否平衡
1900
+ // balanceRatio: 0.33, // 平衡比率(minDepth/maxDepth)
1901
+ //
1902
+ // // 路径分析
1903
+ // avgPathLength: 2.25, // 平均路径长度
1904
+ // maxPathLength: 3, // 最大路径长度
1905
+ // minPathLength: 1, // 最小路径长度
1906
+ //
1907
+ // // 叶子节点分析
1908
+ // leafNodeRatio: 0.5, // 叶子节点比例
1909
+ // leafNodesByLevel: { 2: 1, 3: 1 } // 每层叶子节点数
1910
+ // }
1911
+ ```
1912
+
1913
+ **参数说明:**
1914
+ - `tree`: 树结构数据
1915
+ - `options`: 分析选项(可选),可指定需要计算的统计项,默认计算所有统计项
1916
+ - `includeBasic`: 是否包含基础统计(totalNodes, leafNodes, internalNodes, maxDepth, minDepth, avgDepth, levels),默认 `true`
1917
+ - `includeLevelAnalysis`: 是否包含层级分析(byLevel, maxWidth, avgWidth, widthByLevel),默认 `true`
1918
+ - `includeBranchingFactor`: 是否包含分支因子分析(avgBranchingFactor, maxBranchingFactor, minBranchingFactor, branchingFactorDistribution),默认 `true`
1919
+ - `includeDepthDistribution`: 是否包含深度分布(depthDistribution),默认 `true`
1920
+ - `includeBalanceAnalysis`: 是否包含平衡性分析(depthVariance, isBalanced, balanceRatio),默认 `true`
1921
+ - `includePathAnalysis`: 是否包含路径分析(avgPathLength, maxPathLength, minPathLength),默认 `true`
1922
+ - `includeLeafAnalysis`: 是否包含叶子节点分析(leafNodeRatio, leafNodesByLevel),默认 `true`
1923
+ - `fieldNames`: 自定义字段名配置(可选)
1924
+
1925
+ ```javascript
1926
+ // 只计算基础统计和分支因子(性能优化)
1927
+ const quickAnalysis = t.analyzeTree(tree, {
1928
+ includeBasic: true,
1929
+ includeBranchingFactor: true,
1930
+ includeLevelAnalysis: false,
1931
+ includeDepthDistribution: false,
1932
+ includeBalanceAnalysis: false,
1933
+ includePathAnalysis: false,
1934
+ includeLeafAnalysis: false,
1935
+ })
1936
+ console.log(quickAnalysis.totalNodes) // 4
1937
+ console.log(quickAnalysis.maxBranchingFactor) // 2
1938
+ console.log(quickAnalysis.byLevel) // {} (未计算)
1939
+
1940
+ // 只计算平衡性分析
1941
+ const balanceAnalysis = t.analyzeTree(tree, {
1942
+ includeBasic: true,
1943
+ includeBalanceAnalysis: true,
1944
+ includeLevelAnalysis: false,
1945
+ includeBranchingFactor: false,
1946
+ includeDepthDistribution: false,
1947
+ includePathAnalysis: false,
1948
+ includeLeafAnalysis: false,
1949
+ })
1950
+ console.log(balanceAnalysis.isBalanced) // true/false
1951
+ console.log(balanceAnalysis.depthVariance) // 0.5
1952
+ ```
1953
+
1954
+ **返回的分析信息包括:**
1955
+
1956
+ 1. **基础统计**:总节点数、叶子节点数、内部节点数、深度信息等
1957
+ 2. **层级分析**:每层节点数、最大宽度、平均宽度等
1958
+ 3. **分支因子分析**:平均/最大/最小分支因子、分支因子分布等
1959
+ 4. **深度分布**:每个深度的节点数量
1960
+ 5. **平衡性分析**:深度方差、是否平衡、平衡比率等
1961
+ 6. **路径分析**:平均/最大/最小路径长度
1962
+ 7. **叶子节点分析**:叶子节点比例、每层叶子节点数
1963
+
1964
+ **注意事项:**
1965
+ - 提供全面的树结构分析,适合用于性能优化、结构评估等场景
1966
+ - `isBalanced` 基于深度方差和深度范围判断,深度方差 < 2 且深度范围 ≤ 2 视为平衡
1967
+ - `balanceRatio` 接近 1 表示树更平衡
1968
+ - **性能优化**:通过 `options` 参数可以只计算需要的统计项,对于大型树结构可以显著提升性能
1969
+
1970
+ ---
1971
+
1252
1972
  ## 自定义字段名
1253
1973
 
1254
1974
  所有方法都支持自定义 children 和 id 的属性名,通过最后一个参数传入配置对象:
@@ -1269,21 +1989,20 @@ const foundNode2 = t.findTree(customTreeData, (node) => node.nodeId === 2, field
1269
1989
  ### 运行测试
1270
1990
 
1271
1991
  ```bash
1272
- # 运行所有测试(自动打包后测试源码 + 打包文件,656 个测试用例)
1992
+ # 运行所有测试(自动打包后测试源码 + 打包文件,712 个测试用例)
1273
1993
  npm test
1274
1994
 
1275
1995
  # 运行所有测试(单次,不监听文件变化)
1276
1996
  npm test -- --run
1277
1997
 
1278
- # 仅测试源代码(328 个测试用例)
1998
+ # 仅测试源代码(447 个测试用例)
1279
1999
  npm run test:src
1280
2000
 
1281
- # 仅测试打包后的文件(328 个测试用例,需要先运行 npm run build)
2001
+ # 仅测试打包后的文件(447 个测试用例,需要先运行 npm run build)
1282
2002
  npm run test:dist
1283
2003
 
1284
2004
  # 运行测试并生成覆盖率报告
1285
2005
  npm run test:coverage
1286
- ```
1287
2006
 
1288
2007
  ## 开发
1289
2008