- PR -

XSLTで集計

投稿者投稿内容
けい
常連さん
会議室デビュー日: 2001/09/12
投稿数: 48
投稿日時: 2003-08-06 00:42
XMLで与えられる、
(5,1,4,6,1,5,7,5)
と言うデータを、同じくXMLの
((1,2),(2,0),(3,0),(4,1),(5,3),(6,1),(7,1)) //1が2個、2が0個...
と言うデータに変換したいのですが、よい方法がありませんでしょうか?

#min,maxはここのログから得られたのですが...
ocean
ベテラン
会議室デビュー日: 2003/07/06
投稿数: 65
投稿日時: 2003-08-06 13:38
あまりXSLTには詳しくないので、もっとエレガントな方法があるかもしれませんが・・・

純正のXSLTだと、再帰を使う方法しか思いつきませんでした。count()で線形探索をしているので、項目が増えるとかなり遅くなります。できればスクリプトなどで処理した方が速いと思います。

///////////////////////////////////
// XML

コード:

<?xml version="1.0" encoding="Shift_JIS"?>
<root>
<item value="1"/>
<item value="4"/>
<item value="6"/>
<item value="1"/>
<item value="5"/>
<item value="7"/>
<item value="5"/>
</root>



/////////////////////////////////////
// XSLT

コード:

<?xml version="1.0" encoding="Shift_JIS"?>

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
<root>
<xsl:apply-templates select="root"/>
</root>
</xsl:template>

<xsl:template match="root">
<xsl:if test="count(item)">
<xsl:call-template name="func1">
<xsl:with-param name="min" select="item[1]/@value"/>
<xsl:with-param name="max" select="item[1]/@value"/>
<xsl:with-param name="pos" select="1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>

<!-- 最大値、最小値を取得して、それを引数に func2 を呼ぶ -->
<xsl:template name="func1">

<xsl:param name="min"/>
<xsl:param name="max"/>
<xsl:param name="pos"/>

<xsl:choose>

<xsl:when test="$pos <= count(item)">

<xsl:variable name="val" select="item[$pos]/@value"/>

<xsl:variable name="new_min">
<xsl:choose>
<xsl:when test="$min < $val">
<xsl:value-of select="$min"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$val"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>

<xsl:variable name="new_max">
<xsl:choose>
<xsl:when test="$max > $val">
<xsl:value-of select="$max"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$val"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>

<xsl:call-template name="func1">
<xsl:with-param name="min" select="$new_min"/>
<xsl:with-param name="max" select="$new_max"/>
<xsl:with-param name="pos" select="$pos + 1"/>
</xsl:call-template>

</xsl:when>

<xsl:otherwise>

<xsl:call-template name="func2">
<xsl:with-param name="val" select="$min"/>
<xsl:with-param name="max" select="$max"/>
</xsl:call-template>

</xsl:otherwise>

</xsl:choose>

</xsl:template>

<!-- 渡した値$valの個数を表示し、$val+1を引数に func2 を呼ぶ -->
<!-- $val > $max のとき終了する -->
<!-- 線形探索のため、非常に遅い -->
<xsl:template name="func2">

<xsl:param name="val"/>
<xsl:param name="max"/>

<xsl:if test="$val <= $max">
<item value="{$val}" count="{count(item[@value=$val])}"/>
<xsl:call-template name="func2">
<xsl:with-param name="val" select="$val + 1"/>
<xsl:with-param name="max" select="$max"/>
</xsl:call-template>
</xsl:if>

</xsl:template>


</xsl:stylesheet>



[ メッセージ編集済み 編集者: ocean 編集日時 2003-08-06 15:33 ]
MMX
ぬし
会議室デビュー日: 2001/10/26
投稿数: 861
投稿日時: 2003-08-06 14:01
数列の発生に、ダミーの定数を使う例です

<xsl:variable name="OneToSeven">
<n/><n/><n/><n/><n/><n/><n/>
</xsl:variable>

<xsl:for-each select="$OneToSeven">
<xsl:variable name="Num" select="position()" />
<xsl:value-of select="concat('(',$Num,',',count(itemNo[.=$Num]),')')" />

雰囲気はこんな感じです、XSLTによっては node-set関数を使ったり
N要素をM要素でくくらないと動きません。
XSLT2.0 が使える場合はもっと簡単に書けるとおもいます。
処理速度は全然考えていません。
「仕分け集計」とか一般データ処理はXSLT初期の設計目標に入っていませんから。
XSLT1.0 には配列もハッシュもありません、DOM木があるだけですし
内容テキストを切り刻む、便利な文字列処理もありません。
本格的データ処理を含んだものは XQuery などの方向と思います。

