我已将非结构化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
请注意,我只需要对标签内的单词进行单词计数,而不是标签本身。
有没有办法有效地做到这一点(使用索引或词典数据?)
谢谢,
grifster
答案 0 :(得分:2)
您要求任何给定文档URI&#34;的字数和#34;。但是你假设解决方案涉及索引或词典,并且这不一定是一个好的假设。如果您希望从面向文档的数据库中获取特定于文档的内容,则通常最好直接处理该文档。
因此,让我们专注于单个文档的高效字数统计解决方案,并从那里开始。 OK?
以下是我们如何为单个元素(包括任何子元素)获取字数。这可能是您的文档的根目录: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 }
如果您想查询整个数据库,使用cts:words
的Geert解决方案可能看起来不错。它使用单词列表的词典,以及单词匹配的一些索引查找。但是对于每个单词词典单词:O(nm),它最终会为每个匹配的文档运行XML。要做到这一点,代码必须做的工作类似local:word-count
所做的工作,但一次一个字。许多单词将匹配相同的文档:&#39;&#39;可能在A和B中,然后&#39;然后&#39;可能也在A和B中。尽管使用词典和索引,通常这种方法比简单地将local:word-count
应用于整个数据库要慢。
如果要查询整个数据库并愿意更改XML,可以将每个单词包装在word
元素(或您喜欢的任何元素名称)中。然后在word
上创建一个string类型的元素范围索引。现在,您可以使用cts:values
和cts: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:words
和cts:search
获得相同的结果只需不到1秒。所以local:word-count
效率更高,而且可能足以完成这项工作。
既然您可以有效地构建字数统计地图,那么如果可以保存它呢?从本质上讲,您构建了我们自己的&#34;索引&#34;字数。这很简单,因为地图具有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-properties
或xdmp:document-get-properties
,然后调用右侧XML上的map:map
构造函数。如果您想要多个文档的字数,您可以轻松编写XQuery以将这些地图合并为一个结果。
答案 1 :(得分:0)
您通常会将cts:frequency
用于此目的。不幸的是,这只能提供给从词汇词汇中提取的值,而不能提供给词汇词典中的值。因此,我担心您必须手动计数,除非您可以将所有单词预先标记为可以放置范围索引的元素。我能想到的最接近的一件事是:
for $word in cts:words()
let $freq := count(cts:search(doc()//*,$word))
order by $freq descending
return concat($word, ' - ', $freq)
注意:doc()将搜索所有文档,因此严重缩放。但如果您对每个文档的计数感兴趣,那么性能可能对您来说足够好......