XSLT группирует смежные значения в четное распределение - PullRequest
0 голосов
/ 20 марта 2020

Я хочу равномерно распределить смежные значения в определенное количество групп, чтобы общая сумма каждой группы была больше 300, но меньше 390. Чтобы получить это, я понимаю, что значения нужно будет иногда разделять на Для достижения заданного количества групп.

Так что, если я начну с приведенного ниже, и я хочу только 5 с:

<root>
    <entry>5</entry>
    <entry>90</entry>
    <entry>55</entry>
    <entry>145</entry>
    <entry>90</entry>
    <entry>105</entry>
    <entry>270</entry>
    <entry>150</entry>
    <entry>145</entry>
    <entry>135</entry>
    <entry>140</entry>
    <entry>145</entry>
    <entry>155</entry>
    <entry>130</entry>
    <entry>125</entry>
</root>`

Я хочу получить что-то вроде этого:

<root>
  <group total="380">
    <entry>5</entry>
    <entry>90</entry>
    <entry>55</entry>
    <entry>145</entry>
    <entry>90</entry>
  </group>
  <group total="375">
    <entry>105</entry>
    <entry>270</entry>
  </group>
  <group total="390">
    <entry>150</entry>
    <entry>145</entry>
    <entry split='yes'>105</split>
  </group>
  <group total="390">
    <entry split='yes'>30</entry>
    <entry>140</entry>
    <entry>145</entry>
    <entry split='yes'>75</entry>
  </group>
  <group total="335">
    <entry split='yes'>80</entry>
    <entry>130</entry>
    <entry>125</entry>
  </group>
</root>

ближе всего я получил что-то вроде этого:

<xsl:variable name="total" select="sum(//entry)">

<xsl:variable name="set-number" select="xs:integer(ceiling($total div 390))"/>

<xsl:variable name="count" select="floor($total) div $set-number)"/>

<xsl:for-each-group select="entry" group-ending-with="*[(position() mod $count) = 0]">
   <group total="{sum(current-group())}">
      <xsl:apply-templates select="current-group()"/>
   </group>
</xsl:for-each-group>

, что дает мне:

<root>  
    <group total="150">
        <entry>5</entry>
        <entry>90</entry>
        <entry>55</entry>
    </group>
    <group total="340">
        <entry>145</entry>
        <entry>90</entry>
        <entry>105</entry>
    </group>
    <group total="565">
        <entry>270</entry>
        <entry>150</entry>
        <entry>145</entry>
    </group>
    <group total="420">
        <entry>135</entry>
        <entry>140</entry>
        <entry>145</entry>
    </group>
    <group total="410">
        <entry>155</entry>
        <entry>130</entry>
        <entry>125</entry>
    </group>
</root>

, которое дает мне правильное количество групп, но не очень равномерное распределение.

1 Ответ

0 голосов
/ 20 марта 2020

Это может быть работа для XSLT 3.0 xsl:iterate, хотя даже с этим я изо всех сил пытался обработать, суммировать и разделить входные данные за один шаг;

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    expand-text="yes"
    version="3.0">

  <xsl:param name="min" as="xs:integer" select="300"/>
  <xsl:param name="max" as="xs:integer" select="390"/>

  <xsl:output indent="yes"/>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:template match="root">
      <xsl:copy>
          <xsl:iterate select="entry">
              <xsl:param name="total" as="xs:integer" select="0"/>
              <xsl:param name="group" as="element(entry)*" select="()"/>
              <xsl:on-completion>
                  <xsl:if test="$group">
                      <group>
                          <xsl:copy-of select="$group"/>
                      </group>
                  </xsl:if>
              </xsl:on-completion>
              <xsl:variable name="value" select="xs:integer(.)"/>
              <xsl:variable name="sum" as="xs:integer" select="$total + $value"/>
              <xsl:variable name="split" select="$sum gt $max and $total lt $min"/>
              <xsl:variable name="splitted-values" as="element(entry)*">
                  <entry split="true">{$max - $total}</entry>
                  <entry split="true">{$sum - $max}</entry>
              </xsl:variable>
              <xsl:if test="$sum gt $max">
                  <group total="{$total + (if ($split) then $splitted-values[1][$split] else 0)}">
                      <xsl:copy-of select="$group, $splitted-values[1][$split]"/>
                  </group>
              </xsl:if>
              <xsl:next-iteration>
                  <xsl:with-param name="total" select="if ($sum > $max) then (if ($split) then $splitted-values[2] else xs:integer(.)) else $sum"/>
                  <xsl:with-param name="group"
                    select="if ($split and $sum gt $max)
                            then $splitted-values[2]
                            else if ($sum gt $max)
                            then .
                            else ($group, .)"/>
              </xsl:next-iteration>
          </xsl:iterate>
      </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/a9GPfu/3

Результат также отличается от того, что вы указали в качестве желаемого, я не уверен, что это какой-то просчет в вашем образце или в моем коде, но я надеюсь, что показанное использование xsl:iterate позволит вам настроить его по мере необходимости, если есть какая-то ошибка в вычислениях.

Накопление и разбиение также можно выполнить с помощью fold-left, теперь доступно в Saxon 10 также в HE Edition с открытым исходным кодом, как уже в PE или EE в Saxon 9.8 и 9.9:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="#all" 
    expand-text="yes"
    version="3.0">

    <xsl:param name="min" as="xs:integer" select="300"/>
    <xsl:param name="max" as="xs:integer" select="390"/>

    <xsl:output indent="yes" method="xml"/>

    <xsl:mode on-no-match="shallow-skip"/>

    <xsl:function name="mf:split-entry" as="element(entry)">
        <xsl:param name="content"/>
        <entry split="yes">{$content}</entry>
    </xsl:function>

    <xsl:template match="root">
        <xsl:copy>
            <xsl:apply-templates
                select="
                    fold-left(
                    entry,
                    (),
                    function ($a, $e) {
                        let $v := xs:decimal($e)
                        return
                            if (empty($a))
                            then
                                map {xs:decimal($e): $e}
                            else
                                let $last-group := $a[last()],
                                    $total := map:keys($last-group)
                                return
                                    if ($total + $v le $max)
                                    then
                                        ($a[position() lt last()], map {$total + $v: ($last-group?*, $e)})
                                    else
                                        if ($total lt $min)
                                        then
                                            let $split1 := $max - $total,
                                                $split2 := $v - $split1
                                            return
                                                ($a[position() lt last()], map {$max: ($last-group?*, mf:split-entry($split1))}, map {$split2: mf:split-entry($split2)})
                                        else
                                            ($a, map {$v: $e})
                    }
                    )"
            />
        </xsl:copy>       
    </xsl:template>

    <xsl:template match=".[. instance of map(xs:decimal, element()*)]">
        <group total="{map:keys(.)}">
            <xsl:sequence select="?*"/>
        </group>
    </xsl:template>

</xsl:stylesheet>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...