XSLT Performance in .NET
http://www.oreillynet.com/pub/a/dotnet/2003/07/14/xsltperf.html
改造版のXSLTでの性能表
http://www.xml.com/pub/a/2003/08/06/exslt.html?page=3
ネットワークゲートウェイがXML処理の新分野を切り開く
http://www.atmarkit.co.jp/news/200306/12/xml.html
XSLT最適化の道はこれからまだまだです。

[ メッセージ編集済み 編集者: MMX 編集日時 2003-08-08 09:43 ]

[ メッセージ編集済み 編集者: MMX 編集日時 2003-08-08 10:04 ]
ocean
ベテラン
会議室デビュー日: 2003/07/06
投稿数: 65
投稿日時: 2003-08-07 15:27
引用:

数列の発生に、ダミーの定数を使う例です



面白いですね、これ。

引用:

XSLTによっては node-set関数を使ったり
N要素をM要素でくくらないと動きません。



環境依存が許されるならと、MSXML4を前提に書き直してみました。
msxsl:node-set()を使います。前に投稿したコードよりはかなり速いですが、
やはりエレガントさとはほど遠いです。XSLT2.0に期待です。

コード:
<?xml version="1.0" encoding="Shift_JIS"?>

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:msxsl="urn:schemas-microsoft-com:xslt">

  <xsl:template match="/">
    <root>
      <xsl:apply-templates select="root"/>
    </root>
  </xsl:template>

  <xsl:template match="root">
    <xsl:call-template name="count">
      <xsl:with-param name="list" select="item/@value"/>
    </xsl:call-template>
  </xsl:template>

<!--
///////////////////////////////////////////////////
// パブリック
-->

  <!--
    集計する
  -->
  <xsl:template name="count">
    <xsl:param name="list"/>
    <xsl:variable name="sorted-list-tree">
      <xsl:for-each select="msxsl:node-set($list)">
        <xsl:sort select="." data-type="number" order="ascending"/>
        <dummy><xsl:value-of select="."/></dummy>
      </xsl:for-each>
    </xsl:variable>
    <xsl:variable name="sorted-list" select="msxsl:node-set($sorted-list-tree)/dummy"/>
    <xsl:variable name="max-position" select="count(msxsl:node-set($sorted-list))"/>
    <xsl:if test="$max-position">
      <xsl:call-template name="count-impl">
        <xsl:with-param name="sorted-list" select="$sorted-list"/>
        <xsl:with-param name="cur-position" select="1"/>
        <xsl:with-param name="max-position" select="$max-position"/>
        <xsl:with-param name="cur-value" select="msxsl:node-set($sorted-list)[1]"/>
        <xsl:with-param name="max-value" select="msxsl:node-set($sorted-list)[$max-position]"/>
        <xsl:with-param name="count" select="0"/>
      </xsl:call-template>
    </xsl:if>
  </xsl:template>

  <!--
    集計結果を表示する
  -->
  <xsl:template name="print">
    <xsl:param name="value"/>
    <xsl:param name="count"/>
    <item value="{$value}" count="{$count}"/>
  </xsl:template>

<!--
///////////////////////////////////////////////////
// プライベート
-->

  <!--
    集計の下請け
  -->
  <xsl:template name="count-impl">
    <xsl:param name="sorted-list"/>
    <xsl:param name="cur-position"/>
    <xsl:param name="max-position"/>
    <xsl:param name="cur-value"/>
    <xsl:param name="max-value"/>
    <xsl:param name="count"/>
    <xsl:if test="$cur-value <= $max-value">
      <xsl:choose>
        <xsl:when test="$max-position < $cur-position">
          <xsl:call-template name="print">
            <xsl:with-param name="value" select="$cur-value"/>
            <xsl:with-param name="count" select="$count"/>
          </xsl:call-template>
        </xsl:when>
        <xsl:when test="msxsl:node-set($sorted-list)[$cur-position] = $cur-value">
          <xsl:call-template name="count-impl">
            <xsl:with-param name="sorted-list" select="$sorted-list"/>
            <xsl:with-param name="cur-position" select="$cur-position + 1"/>
            <xsl:with-param name="max-position" select="$max-position"/>
            <xsl:with-param name="cur-value" select="$cur-value"/>
            <xsl:with-param name="max-value" select="$max-value"/>
            <xsl:with-param name="count" select="$count + 1"/>
          </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
          <xsl:call-template name="print">
            <xsl:with-param name="value" select="$cur-value"/>
            <xsl:with-param name="count" select="$count"/>
          </xsl:call-template>
          <xsl:call-template name="count-impl">
            <xsl:with-param name="sorted-list" select="$sorted-list"/>
            <xsl:with-param name="cur-position" select="$cur-position"/>
            <xsl:with-param name="max-position" select="$max-position"/>
            <xsl:with-param name="cur-value" select="$cur-value + 1"/>
            <xsl:with-param name="max-value" select="$max-value"/>
            <xsl:with-param name="count" select="0"/>
          </xsl:call-template>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:if>
  </xsl:template>

