AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 79101873
Accepted
Lance Pollard
Lance Pollard
Asked: 2024-10-18 19:29:33 +0800 CST2024-10-18 19:29:33 +0800 CST 2024-10-18 19:29:33 +0800 CST

如何构建一个 trie 来查找精确的语音匹配,按权重进行全局排序并分页?(基于此示例)

  • 772

目标

我在使用 AI 构建用于查找押韵词的 Trie 方面取得了很大进展。基本上,假设您有 1000 万个使用CMU 发音系统编写的英语单词,其中每个音素都是 Trie 中的一个节点(ARPABET在此图中)。

在此处输入图片描述

以下是主要特点:

  1. Trie 节点是 1+ ASCII 符号。例如B“b”音和CH“ch”音等。
  2. Trie 可以分页并从特定页面跳转。因此它需要一个limit, 和page属性。
  3. 分页可以选择性地限制为特定音素序列长度的匹配。例如,音素的个数g l a d为 4。
  4. Trie 搜索输入(由用户提供)是音素数组。音素是 ASCII 中的声音单位(如上所示ARPABET)。
  5. 输入到 trie 中会扩展为所有可能的韵律。它使用音素到音素数组的映射进行扩展(仅在下面的函数中部分实现,需要数周时间才能正确微调此处的值,我很快就需要 TODO)。
  6. 我们可以将扩展的输入称为“ rhymingPhonemeArray”。
  7. 中的每个音素序列都rhymingPhonemeArray有权重。所谓“加权”,这个权重基本上就是“映射的音素与原始音素的接近程度”。这样我们就可以判断“这个音素序列(具有较低的累积权重)比这个音素序列押韵更好rhymingPhonemeArray”。

问题

我现在面临的问题(我们找到的解决方案,下面用 JavaScript 分享)是:

  1. 遍历整个 trie 来查找所有可能的匹配,这是未优化的。理想情况下,它只遍历它需要的内容(页面/限制数量和偏移量)。
  2. 之后 Trie 会对整个匹配集进行排序,而不是按照我们的要求排序,也就是获取已排序的页面/限制数量。这是关键问题,不确定是否/如何以优化的方式做到这一点,或者是否可能。
  3. 是rhymingPhonemeArray迭代的,所以如果我们分页并且rhymingPhonemeArray是这样的[G-A-D (cum-weight: 10), G-L-A-D (cum-weight: 24 or whatever), G-R-A-D (cum-weight: 29), etc.],你会G-A-D先找到与押韵的所有内容,然后再分页,分页G-L-A-D等等。我想避免这种分组。相反,累积权重需要通过所有 10m 个单词的“全局集”进行排序和分页。

因此对于 (3),它应该找到(类似这样的内容):

input: G-A-D
matches:
  G-A-D
  G-L-A-D
  A-G-A-D
  A-R-G-A-D
  G-R-A-D
  A-G-R-A-D
  A-R-G-L-A-D
  A-R-G-R-A-D
  ...

我说的“类似这样的事情”的意思是,请注意它并不像这样(首先,G-A-D找到所有匹配项,然后G-L-A-D找到所有匹配项等等):

input: G-A-D
matches:
  G-A-D
  A-G-A-D
  A-R-G-A-D
  G-L-A-D
  A-R-G-L-A-D
  G-R-A-D
  A-G-R-A-D
  A-R-G-R-A-D
  ...

相反,在第一种情况下matches,它更加交织在一起,应该基于每个单词的全局累积权重。

问题

如何修改以下“Trie 实现”来解决上述 1、2 和 3 的问题? 注意:它不需要 100% 准确,只需寻找如何正确解决此分页问题的关键见解(即使只是在高级/伪代码级别)。它们都是同一潜在问题的各个方面,这是我已经说过的,但我会再次说明:

下面的 Trie 实现不能正确且有点高效地通过按全局累积权重排序的词进行分页,这些词与rhymingPhonemeArray(从输入生成的phonemes)完全匹配。

