Fizzbuzz and complex filtering

I’ve just implemented a simple fizzbuzz lister:

\define fizzbuzz()
<$let
f3="[remainder[3]match[0]then[fizz]else[]]"
f5="[remainder[5]match[0]then[buzz]else[]]">
<$list variable=num filter="[range[100]]">
  <$set name=numfizz filter="[<num>subfilter<f3>]">
  <$set name=numbuzz filter="[<num>subfilter<f5>]">
  <$set name=numfizzbuzz filter="[<numfizz>addsuffix<numbuzz>!is[blank]else<num>]">
  <<numfizzbuzz>><br>
  </$set></$set></$set>
</$list>
</$let>
\end

<<fizzbuzz>>

The output is something like “1 2 fizz 4 buzz fizz 7 8 fizz buzz 11 fizz 13 14 fizzbuzz 16 17 fizz 19 buzz fizz 22 (…)”

That’s all and dandy but what about if I want this output a list within a filter for further computing? This fuzzbizz is just a simple example for cases where something trivial via list widget is not trivial without it…

I could use a temporary tiddler and store each of my values within it in the $list loop (and retrieve the whole dictionary afterward, getting my list within a filter), but this would not work if not executed in the context of a button click so it’s not what I want.

For the price of more complex subfilters and by using :map, I could transform the initial list of numbers “num” into list of things like0 “num,fizzy” then “num,fizzy,buzzy” then finally fizzbuzzy but that would be far from ideal and nice. (examples: 15 → 15,fizz → 15,fizz,buzz → fizzbuzz or 37 → 37, → 37, → 37 or 78 → 78,fizz → 78,fizz, → fizz)

Any ideas?

I think this is right on the edge of the kind of situation where using the <$wikify> widget is indicated. That is to say:

  • The logic cannot be expressed as a filter
  • It is known in advance that the data does not contain text that could be inadvertently wikified (this can happen with user input, for instance)

In this instance, at first sight I think it may actually be possible to simplify things to a single filter (perhaps using some subfilters to make it more readable), which is always an interesting challenge.

Thank you@jeremyruston. Actually, I’ve had just thought about $wikify and was about to write about it when I read your post. Yes, wikify is a soolution. But not in every case, as the context of wikify is blank and may not house some stuff required by your filters (not the case in my example). I encountered that in real life.

At the end of the day, I wonder if the problem does not reside in that the filter operate on and generated only lists of texts. What I would need is an ability to operate on a list of tuples or the ability to generate external list of texts or external hashtables of texts and be able to fetch data from it.

something like this within a filter run:

[range[100]makeHash:fizz<fizzSubfilter>makeHash:buzz<fizzSubfilter>] 
:map[hash[fizz]addsuffix[<currentTiddler>hash[buzz]]!is[blank]else<currentTiddler>]

Note that this solution also used a non existent feature: having a filter run as an argument of a filter operator and use its results as the effective argument of the filter operator. Such a possibility would be marvelous and ease the writing of complex filters.

Without that dreamed possibility, I would need another :map within my :map and I’m not sure how to do it yet.

Here is my solution in a single filter.

\define fizzbuzz()
<$let
f3="[remainder[3]match[0]then[fizz]else[ ]]"
f5="[remainder[5]match[0]then[buzz]else[ ]]"
f35="
[subfilter<f3>]
:map[<..currentTiddler>subfilter<f5>addprefix<currentTiddler>trim[ ]!is[blank]else<..currentTiddler>]
">
<$list variable=num filter="[range[100]] :map[subfilter<f35>]">
  (<<num>>)
</$list>
</$let>
\end

<<fizzbuzz>>

I put parenthesis to show results without room for errors.

Note the space I had to resort to retain values and the final trim to get rid of any space. Try the same code without that space and see the havoc!

Before 5.2, it would have been impossible. Let’s hope this fun problem will eventually help someone one day…

An interesting example :slight_smile: I’d do it that way:

\define mod15() [remainder[15]match[0]then[116]else<currentTiddler>]
\define mod5() [remainder[5]match[0]then[101]else<currentTiddler>]
\define mod3() [remainder[3]match[0]then[103]else<currentTiddler>]

