Javascript 为什么我需要一个列表折叠来解构一棵树的这个树的变形?
退化可以解构一个值Javascript 为什么我需要一个列表折叠来解构一棵树的这个树的变形?,javascript,recursion,functional-programming,tree,catamorphism,Javascript,Recursion,Functional Programming,Tree,Catamorphism,退化可以解构一个值 [1,2,3].reduce((acc, x) => acc + x, 0); // 6 或维护结构,并像底层类型的标识一样: [1,2,3].reduce((acc, x) => acc.concat([x]), []); // [1,2,3] 对于列表(或JS中的数组),变形和折叠(可折叠容器的)重合 但是,对于树木,它们不会: const treeCata=f=>([x,xs])=> f(x)(arrMap(treeCata(f))(xs)); 常量a
[1,2,3].reduce((acc, x) => acc + x, 0); // 6
或维护结构,并像底层类型的标识一样:
[1,2,3].reduce((acc, x) => acc.concat([x]), []); // [1,2,3]
对于列表(或JS中的数组),变形和折叠(可折叠容器的)重合
但是,对于树木,它们不会:
const treeCata=f=>([x,xs])=>
f(x)(arrMap(treeCata(f))(xs));
常量arrMap=f=>xs=>
x.map((x,i)=>f(x,i));
常数arrFold=f=>acc=>xs=>
x.reduce((acc,x)=>f(acc)(x),acc);
const log=x=>(console.log(x),x);
常量节点=(x,xs)=>([x,xs]);
常量节点_ux=>xs=>([x,xs]);
常数树=节点(1[
节点(2[
节点(3,[]),
节点(4,[])
]),
节点(5,[])
]);
const foo=treeCata(节点_)(树);
常数条=treeCata(x=>xs=>x+arrFold(y=>z=>y+z)(0)(xs))(树);
日志(foo);
对数(巴)代码>有两种褶皱
用于对数据结构执行归纳的结构折叠
遍历折叠,用于遍历和汇总数据结构的元素
结构折叠直观地用给定函数替换数据结构的每个数据构造函数
节点(1)([
节点(2)([
节点(3)([]),
节点(4)([]))
]),
节点(5)([]))
])
/*
|
|特雷卡塔(fNode)
v
*/
fNode(1)([
fNode(2)([
fNode(3)([]),
fNode(4)([]))
]),
fNode(5)([]))
])
但是,遍历折叠遍历数据结构的各个元素并对它们进行汇总
Node_(1)([
Node_(2)([
Node_(3)([]),
Node_(4)([])
]),
Node_(5)([])
])
/*
|
| foldrTree(add)(0)
v
*/
[1, 2, 3, 4, 5].reduceRight(add, 0);
在Haskell中,类型类实现了遍历折叠。可折叠类型类的最小完整定义是方法。那么,让我们为树实现它
//节点:a->[树a]->树a
const Node_=value=>children=>({value,children});
//树形图:(a->[b]->b)->树a->b
const treeCata=fNode=>函数折叠({value,children}){
返回fNode(值)(children.map(fold));
};
//树:树a->[a]
常量treeElems=treeCata(x=>xss=>[x].concat(…xss));
//折叠树:((b,a)->b)->b->树a->b
const foldrTree=cons=>empty=>tree=>
树元素(tree).还原右(cons,空);
//加:(数字,数字)->数字
常数加=(x,y)=>x+y;
//树:树编号
常数树=节点(1)([
节点(2)([
节点(3)([]),
节点(4)([]))
]),
节点(5)([]))
]);
日志(foldrTree(add)(0)(tree))
我认为您的treeCata
很好,对于您定义的类型treea=Node a[treea]
是一个有效的亚同构。基本函子是treefab=Node a[b]
,所有代数都必须处理这个结构。反同构只为您处理递归,而不是节点子节点的列表结构。如果它这样做了,它将对您隐藏信息,您将无法使用初始代数重建树
当然,您可能希望在TreeF
上定义一些辅助函数。由于节点的非空性,一个特别有趣的问题可能是
// fold :: Semigroup a => TreeF a a -> a
// fold1 :: (a -> a -> a) -> TreeF a a -> a
const fold1 = f => ([x, xs]) => arrFold(f)(x)(xs)
有了它,您确实可以将求和函数定义为
const treeSum = treeCata(fold1((x, y) => x+y));
正如@Aadit所言,这是一个函数,它允许我们从结构褶皱中导出横向褶皱
const treeCata=f=>([x,xs])=>
f([x,arrMap(treeCata(f))(xs)]);
常量arrMap=f=>xs=>
x.map((x,i)=>f(x,i));
常数arrFold=f=>acc=>xs=>
x.reduce((acc,x)=>f(acc)(x),acc);
常数treeF_fold1=f=>([x,xs])=>arrFold(f)(x)(xs);
常量treeSum=treeCata(treeF_fold1(x=>y=>x+y));
常量节点=(x,xs)=>([x,xs]);
常量节点_ux=>xs=>([x,xs]);
常数树=节点(1[
节点(2[
节点(3,[]),
节点(4,[])
]),
节点(5,[])
]);
log(treeCata(Node_)(tree));
console.log(treeSum(tree))
treeCata
接收单个函数f
,该函数是节点
数据构造函数的功能化。因此,我得到了我想要的,即类型为[treea]
的值,而没有节点
层。我应该从一开始就看到这一点。@scriptum这对我来说也不是很明显,我在网上学习了一些其他的例子treeCata。fold1
函数相当于foldMap id
@AaditMShah我故意保留fold1
非货币化。根据我的理解,传递给亚同态的代数只接受一个参数,在treeCata
的情况下,类型为TreeF
的值。(我想把它命名为treeF_fold1
,但是相邻的'f'疏远了我)@AaditMShah为了实现Foldable
,我宁愿先用treeFmap=f=>treeCata([x,xs])
分别派生Functor
treeFmap=sconcat=>t=>treeCata(fold1(sconcat))(树形图(f)(t))