优化算法,将文本节点与修饰合并

Optimize the algorithm to merge text nodes with decorations

提问人:developer1 提问时间:9/27/2023 最后编辑:developer1 更新时间:9/27/2023 访问量:49

问:

我需要帮助弄清楚如何合并两个带有装饰的节点数组。我有一个非常低效的算法,但我需要更好的东西或任何想法。

我有两个这样的文本节点数组。通常,每个节点都由文本和文本修饰(颜色、字体样式等)和索引组成。我想合并这两个数组,以便文本保持不变,但装饰将合并。fromto

这是我当前的算法,它遍历内容的每个字母,检查字母是否有任何修饰,并为包含所有修饰的字母创建一个单独的节点:

const getDecorationsByIndex = (nodes, index) => {
  const foundNodes: any[] = [];

  nodes.forEach(node => {
    if (index >= node.from && index < node.to) {
      foundNodes.push(node);
    }
  });

  return foundNodes.flatMap(node => node.textData?.decorations || []);
};

const mergeNodes = (nodes1, nodes2, content) => {
  const mergedNodes = [...content].map((s, index) => {
    const decorations1 = getDecorationsByIndex(nodes1, index);
    const decorations2 = getDecorationsByIndex(nodes2, index);

    return {
      id: '',
      nodes: [],
      type: Node_Type.TEXT,
      textData: {
        decorations: [...decorations1, ...decorations2],
        text: s,
      },
    };
  });

  return mergedNodes;
};

举个例子,让我们看一个简单的文本,它有各种装饰,从颜色到字体粗细。def

每个数组都完全表示文本。 这个增加了一些大胆:

[
  {
      "type": "TEXT",
      "id": "",
      "nodes": [],
      "textData": {
          "text": "de",
          "decorations": [
              {
                  "type": "BOLD",
                  "fontWeightValue": 700
              }
          ]
      },
      "from": 0,
      "to": 2
  },
  {
      "type": "TEXT",
      "id": "",
      "nodes": [],
      "textData": {
          "text": "f",
          "decorations": []
      },
      "from": 2,
      "to": 3
  }
]

第二个数组添加颜色:

    [
  {
      "id": "",
      "nodes": [],
      "type": "TEXT",
      "textData": {
          "decorations": [
              {
                  "type": "COLOR",
                  "colorData": {
                      "foreground": "#d73a49"
                  }
              }
          ],
          "text": "def"
      },
      "from": 0,
      "to": 3
  }
]

结果应为:

[
  {
      "type": "TEXT",
      "id": "",
      "nodes": [],
      "textData": {
          "text": "de",
          "decorations": [
              {
                  "type": "BOLD",
                  "fontWeightValue": 700
              },{
                  "type": "COLOR",
                  "colorData": {
                      "foreground": "#d73a49"
                  }
              }
          ]
      },
      "from": 0,
      "to": 2
  },
  {
      "type": "TEXT",
      "id": "",
      "nodes": [],
      "textData": {
          "text": "f",
          "decorations": [{
                  "type": "COLOR",
                  "colorData": {
                      "foreground": "#d73a49"
                  }
              }]
      },
      "from": 2,
      "to": 3
  }
]

结果应该是这些节点的一个数组,这些节点最终将具有相同的文本和所有装饰。帮助我优化我的算法或为我指明正确的方向,我应该如何合并这些节点。谢谢。

JavaScript 打字稿 TipTap

评论

0赞 trincot 9/27/2023
修饰具有属性,但合并函数还会添加 中的属性。这有什么实际意义?texttextcontents
0赞 developer1 9/27/2023
@trincot 是的,因为我一封一封地写,这是不好的部分。理想情况下,我只想使用来自每个节点的节点来执行所有合并/拆分。textData
0赞 developer1 9/27/2023
基本上,如果我逐个节点地将所有文本合并在一起。content
0赞 trincot 9/27/2023
嗯......因此,如果您得到一个逐个节点的答案,它将不符合您的目的吗?您能否为您给出的示例输入提供预期的输出?我不知道该怎么办.contents
0赞 trincot 9/27/2023
如果一个节点的文本与另一个重叠节点的文本之间存在矛盾怎么办?

答:

0赞 trincot 9/27/2023 #1

您可以使用 from/to 信息按索引对节点进行排序(因此您需要复制节点,一次用于获取其属性,一次用于属性)。然后对其进行排序,并按该顺序迭代以跟踪哪些装饰处于活动状态或未处于活动状态。fromto

在开始之前,您还可以收集子字符串并从中重新生成字符串。然后可以在主算法中使用它来再次对其进行切片。

const Node_Type = { TEXT: 3 };

// Helper function to toggle a set member
const toggle = (set, member) => !set.delete(member) && set.add(member);

const extractText = (nodes) =>
    // Reconstruct text from the nodes (there should be no inconsistency)
    nodes.reduce((acc, node) => {
        acc.length = Math.max(acc.length, node.from);
        acc.splice(node.from, node.to - node.from, ...node.textData.text);
        return acc;
    }, []).map(ch => ch ?? " ").join("");

const mergeNodes = (nodes1, nodes2) => {
    const nodes = nodes1.concat(nodes2);
    const text = extractText(nodes);
    const activeNodes = new Set;
    return nodes.flatMap(node => [[node.from, node], [node.to, node]])
                 .sort(([a], [b]) => a - b)
                 .map(([from, node], i, toggles) => {
        toggle(activeNodes, node); // Activate or deactivate a decoration
        const to = toggles[i+1]?.[0] ?? text.length;
        if (from === to || !activeNodes.size) return; // Nothing to generate here
        return {
            id: '',
            nodes: [],
            type: Node_Type.TEXT,
            textData: {
               decorations: Array.from(activeNodes, ({textData: {decorations}}) => decorations).flat(), 
               text: text.slice(from, to),
            },
            from,
            to
        };
    }).filter(Boolean); // Exclude the undefined entries
}

// Sample input
const a = [{
    "type": "TEXT", "id": "", "nodes": [],
    "textData": { "text": "de", "decorations": [{"type": "BOLD","fontWeightValue": 700}] },
    "from": 0, "to": 2
}, {
    "type": "TEXT", "id": "", "nodes": [],
    "textData": { "text": "f someFoo", "decorations": []},
    "from": 2, "to": 11
}];

const b = [{
    "id": "", "nodes": [], "type": "TEXT",
    "textData": {
        "decorations": [{"type": "COLOR","colorData": {"foreground": "#d73a49"}}],
        "text": "def"
    },
    "from": 0, "to": 3
}, {
    "id": "", "nodes": [], "type": "TEXT",
    "textData": { "decorations": [], "text": " "},
    "from": 3, "to": 4
}, {
    "id": "", "nodes": [], "type": "TEXT",
    "textData": {
        "decorations": [{"type": "COLOR", "colorData": {"foreground": "#6f42c1"}}],
        "text": "someFoo"
    },
    "from": 4, "to": 11
}];

console.log(mergeNodes(a, b));

请注意,共享装饰不会被克隆,因此某些节点的数组中将具有相同的对象引用。这意味着,如果您要改变装饰,它将应用于共享它的所有节点。如果你希望它以不同的方式工作,你应该在这个过程中克隆装饰。decorations