{{{ [range[16]] :map[subfilter<mod15>] :map[subfilter<mod5>] :map[subfilter<mod3>]  }}}

Where 103=fizz, 101=buzz and 116=fizz buzz

This would be good enough to run matches against the “abstract” numbers.

If it needs to be human readable it would need some extra “conversion” runs, because remainder can’t deal with “non number” values. It always returns 0 :confused:

\define mod15() [remainder[15]match[0]then[116]else<currentTiddler>]
\define mod5() [remainder[5]match[0]then[101]else<currentTiddler>]
\define mod3() [remainder[3]match[0]then[103]else<currentTiddler>]

\define fizzbuzz() [match[116]then[fizz buzz]else<currentTiddler>]
\define buzz() [match[101]then[buzz]else<currentTiddler>]
\define fizz() [match[103]then[fizz]else<currentTiddler>]

{{{ [range[16]] :map[subfilter<mod15>] :map[subfilter<mod5>] :map[subfilter<mod3>] :map[subfilter<fizz>] :map[subfilter<buzz>] :map[subfilter<fizzbuzz>]   }}}

just a thought.

That dream bubbles up in my brain at least a dozen times a month.

CAR and CDR, anyone?

The last time I (pipe-dreamed?) and gave it any serious thought, I realized all operators would need to be listops effectively, the whole language becoming Lisp-with-sqr-brackets – which it is almost, anyway.

Then my pipe ran out of weed :wink:

@pmario How clever of you of using :map in this way. Yet you had to take care of multiples of 15 by creating a dedicated filter, which gives you no brownie points :-b

Here is my generalization for taking care of multiple of 7 and 11. The trick was to recognize that :map forbid having something other than equally the same number of tiddlers in the output than in the input and so I had to create a list. But I used :map in a way similar to you in order to accumulate results but not final results!

\define fizzbuzz()
<$let
f3="[remainder[3]match[0]then[fizz]else[ ]]"
f5="[remainder[5]match[0]then[buzz]else[ ]]"
f7="[remainder[7]match[0]then[kazz]else[ ]]"
f11="[remainder[11]match[0]then[mozz]else[ ]]"
f-all="
[!is[blank]]
:map[<..currentTiddler>subfilter<f3>]
:map[<..currentTiddler>subfilter<f5>append<currentTiddler>join[,]]
:map[<..currentTiddler>subfilter<f7>append<currentTiddler>join[,]]
:map[<..currentTiddler>subfilter<f11>append<currentTiddler>split[,]trim[ ]!is[blank]reverse[]join[]!is[blank]else<..currentTiddler>]
">
<$list variable=num filter="[range[1157]]:map[subfilter<f-all>]">
  (<<num>>)
</$list>
</$let>
\end

<<fizzbuzz>>

@CodaCoder It would be great indeed.

Anotger wet dream of mine is a forth-like filter capabilities. Example:

[enlist[6 15 8]forth[swap - *]]

which would produce “42”. As a filter operator, I think I could do it with my current proficiency in tiddlywiki.

But the real things would be the ability to use user-supplied operator in the forth context, thus allowing really clever ways of dealing with data, numeric or string at least. And this is beyond me yet: I would have to code all the operators I need in the javascript tiddler defining the forth macro, which is not such a good idea.

That’s spooky/strange - TW often reminds me of Forth!

Lisp, Forth (where are you, Prolog?) Jeez, that takes me back in time. :nerd_face:

@CodaCoder Maybe we should use a lisp interpreter in javascript to rewrite tiddlywiki in lisp, that would help :smiley: But that would be a littje sluggish, eventually. Maybe a case for websm?

</old-memory>

1 Like

Funnily enough, I have indeed been interested in FORTH for a long time. Back in 1983 I published a book that included an interpreter for “FROTH”, a FORTH-like threaded interpreted language written in 6502 machine code:

The late Joe Armstrong was a good friend before he sadly passed away. With reference to TiddlyWiki’s filter language, he used to tell me that I had invented the monad. I’ve still no real idea what a monad is, but it cheered me up as it sounds very grown up.

