FP:树木 - 地图,折叠,每个。如何?

时间:2017-01-19 23:29:20

标签: javascript algorithm tree functional-programming category-theory

我有一个working OOP code递归地将一个图形元素组合呈现给画布。我有点不喜欢它,我试图看看功能版本会是什么样子。

当然,可以编写一个专门的递归纯函数,但由于框架涉及类似的算法,我想:

  • 利用功能组合的力量。
  • 了解FP - 及其数据管道范例(通过纯函数转换数据) - 如何将自己置于比列表(树/图)更复杂的结构和更简单的算法(比如说找到所有顺序迭代列表的奇数)。

受到Lazy.js的启发,我已经开始编码并且做到了这一点:

LazyTree.from( drawing )
    .keepNodes( visible )
    .keepChildrenOf( nonClipping )
    .traverse( log );

但至于地图弃用 - 我有很多未解答的问题。

目标

这是我试图解决的问题的简化版本:

数据

矩形的合成(层次结构)。每个边界都在相对坐标(到它的父节点):

const drawing = {
    name: 'Face',
    bounds: { x: 10, y: 10, w: 100, h: 100 },
    children: [{
        name: 'Left eye',
        bounds: { x: 10, y: 10, w: 20, h: 20 }, // Abs: (20, 20, 20, 20)
        children: [{
            name: 'Left pupil',
            bounds: { x: 5, y: 5, w: 10, h: 10 } // Abs: (25, 25, 10, 10)
        }]
    },{
        name: 'Right eye',
        bounds: { x: 70, y: 10, w: 20, h: 20 }, // Abs: (80, 20, 20, 20)
        children: [{
            name: 'Right pupil',
            bounds: { x: 5, y: 5, w: 10, h: 10 } // Abs: (85, 25, 10, 10)
        }]
    }]
};

任务 - getAbsoluteBounds

任务是将此合成转换为具有绝对坐标的合成(如注释中所示)。

问题和想法

折?

子的绝对坐标是其父绝对坐标转换的相对坐标。所以带有累加器的 fold 是候选者。

但折叠与 catamorphism 和动词如 combine 相关联,通常会返回单个值。

有问题的转换采用树并返回相同的结构但具有不同的值 - 因此它听起来更像是地图,但需要累加器。< / p>

就累加器而言,值得注意的是特定节点的所有子节点都应该获得相同的累加器。对于上面的数据,Left eyeRight eye都应获得相同的Face绝对坐标(而不是Right eye获取Left eye的返回累加器深度优先遍历)。

我不清楚的另一件事是谁应该负责构建输出树。它应该是高阶函数(折叠,映射还是其他),还是它应该是聚合器?

停止条件

与上一节相关,考虑剪切其子节点的所有矩形,以及以下组成:

const drawing = {
    name: 'Parent',
    bounds: { x: 10, y: 10, w: 10, h: 10 },
    children: [{
        name: 'Child',
        bounds: { x: 1000000, y: 1000000, w: 10, h: 10 }, 
        children: [{
            name: 'Grandchild',
            bounds: { x: 5, y: 5, w: 5, h: 5 }
        }]
    }]
};

Child边界与其父节点(Parent)的关系超出范围,因此当遍历到Child时,分支遍历应该停止(没有点遍历{{1} }})。

问题是:如何通过折叠功能实现这一点?一种解决方案是在累加器返回约定的值(例如Grandchild)时停止分支遍历。但这与列表的折叠API有所不同。

访问前后

渲染算法包括:

undefined

我想知道如何通过fill( shape ); renderChildren( shape ); stroke( shape ); traverse()来实现这一目标。这些应该采取2次回调(前,后)吗?

遍历策略

树遍历可能是:

使用列表,我们有each()之类的功能。 Lazy.js允许adding a custom iterator然后可以链接。

因此,处理遍历策略的FP方式似乎是一种转换功能。还有什么吗?

摘要

我已经谈到了使用数据管道模型为树结构实现渲染算法的一些挑战。

我怀疑其他FP方法是否更合适?也许数据管道模型不适合这类问题。或许,我应该忘记在FP库中看到的API(几乎只处理列表)并创建一个适合于手头任务的API(例如,具有也涉及累加器的map函数)。

我找不到专门用于树的FP库,而且那里的信息通常仅限于非常简单的问题。

所以希望有人会回答“这就是应该如何做的事情”。

1 个答案:

答案 0 :(得分:0)

据我所知,您可以按照以下方式了解详情。

它将继续遍历父级边界内剩余的项目,将其坐标转换为绝对值,然后再将其渲染。但是,如果孩子的边界与父母的边界重叠,则跳过孩子及其后代。没有转换为绝对坐标和渲染。

function render(bounds){
  console.log("Rendered:", bounds);
}

function relToAbs(o, b = {x: 0, y:0, w:Infinity, h:Infinity}, go = true){
  go = o.bounds.x < b.w && o.bounds.y < b.h ? (o.bounds.x += b.x, o.bounds.y += b.y, render(o.bounds), go) : !go;
  o.children && go && (o.children = o.children.map(p => relToAbs(p,o.bounds,go)));
  return o;
}

var drawing = {    name: 'Face',
                 bounds: { x: 10, y: 10, w: 100, h: 100 },
               children: [{    name: 'Left eye',
                             bounds: { x: 200, y: 10, w: 20, h: 20 },          // Abs: (20, 20, 20, 20)
                           children: [{    name: 'Left pupil',
                                         bounds: { x: 5, y: 5, w: 10, h: 10 } // Abs: (25, 25, 10, 10)
                                      }]
                          },
                          {    name: 'Right eye',
                             bounds: { x: 70, y: 10, w: 20, h: 20 },          // Abs: (80, 20, 20, 20)
                           children: [{    name: 'Right pupil',
                                         bounds: { x: 5, y: 5, w: 10, h: 10 } // Abs: (85, 25, 10, 10)
                                      }]
                          }]
              };
console.log(JSON.stringify(relToAbs(drawing),null,2));