Wähle drei Töpfe

Jeder Topf hat vier Eigenschaften:

  • verschiedene Deckel: kein Deckel, flacher Griff, hoher Griff
  • verschiedene Farben: hell, gestreift, dunkel
  • verschiedene Zeichen: Ring, Kreuz, Doppelkreuz
  • verschiedene Zeichenanzahl: 1, 2 oder 3

Es sollen genau drei Töpfe so ausgewählt werden, dass für jede der vier Eigenschaften alle Töpfe entweder den gleichen Wert oder alle unterschiedliche Werte haben.

XML-Notation

Die Eigenschaften sind Attribute (zur Vereinfachung nenne ich diese a, b, c und d) und jedes Attribut muss einen von drei möglichen Werten annehmen (1, 2, 3).

<pots>
  <pot a="1" b="1" c="3" d="2" />
  <pot a="1" b="1" c="3" d="3" />
  <pot a="3" b="2" c="1" d="2" />
  <pot a="1" b="3" c="2" d="1" />
  <pot a="1" b="1" c="1" d="1" />
  <pot a="2" b="2" c="1" d="2" />
  <pot a="3" b="2" c="2" d="1" />
  <pot a="3" b="2" c="1" d="1" />
  <pot a="3" b="1" c="3" d="2" />
  <pot a="3" b="1" c="1" d="1" />
  <pot a="2" b="1" c="3" d="2" />
  <pot a="3" b="2" c="3" d="3" />
</pots>

Lässt sich die Lösung mit einem XPath-Ausdruck angeben? Nein, wohl nicht, und ich bin auch nur auf eine brute force-Lösung gekommen, die mir wenig elegant erscheint:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  xmlns:my="my" 
  exclude-result-prefixes="#all">
  <xsl:output indent="yes" />

  <xsl:template match="pots">
    <xsl:variable name="a" as="node()*" select="my:triples(*, 'a')" />
    <xsl:variable name="b" as="node()*" select="my:triples(*, 'b')" />
    <xsl:variable name="c" as="node()*" select="my:triples(*, 'c')" />
    <xsl:variable name="d" as="node()*" select="my:triples(*, 'd')" />
    <result>
      <!-- only triples which are present for all attributes are returned -->
      <xsl:for-each select="$a[. = $b][. = $c][. = $d]">
        <xsl:sequence select="." />
      </xsl:for-each>
    </result>
  </xsl:template>

  <!-- this function returns all valid triples for a single attribute -->
  <xsl:function name="my:triples" as="node()*">
    <xsl:param name="nodes" as="node()+" />
    <xsl:param name="attr" as="xs:string" />

    <!-- look for common values -->
    <xsl:for-each select="$nodes">
      <xsl:variable name="t1" select="." />
      <xsl:for-each select="$t1/following-sibling::*[@*[name() eq $attr] eq $t1/@*[name() eq $attr]]">
        <xsl:variable name="t2" select="." />
        <xsl:for-each select="$t2/following-sibling::*[@*[name() eq $attr] eq $t1/@*[name() eq $attr]]">
          <triple>
            <xsl:value-of select="(my:pos($nodes, $t1), my:pos($nodes, $t2), my:pos($nodes, .))" />
          </triple>
        </xsl:for-each>
      </xsl:for-each>
    </xsl:for-each>

    <!-- look for distinct values -->
    <xsl:for-each select="$nodes">
      <xsl:variable name="t1" select="." />
      <xsl:for-each select="$t1/following-sibling::*[@*[name() eq $attr] ne $t1/@*[name() eq $attr]]">
        <xsl:variable name="t2" select="." />
        <xsl:for-each select="
              $t2/following-sibling::*[@*[name() eq $attr] ne $t1/@*[name() eq $attr] 
              and 
              @*[name() eq $attr] ne $t2/@*[name() eq $attr]]">
          <triple>
            <xsl:value-of select="(my:pos($nodes, $t1), my:pos($nodes, $t2), my:pos($nodes, .))" />
          </triple>
        </xsl:for-each>
      </xsl:for-each>
    </xsl:for-each>
  </xsl:function>

  <xsl:function name="my:pos" as="xs:string">
    <xsl:param name="nodes" as="node()+" />
    <xsl:param name="node" as="node()" />
    <xsl:value-of select="count($nodes[$node >> .]) + 1" />
  </xsl:function>

</xsl:transform>

Wer kann eine elegantere Lösung anbieten?