2 Likes

I should also add that conversely I have no Lisp experience; I suspect that TiddlyWiki would have started out differently if I had done.

:rofl:

No doubt. But having said that, I’m oft surprised at how little it reflects its JavaScript underpinnings. “Respect” for having stayed true to your vision (whatever it might have been).

@jeremyruston Looking at wikipedia for monad, it brings forth the concept of defensive programming with things like Maybe types. That’s a much more interesting concept than fiddling with forth so I’m starting a new thread about that.

@jypre I had no idea what you were talking about with “Fizz Buzz” until I too used wikipedia. Then scanning this thread you were talking about products and multiples.

Personally I have had a long run obsession with the Prime numbers and could build half a dozen or more ways to check fizz buzz as I do for primes. The easiest method is a sieve which will help you evaluate any number divisible by 3 or 5

I will see if I can provide an example.

This is a shorter solution

<$list filter="[range[1,100]] :map[{!!title}remainder[15]match[0]then[FizzBuzz]else{!!title}]">
<$list filter="[{!!title}!match[FizzBuzz]remainder[5]match[0]then[Buzz]else{!!title}] :map[{!!title}!match[FizzBuzz]!match[Buzz]remainder[3]match[0]then[Fizz]else{!!title}]"/>
</$list>

You can store this in a variable if you like to use this later as Jeremy explained above, then you can wrap the code into a macro like below

\define FizzBuzz-Macro(n:100)
<$list filter="[range[1,$n$]] :map[{!!title}remainder[15]match[0]then[FizzBuzz]else{!!title}]">
<$list filter="[{!!title}!match[FizzBuzz]remainder[5]match[0]then[Buzz]else{!!title}] :map[{!!title}!match[FizzBuzz]!match[Buzz]remainder[3]match[0]then[Fizz]else{!!title}]"/>
</$list>
\end


!! Example

<$wikify name=fizzbuzz text=<<FizzBuzz-Macro 50>> >
<$text text=<<fizzbuzz>> />
</$wikify>

Produces:

1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz 41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz

Note: The example is just to show you can store results in a variable!

Alternative ii: String Concatenation

If the above solution is complicated or you want to use this in older TW, then this is the second SIMPLE solution

\define FizzBuzz-Macro-alt(n:100)
<$list filter="[range[1,$n$]]">
<$list filter="[{!!title}remainder[3]match[0]then[Fizz]]"/><$list filter="[{!!title}remainder[5]match[0]then[Buzz]]"/>
<$list filter="[{!!title}remainder[3]!match[0]then{!!title}remainder[5]!match[0]then{!!title}]"/>
</$list>
\end

!! Example

<$wikify name=fizzbuzz text=<<FizzBuzz-Macro-alt 50>> >
<$text text=<<fizzbuzz>> />
</$wikify>

produces

1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz 41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz

Note: The example is just to show you can store results in a variable!

@Mohammad The first macro is a nice cascading effect and a very nice example of why using $list without setting a variable can be handy. It’s not as easily readable as with such variables though.

The second macro is also interesting but alas does not produce the intended result. Have you tested it? You should filter for remainder[3], because after it, you have a number between 0 and 2 and never a multiple of 5 so that’s why your macro is broken.

I propose a fix for this one, using the filter operator:

\define FizzBuzz-Macro-alt(n:100)
<$let div3="[remainder[3]match[0]]" div5="[remainder[5]match[0]]">
<$list filter="[range[1,$n$]]">
<$list filter="[{!!title}remainder[3]match[0]then[Fizz]]"/><$list
       filter="[{!!title}remainder[5]match[0]then[Buzz]]"/>
<$list filter="[{!!title}!filter<div3>!filter<div5>]"/>
</$list>
</$let>
\end

!! Example

<<FizzBuzz-Macro-alt 66>>

This last example provides quite an easy way to have also special cases for multiples of 7 or 11 as I did previously. This is even the simplest way to do it!

Ah, I forgot then! see corrected code above!

Jean-Pierre
Note that the second solution uses string concatenation and is a ling of smart solution!

1 Like