</xsl:stylesheet>


ほむら
ぬし
会議室デビュー日: 2003/02/28
投稿数: 583
お住まい・勤務地: 東京都
投稿日時: 2003-08-07 17:06
ども、ほむらです。
誰も使用していないのでもしかしたら
環境依存なのかもしれませんが。。。。
---------------------
XML文書はocean氏の物を使用しています
コード:
<?xml version="1.0" encoding="shift-jis"?>
<xsl:stylesheet version="1.0" 
 xmlns="http://www.w3.org/1999/xhtml"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xml:lang="ja">
    <xsl:template match="/root">
        全ノード数は <xsl:value-of select="count(child::item)" /> です
        <xsl:for-each select="item">
            <xsl:sort data-type="number" select="@value"/>
            <xsl:apply-templates select="."/>
        </xsl:for-each>
    </xsl:template>
    <xsl:template match="item">
        <xsl:variable name="v" select="@value" />
        <!-- 自分よりも前に同じノードがあった場合は集計済みなので無視 -->
        <xsl:if test="count(preceding-sibling::item[@value=$v]) = 0">
            <!-- 自分よりも後ろにあるノードの集計 -->
            (<xsl:value-of select="@value"/>, <xsl:value-of select="count(following-sibling::item[@value=$v])+1"/>),
        
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>


出力結果:
全ノード数は 7 です (1, 2), (4, 1), (5, 2), (6, 1), (7, 1),

テストはWindows98SE MSXML4 SP1です
ocean
ベテラン
会議室デビュー日: 2003/07/06
投稿数: 65
投稿日時: 2003-08-07 18:34
こんにちは、ほむらさん。

このコードは自分で組んだ後で知りましたが、0個も出力する仕様のようだったので、見なかったことにしました。

試しに1792個のノードで計測したところ、

最初の投稿:7.5sec
最後の投稿:1.4sec
ほむらさんのコード:3.7sec

でした。思ったほど速度差もないので、もし0個を出力しなくて良いのなら、私のコードは忘れてください。

ほむら
ぬし
会議室デビュー日: 2003/02/28
投稿数: 583
お住まい・勤務地: 東京都
投稿日時: 2003-08-07 21:47
ども、ほらむです。
さすがに数がいっぱいあると遅すぎですね。
手抜きはよくないです(笑
引用:

試しに1792個のノードで計測したところ、

最初の投稿:7.5sec
最後の投稿:1.4sec
ほむらさんのコード:3.7sec


というわけで修正です。
コード:
<xsl:if test="count(preceding-sibling::item[@value=$v]) = 0">
という1行を
<xsl:if test="name(preceding-sibling::item[@value=$v]) = ''">
に変更すると倍くらい速くなります


ocean
ベテラン
会議室デビュー日: 2003/07/06
投稿数: 65
投稿日時: 2003-08-07 22:48
おお、ほむらさんの新コードだと0.7secでした。すごい。

<xsl:if test="name(preceding-sibling::item[@value=$v]) = ''">

のところって、キャッシュとか最適化とか働いてるんでしょうか。線形探索に見えたので、ここまで速くなるとは思いませんでした。

色々いじっていて気づいたのですが、

<xsl:sort data-type="number" select="@value"/>

はなくても速度が変わりませんでした。なので、ソートが速度に関係しているわけでもなさそうです。面白い・・・

単純な上に高速とあっては・・・私のコードは完全に忘れてください。


[ メッセージ編集済み 編集者: ocean 編集日時 2003-08-07 22:56 ]

スキルアップ/キャリアアップ(JOB@IT)