有没有可能解决这个问题,而不必遍历整个 Trie?考虑到(记住),输入被扩展为rhymingPhonemeArray(可能有很多种可能性,但我们实际上会将输入限制为 3 个音节,这与主题无关)。如果不可能,你能解释一下为什么不可能吗?

如果可能的话,您将如何修改此 trie 以支持分页并跳转到特定页面,而不必遍历所有内容,同时分页结果根据每个单词的累积权重进行全局排序?

Trie 实现

我们最终得到的 Trie 如下:

class TrieNode {
  constructor() {
    this.children = {}; // Store child nodes
    this.isWord = false; // Flag to check if node marks the end of a word
    this.word = null; // Store the word if this is an end node
    this.cumulativeWeight = 0; // Store the cumulative weight for sorting
    this.phonemeLength = 0; // Length of the phoneme sequence
  }
}

class PhoneticTrie {
  constructor() {
    this.root = new TrieNode(); // Root node of the trie
  }

  // Insert words and phoneme clusters into the Trie
  insert(word, phonemes, weight) {
    let node = this.root;
    for (let phoneme of phonemes) {
      if (!node.children[phoneme]) {
        node.children[phoneme] = new TrieNode();
      }
      node = node.children[phoneme];
    }
    node.isWord = true;
    node.word = word;
    node.cumulativeWeight = weight; // Store the cumulative weight at this node
    node.phonemeLength = phonemes.length; // Store the length of the phoneme sequence
  }

  // Global sorting of rhyming phoneme matches using a min-heap
  searchWithPagination({ phonemes, limit, page, length }) {
    const minHeap = new MinHeap(); // Min-Heap to store sorted results

    // Generate rhyming phoneme variations
    const rhymingPhonemesArray = expandToRhymingPhonemes(phonemes);

    // Search the Trie for all rhyming phoneme sequences and insert results into the global heap
    for (let rhyme of rhymingPhonemesArray) {
      this.searchAndInsertToHeap(rhyme.phonemes, this.root, rhyme.weight, minHeap, length);
    }

    // Paginate results directly from the globally sorted heap
    const paginatedResults = [];
    const startIndex = (page - 1) * limit;
    let index = 0;

    while (minHeap.size() > 0 && paginatedResults.length < limit) {
      const wordData = minHeap.extractMin();
      if (index >= startIndex) {
        paginatedResults.push(wordData.word);
      }
      index++;
    }

    return paginatedResults;
  }

  // Search a specific phoneme sequence in the Trie and insert matches into the heap
  searchAndInsertToHeap(phonemes, node, rhymeWeight, heap, targetLength, depth = 0, phonemeSeq = []) {
    if (depth === phonemes.length) {
      if (node.isWord && node.word) {
        // If length filtering is specified, ensure the word matches the phoneme length
        if (!targetLength || node.phonemeLength === targetLength) {
          heap.insert({
            word: node.word,
            cumulativeWeight: node.cumulativeWeight + rhymeWeight, // Add the rhyme weight here
            phonemeLength: node.phonemeLength, // Include phoneme length for sorting
          });
        }
      }
      return;
    }

    const phoneme = phonemes[depth];
    if (!node.children[phoneme]) return; // No match
    this.searchAndInsertToHeap(phonemes, node.children[phoneme], rhymeWeight, heap, targetLength, depth + 1, [...phonemeSeq, phoneme]);
  }
}


class MinHeap {
  constructor() {
    this.heap = []; // Store the heap elements
  }

  // Insert an item into the heap based on its weight and phoneme length
  insert({ word, cumulativeWeight, phonemeLength }) {
    this.heap.push({ word, cumulativeWeight, phonemeLength });
    this.bubbleUp(this.heap.length - 1);
  }

