【问题标题】:Counting all unique words in an unstructured document using index data使用索引数据计算非结构化文档中的所有唯一词
【发布时间】:2014-08-20 11:10:45
【问题描述】:

我已将非结构化 HTML 文档加载到 Marklogic 中,并且, 对于任何给定的文档 URI,我需要一种使用索引/词典来为所有唯一单词提供字数的方法。

例如,假设我有下面的文件,保存在 URI“/html/example.html”下:

<html>
<head><title>EXAMPLE</title></head>
<body>
<h1>This is a header</h1>
<div class="highlight">This word is highlighted</div>
<p> And these words are inside a paragraph tag</p>
</body>
</html>

在 XQuery 中,我会通过传入 URI 来调用传入的函数并获得以下结果:

EXAMPLE 1
This 2
is 2
a 2
header 1
word 1
highlighted 1
And 1
these 1
words 1
are 1
inside 1
paragraph 1
tag 1

请注意,我只需要对标签内的单词进行字数统计,而不是标签本身。

有什么方法可以有效地做到这一点(使用索引或词典数据?)

谢谢,

骗子

【问题讨论】:

    标签: marklogic


    【解决方案1】:

    您要求“任何给定文档 URI”的字数。但是您假设解决方案涉及索引或词典,这不一定是一个好的假设。如果您想从面向文档的数据库中获取特定于文档的内容,通常最好直接处理文档。

    因此,让我们专注于单个文档的有效字数统计解决方案,然后从那里开始。好吗?

    以下是我们如何获取单个元素(包括任何子元素)的字数。这可能是您文档的根目录:doc($uri)/*

    declare function local:word-count($root as element())
    as map:map
    {
      let $m := map:map()
      let $_ := cts:tokenize(
        $root//text())[. instance of cts:word]
        ! map:put($m, ., 1 + (map:get($m, .), 0)[1])
      return $m
    };
    

    这会生成一个地图,我发现它比平面文本更灵活。每个键是一个词,值是计数。变量 $doc 已包含您的示例 XML。

    let $m := local:word-count($doc)
    for $k in map:keys($m)
    return text { $k, map:get($m, $k) }
    
    inside 1
    This 2
    is 2
    paragraph 1
    highlighted 1
    EXAMPLE 1
    header 1
    are 1
    word 1
    words 1
    these 1
    tag 1
    And 1
    a 2
    

    请注意,映射键的顺序是不确定的。如果你愿意,可以添加 order by 子句。

    let $m := local:word-count($doc)
    for $k in map:keys($m)
    let $v := map:get($m, $k)
    order by $v descending
    return text { $k, $v }
    

    如果您想查询整个数据库,Geert 使用cts:words 的解决方案可能看起来不错。它为单词列表使用词典,并使用一些索引查找进行单词匹配。但它最终会为每个单词词典单词的每个匹配文档遍历 XML:O(nm)。要正确地做到这一点,代码必须做类似于local:word-count 所做的工作,但一次一个字。许多单词将匹配相同的文档:'the' 可能在 A 和 B 中,而 'then' 也可能在 A 和 B 中。尽管使用了词典和索引,但通常这种方法比简单地将 local:word-count 应用于整个数据库。

    如果您想查询整个数据库并愿意更改 XML,您可以将每个单词包装在 word 元素(或您喜欢的任何元素名称)中。然后在word 上创建一个字符串类型的元素范围索引。现在您可以使用cts:valuescts:frequency 直接从范围索引中提取答案。这将是 O(n),成本比 cts:words 方法低得多,并且可能比 local:word-count 更快,因为根本不会访问任何文档。但是生成的 XML 相当笨拙。

    让我们返回并将local:word-count 应用于整个数据库。首先调整代码,以便调用者提供地图。这样我们就可以构建一个包含整个数据库字数的地图,并且我们只查看每个文档一次。

    declare function local:word-count(
      $m as map:map,
      $root as element())
    as map:map
    {
      let $_ := cts:tokenize(
        $root//text())[. instance of cts:word]
        ! map:put($m, ., 1 + (map:get($m, .), 0)[1])
      return $m
    };
    
    let $m := map:map()
    let $_ := local:word-count($m, collection()/*)
    for $k in map:keys($m)
    let $v := map:get($m, $k)
    order by $v descending
    return text { $k, $v }
    

    在我的笔记本电脑上,它在不到 100 毫秒的时间内处理了 151 个文档。大约有 8100 个单词和 925 个不同的单词。从 cts:wordscts:search 获得相同的结果只用了不到 1 秒。所以local:word-count 的效率更高,而且对于这项工作来说可能足够高效。

    现在您可以高效地构建字数统计图,如果您可以保存它会怎样?本质上,您将建立我们自己的字数“索引”。这很容易,因为地图具有 XML 序列化。

    (: Construct a map. :)
    map:map()
    (: The document constructor creates a document-node with XML inside. :)
    ! document { . }
    (: Construct a map from the XML root element. :)
    ! map:map(*)
    

    因此,您可以在插入或更新每个新 XML 文档时调用 local:word-count。然后将字数图存储在文档的属性中。使用 CPF 管道,或通过 RecordLoader 使用您自己的代码,或在 REST 上传端点等中执行此操作。

    如果您需要单个文档的字数统计,只需调用xdmp:document-propertiesxdmp:document-get-properties,然后调用右侧XML 上的map:map 构造函数。如果您想计算多个文档的字数,您可以轻松编写 XQuery 将这些映射合并到一个结果中。

    【讨论】:

      【解决方案2】:

      您通常会为此目的使用cts:frequency。不幸的是,这只能提供给从值词典中提取的值,而不是来自单词词典的值。因此,除非您可以将所有单词预先标记为可以放置范围索引的元素,否则恐怕您将不得不手动进行计数。我能想到的最接近的一件事是:

      for $word in cts:words()
      let $freq := count(cts:search(doc()//*,$word))
      order by $freq descending
      return concat($word, ' - ', $freq)
      

      注意: doc() 将搜索所有文档,因此扩展性很差。但是,如果您对每个文档的计数感兴趣,那么性能可能对您来说已经足够了..

      【讨论】:

      • 我建议cts:words((), 'document') 这样您就不会从属性片段中提取单词。还要考虑使用哪种排序规则。 $freq 的表达式可以多次计算单词:c。使用count(cts:search(collection()//*[not(*)],$word)) 稍微好一点,但在&lt;a&gt;foo&lt;b&gt;bar&lt;/b&gt;&lt;/a&gt; 中错过了foo
      • 可能更糟,因为它计算包含单词的元素,而不是单词本身。如果需要扩展,请使用值词典,否则使用 mblakele 的建议..
      猜你喜欢
      • 2012-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-22
      • 2021-05-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-12
      相关资源
      最近更新 更多