我需要一些保持顺序的XQuery序列合并的帮助

时间:2017-09-05 17:39:30

标签: xquery marklogic

我正在研究一个函数来合并一组序列,这些序列将尽可能地保留所有序列的顺序。在所有序列上执行不同的值($序列)不会保留顺序。

我有以下MarkLogic XQuery代码:

xquery version "1.0-ml";

declare function local:map-sequence($map, $list as xs:string*) {
    let $count := fn:count($list) - 1
    return for $idx in (1 to $count)
                  return if (map:contains($map, $list[$idx]))
                  then map:put($map, $list[$idx], fn:distinct-values((map:get($map, $list[$idx]), $list[$idx + 1])))
                  else map:put($map, $list[$idx], $list[$idx + 1])
};

declare function local:first($map) {
let $all-children := for $key in map:keys($map) return map:get($map, $key)

return distinct-values(map:keys($map)[not(.=$all-children)])
};

declare function local:next($map, $key as xs:string) {
    if (map:contains($map, $key))
    then if (fn:count(map:get($map, $key)) eq 1)
         then map:get($map, $key)
         else 
            let $children := map:get($map, $key)
            return 
                for $next in $children
                let $others := $children[fn:not(.=$next)]
                let $descedents := local:descendents($map, $next)
              return if ($descedents[.=$others])
                     then $next
                     else ()
    else ()
};

declare function local:descendents($map, $key as xs:string) {
    for $child in map:get($map, $key)
    return ($child, local:descendents($map, $child))
};

declare function local:sequence($map, $key as xs:string) {
    let $next := local:next($map, $key)
    return if (fn:count($next) gt 1)
           then 
                    for $choice in $next
                    return $choice

           else if (fn:count($next) eq 1)
                then ($next, local:sequence($map, $next))
                else ()
};

let $map := map:map()
let $seq1 := local:map-sequence($map, ('fred', 'barney', 'pebbles'))
let $seq2 := local:map-sequence($map, ('fred', 'wilma', 'betty', 'pebbles'))
let $seq3 := local:map-sequence($map, ('barney', 'wilma', 'betty'))

let $first := local:first($map)

return ($map,
for $top in $first
       return ($top, local:sequence($map, $top))
 )

它返回

{"barney":["pebbles", "wilma"], "fred":["barney", "wilma"], "wilma":"betty", "betty":"pebbles"}
fred
barney
wilma
betty
pebbles

它仍然需要工作。如果你添加:

let $seq4 := local:map-sequence($map, ('fred', 'bambam'))

bambam没有出现。我仍在努力,但如果其他人有建议,那么我想听听他们。

谢谢, 洛伦

1 个答案:

答案 0 :(得分:2)

据我了解您的问题,每个序列代表一个值的层次结构,因此从序列("foo", "bar", "baz")我们可以遵循"foo" < "bar""foo" < "baz""bar" < "baz"应该最好保持最终的顺序。

从您的预期输出中,您似乎希望将值从具有最小数量(传递)前导(在您的情况下为"fred")的值排序到具有最多值({{1}有四个前辈:"pebbles")。

我无法访问 MarkLogic 及其专有地图,因此我将使用标准的 XQuery 3.0 地图。底层算法应易于翻译。

作为第一步,我们构建了至少一个输入序列中找到的每个唯一值的所有直接前任的映射。由于XQuery 3.0地图无法就地修改,因此我们使用fn:fold-left(...)逐步构建一个地图。另请注意,即使每个列表的第一个元素也会添加到地图中,并带有一个空的前导序列。

("barney", "fred", "betty", "wilma")

接下来我们需要这个前辈映射的传递闭包,因此我们需要收集一系列前辈可以从给定键到达的所有值。我们可以使用简单的深度优先搜索

来完成此操作
declare function local:add-preds($map0, $list as xs:string*) {
  fn:fold-left(
    1 to fn:count($list),
    $map0,
    function($map, $idx) {
      map:put(
        $map,
        $list[$idx],
        (: add the current predecessor to the list :)
        fn:distinct-values((map:get($map, $list[$idx]), $list[$idx - 1]))
      )
    }
  )
};

这会转换您的示例初始前置映射

declare function local:transitive($preds) {
  map:merge(
    for $key in map:keys($preds)
    return map:entry($key, local:all-predecessors($preds, $key, $key)[not(. = $key)])
  )
};

declare function local:all-predecessors($succ, $key, $seen0) {
  fold-left(
    map:get($succ, $key),
    $seen0,
    function($seen, $next) {
      if($next = $seen) then $seen
      else local:all-predecessors($succ, $next, ($seen, $next))
    }
  )
};

并将其转换为

map {
  "bambam": "fred",
  "pebbles": ("barney", "betty"),
  "fred": (),
  "wilma": ("fred", "barney"),
  "barney": "fred",
  "betty": "wilma"
}

使用该地图,您的排序现在变得非常简单:只需获取地图中的所有键,按其前任的数量排序,然后输出:

map {
  "bambam": "fred",
  "pebbles": ("barney", "fred", "betty", "wilma"),
  "fred": (),
  "wilma": ("fred", "barney"),
  "barney": "fred",
  "betty": ("wilma", "fred", "barney")
}

这会返回您想要的结果:let $map0 := map{} let $map1 := local:add-preds($map0, ('fred', 'barney', 'pebbles')) let $map2 := local:add-preds($map1, ('fred', 'wilma', 'betty', 'pebbles')) let $map3 := local:add-preds($map2, ('barney', 'wilma', 'betty')) let $map4 := local:add-preds($map3, ('fred', 'bambam')) let $trans := local:transitive($map4) for $key in map:keys($trans) order by count(map:get($trans, $key)) return $key