  bubbleUp(index) {
    let currentIndex = index;
    while (currentIndex > 0) {
      const parentIndex = Math.floor((currentIndex - 1) / 2);
      // Sort primarily by cumulative weight, secondarily by phoneme length
      if (
        this.heap[currentIndex].cumulativeWeight > this.heap[parentIndex].cumulativeWeight ||
        (this.heap[currentIndex].cumulativeWeight === this.heap[parentIndex].cumulativeWeight &&
          this.heap[currentIndex].phonemeLength > this.heap[parentIndex].phonemeLength)
      ) {
        break;
      }
      [this.heap[currentIndex], this.heap[parentIndex]] = [this.heap[parentIndex], this.heap[currentIndex]];
      currentIndex = parentIndex;
    }
  }

  // Extract the item with the lowest weight
  extractMin() {
    if (this.heap.length === 0) return null;
    if (this.heap.length === 1) return this.heap.pop();
    const min = this.heap[0];
    this.heap[0] = this.heap.pop();
    this.bubbleDown(0);
    return min;
  }

  bubbleDown(index) {
    const lastIndex = this.heap.length - 1;
    while (true) {
      let leftChildIdx = 2 * index + 1;
      let rightChildIdx = 2 * index + 2;
      let smallestIdx = index;

      if (
        leftChildIdx <= lastIndex &&
        (this.heap[leftChildIdx].cumulativeWeight < this.heap[smallestIdx].cumulativeWeight ||
          (this.heap[leftChildIdx].cumulativeWeight === this.heap[smallestIdx].cumulativeWeight &&
            this.heap[leftChildIdx].phonemeLength < this.heap[smallestIdx].phonemeLength))
      ) {
        smallestIdx = leftChildIdx;
      }
      if (
        rightChildIdx <= lastIndex &&
        (this.heap[rightChildIdx].cumulativeWeight < this.heap[smallestIdx].cumulativeWeight ||
          (this.heap[rightChildIdx].cumulativeWeight === this.heap[smallestIdx].cumulativeWeight &&
            this.heap[rightChildIdx].phonemeLength < this.heap[smallestIdx].phonemeLength))
      ) {
        smallestIdx = rightChildIdx;
      }
      if (smallestIdx === index) break;

      [this.heap[index], this.heap[smallestIdx]] = [this.heap[smallestIdx], this.heap[index]];
      index = smallestIdx;
    }
  }

  size() {
    return this.heap.length;
  }
}

function expandToRhymingPhonemes(phonemes) {
  // todo: this function would have a huge map of phoneme substitutions...
  const phonemeSubstitutions = {
    "k": ["g", "p"], // Example of substitutions for the phoneme "k"
    "æ": ["a", "e"], // Example for "æ"
    "t": ["d", "s"], // Example for "t"
  };

  const rhymingPhonemesArray = [];

  function generateRhymes(sequence, depth = 0, currentWeight = 0) {
    if (depth === sequence.length) {
      rhymingPhonemesArray.push({ phonemes: sequence.slice(), weight: currentWeight });
      return;
    }

    const phoneme = sequence[depth];
    const substitutions = phonemeSubstitutions[phoneme] || [phoneme];

    for (const sub of substitutions) {
      generateRhymes(
        [...sequence.slice(0, depth), sub, ...sequence.slice(depth + 1)],
        depth + 1,
        currentWeight + (sub === phoneme ? 0 : 1)  // Substitution adds to weight
      );
    }
  }

  generateRhymes(phonemes);
  return rhymingPhonemesArray;
}

const trie = new PhoneticTrie();
trie.insert("glad", ["g", "l", "a", "d"], 3);
trie.insert("grad", ["g", "r", "a", "d"], 2);
trie.insert("blad", ["b", "l", "a", "d"], 4);
trie.insert("grin", ["g", "r", "i", "n"], 5);

