Solving FizzBuzz in XSLT 2.0
I recently posted the following to the good folks who subscribe and contribute to XSL-List, a group of folks who are responsible for teaching both myself, and *MANY* others all we know about the XSLT language. Here's the text of my post,
Hey All,
Building upon the theme provided by Dr. Kay a while back[1], I recently wrote a post that showcased how one could not only use XSLT to solve the problem, but do so in a way that provides for dynamic data evaluation in regards to the variables used, and what those variables should print out when the test evaluates to true.
http://dev.aol.com/blog/mdavidpeterson/2007/03/14/fizz-buzz-in-xslt-1.0
My question to you all is this: Using this same premise, how would one solve this in XSLT 2.0, and in particular, given that this touches smack-dab center on the benefits that FXSL provides the XSLT developer, how one could use FXSL to solve this same problem?
I been bouncing around different ideas in my head all morning as to the best way to optimize this in XSLT 2.0/FXSL, but as of yet, haven't come up with something that I think really takes advantage of what XSLT 2.0/FXSL bring to the table. Ideas from the community at large?
Thanks in advance!
[1] http://blogs.msdn.com/mfussell/archive/2004/05/13/130969.aspx#131098
"It's a little bit odd to try and prove your point with an example that makes no use of XML input or XML output..."
Here's the first solution provided by none other than David Carlisle (who amongst other things, is one of the folks responsible for bringing MathML into the world)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="fizzbuzz">
<xsl:variable name="m" select="test/mod"/>
<xsl:for-each select="xs:integer(substring-before(range,'-')) to xs:integer(substring-after(range,'-'))">
<xsl:text> </xsl:text>
<xsl:variable name="r" select="$m[(current() mod @value) = @test]"/>
<xsl:value-of separator="" select="if($r) then $r else ."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
If not obvious before now, XSLT 2.0 *ROCKS*! :D
If/as any more solutions come in (I'm particularly interested in seeing how one would use FXSL to solve this problem as well), I will update this post accordingly.
Update #1 from David Carlisle,
If the input were
<fizzbuzz>
<start>1</start>
<stop>100</stop>
rather than
<fizzbuzz>
<range>1-100</range>
You could simplify that greatly.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="fizzbuzz">
<xsl:value-of separator="" select="
for $i in start to stop
return (' ',$i, test/mod[($i mod @value) = @test])
[not(position() lt last() and . instance of xs:integer)]"/>
</xsl:template>
</xsl:stylesheet>
Update #2 via Mukul Gandhi
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:output method="text"/>
<xsl:template match="fizzbuzz">
<xsl:variable name="range.start" select="xs:integer(substring-before(range,'-'))"/>
<xsl:variable name="range.end" select="xs:integer(substring-after(range,'-'))"/>
<xsl:variable name="t" select="test"/>
<xsl:for-each select="$range.start to $range.end">
<xsl:value-of
select="if (((. mod $t/mod[1]/@value) =
$t/mod[1]/@test) and ((. mod $t/mod[2]/@value) = $t/mod[2]/@test))
then concat($t/mod[1], $t/mod[2]) else if ((. mod $t/mod[1]/@value) =
$t/mod[1]/@test) then $t/mod[1] else if ((. mod $t/mod[2]/@value) =
$t/mod[2]/@test) then $t/mod[2] else ."/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Update #3 via Abel Braaksma
Hi David,
Though others have already provided good solutions, I couldn't resist upon reading your request. Coming from a Perl world, I like one-liners, and though very much against any spirit of clear code, here's a version that does away with if-statements altogether (oops, I see now that David C's second approach already had that).
I agree with David C that a slightly different input XML would make the code a fair amount more readable. With the proposed start/end it looks like this (quite like David's simplified example):
<xsl:template match="fizzbuzz">
<xsl:value-of separator=" "
select="for $i in start to end
return ((string-join(test/mod[$i mod @value = @test], ''))[.], $i)[1]" />
</xsl:template>
With the original input document, however, my attempt looks like this:
<xsl:template match="fizzbuzz">
<xsl:value-of separator=" "
select="
for $i in range/xs:integer(tokenize(., '\D')[1])
to range/xs:integer(tokenize(., '\D')[last()])
return (string-join(test/mod[$i mod @value = @test], '')[.], $i)[1]" />
</xsl:template>
Note that there's a slight change in semantics for the range value (now any separator works). I.e.:
<!-- outputs 'FizzBuzz' -->
<range>54390</range>
<!-- outputs '4 Buzz Fizz 7' -->
<range>4 to 7</range>
<!-- outputs all up to 100 -->
<range>1-100</range>
The oddbit in my code (well, it took me some time to get it right) is the string-join. Most functions return an empty sequence when provided with an empty sequence, but not string-join, hence the rather awkward conversion to a sequence and testing for self: string-join(inp-seq, '')[.]
This returns an empty sequence when it string-join returns an empty string, which will be factored out, hence the following: ((), $i)[1] will return the last value.
Thanks for the exercise :-)
Update #4 from Dimitre Novatchev
Please visit http://www.oreillynet.com/xml/blog/2007/03/fizzbuzz_20_adventures_in_beau.html for what I am now officially tagging "FizzBuzz 2.0 :: An Adventure in Beautiful Code".
Update #5 via Chris Wallace @ http://thewallaceline.blogspot.com/2007/03/fizzbuzz.html, we have our first XQuery entry. :)
I've taken the liberty of splitting the hyphenated string into two attributes.
[Not sure how to retain the indentation in this blog]
let $config :=
<fizzbuzz>
<range min="1" max="100"/>
<test>
<mod value="3" test="0">Fizz<mod>
<mod value="5" test="0">Buzz</mod>
</test>
</fizzbuzz>
for $i in ($config/range/@min to $config/range/@max)
let $s := for $mod in $config/test/mod
return
if ($i mod $mod/@value = $mod/@test)
then string($mod)
else ()
return
if (exists($s))
then string-join($s,' ')
else $i
Update #6 via Jakob @ http://www.oreillynet.com/xml/blog/2007/03/fizzbuzz_20_adventures_in_beau.html#comment-539367
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:for-each select="1 to 100">
<xsl:choose>
<xsl:when test="(. mod 3) = 0"><xsl:value-of select="'fizz'"/></xsl:when>
<xsl:when test="(. mod 5) = 0"><xsl:value-of select="'buzz'"/></xsl:when>
<xsl:otherwise><xsl:value-of select="."/></xsl:otherwise>
</xsl:choose>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
- mdavidx5's blog
- Login or register to post comments