// Search for similar words to "g-l-a-d" and paginate the results, with optional length filtering
const resultsPage1 = trie.searchWithPagination({
  phonemes: ["g", "l", "a", "d"],
  limit: 2,
  page: 1,
  length: 4, // Only consider words with exactly 4 phonemes
});

const resultsPage2 = trie.searchWithPagination({
  phonemes: ["g", "l", "a", "d"],
  limit: 2,
  page: 2,
  length: 4,
});

console.log(resultsPage1); // Output: ["grad", "glad"] (sorted by weight and length)
console.log(resultsPage2); // Output: ["blad", "grin"]

更新

  1. 这里是对 Trie 进行进一步扩展/尝试以解决这一关键问题的要点。
javascript
  • 1 1 个回答
  • 46 Views

1 个回答

  • Voted
  1. Best Answer
    Matt Timmermans
    2024-10-18T20:36:45+08:002024-10-18T20:36:45+08:00

    你遇到的基本问题是,trie 隐含着全局词汇排序,但你希望你的输出符合全局权重排序。

    trie 对你没有帮助。

    适合该问题的数据结构由按权重排序的单词数组与倒排索引组合而成,对于您可能想要押韵的每个语音后缀,提供押韵词索引的有序列表。

    您或许可以使用开源倒排索引实现。

    • 1

相关问题

  • 合并排序不起作用 - Javascript代码:即使在调试后也无法找到错误

  • select.remove() 方法工作得很奇怪[关闭]

  • useOpenWeather() 中总是出现 401 res -react-open-weather lib [重复]

  • 输入元素没有只读属性,但字段仍然不可编辑[关闭]

  • 如何编辑 D3.js RadialTree 的第一个节点半径?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    Vue 3:创建时出错“预期标识符但发现‘导入’”[重复]

    • 1 个回答
  • Marko Smith

    为什么这个简单而小的 Java 代码在所有 Graal JVM 上的运行速度都快 30 倍,但在任何 Oracle JVM 上却不行?

    • 1 个回答
  • Marko Smith

    具有指定基础类型但没有枚举器的“枚举类”的用途是什么?

    • 1 个回答
  • Marko Smith

    如何修复未手动导入的模块的 MODULE_NOT_FOUND 错误?

    • 6 个回答
  • Marko Smith

    `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它?

    • 3 个回答
  • Marko Smith

    何时应使用 std::inplace_vector 而不是 std::vector?

    • 3 个回答
  • Marko Smith

    在 C++ 中,一个不执行任何操作的空程序需要 204KB 的堆,但在 C 中则不需要

    • 1 个回答
  • Marko Smith

    PowerBI 目前与 BigQuery 不兼容:Simba 驱动程序与 Windows 更新有关

    • 2 个回答
  • Marko Smith

    AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String”

    • 1 个回答
  • Marko Smith

    我正在尝试仅使用海龟随机和数学模块来制作吃豆人游戏

    • 1 个回答
  • Martin Hope
    Aleksandr Dubinsky 为什么 InetAddress 上的 switch 模式匹配会失败,并出现“未涵盖所有可能的输入值”? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge 为什么这个简单而小的 Java 代码在所有 Graal JVM 上的运行速度都快 30 倍,但在任何 Oracle JVM 上却不行? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini 具有指定基础类型但没有枚举器的“枚举类”的用途是什么? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer 何时应使用 std::inplace_vector 而不是 std::vector? 2024-10-29 23:01:00 +0800 CST
  • Martin Hope
    Chad Feller 在 5.2 版中,bash 条件语句中的 [[ .. ]] 中的分号现在是可选的吗? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench 为什么双破折号 (--) 会导致此 MariaDB 子句评估为 true? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng 为什么 `dict(id=1, **{'id': 2})` 有时会引发 `KeyError: 'id'` 而不是 TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String” 2024-03-20 03:12:31 +0800 CST
  • Martin Hope
    MarkB 为什么 GCC 生成有条件执行 SIMD 实现的代码? 2024-02-17 06:17:14 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve