Filter operator to generate random numbers

There was a(nother) brief discussion on GitHub whether TW should have a random number generator with the result that an inclusion in the core is currently unlikely.
https://github.com/Jermolene/TiddlyWiki5/issues/7235

With some good ideas from Mario and Jeremy, I made this relatively simple filter operator into a plugin:
plugin_yaisog_random-filter_v0.9.2_20230128.json (10.0 KB)

Writing the documentation and examples took what felt like 80% of the development time, so I’m not gonna write too much here. If you’re interested, just take a look at the readme. And maybe read Jeremy’s misgivings in the GitHub thread linked above.

Have a nice day
Yaisog

3 Likes

Maybe I’m a party pooper but I’m pretty sure someone already implemented this? Regardless, thank you @Yaisog :smiley:

…and this part is less likely that they also included:

Writing the documentation and examples …

1 Like

I think there is a typo in the readme. It says range here

A discussion on random number is here also:

Random Tiddler Button - Tips & Tricks - Talk TW (tiddlywiki.org)

Where there are two other macro/filter (by Saq and Telumire)

Corrected. Didn’t update the version number as the change is too insignificant.
Thanks for the heads-up.

Here is a way to shuffle a list that needs nothing more than the random[] operator.

\define shuffle() [enlist<accumulator>remove<currentTiddler>append<currentTiddler>join[ ]]
\define map-position-to-item() [enlist<accumulator>nth<currentTiddler>reduce<shuffle>,<accumulator>]

<$let itemList="a b c d e f g h i j"
      numberOfItems = {{{ [enlist<itemList>count[]] }}}
      randomizer={{{ [range<numberOfItems>,[1]] :map[random[1],<currentTiddler>] +[join[ ]] }}}>
  <$list filter="[enlist:raw<randomizer>reduce<map-position-to-item>,<itemList>enlist-input[]]" template="$:/core/ui/ListItemTemplate" />
</$let>

This was one of the harder pieces of code to figure out, because I repeatedly ran into things that were not possible to do with pure WikiText.

It uses two nested reduce[] filters, the first of which basically only transforms the current index in the randomizer list to a tiddler title according to the current order in the accumulator (whose initial value is the itemList). The inner shuffle then moves that title to the end of the list. Basically, an item is chosen at random from the list, moved to the end, and the next item is chosen from the remaining items on the original list. After each item of the original list has been moved, only the shuffled list remains.

If the move[] filter allowed for a variable parameter N instead of immutably defining it in its suffix, then this complex nested structure might have been avoided.

It got also a bit more complicated since I wanted to use a pregenerated randomizer together with an item list that could be generated or changed by refresh. In the above snippet, the list is reshuffled with every keypress or click. But a pregenerated randomizer can be made persistent until changed by an action, as shown in the plugin docs. Hence, a number of randomizers could be generated e.g. at startup and used later. The exact number of items per randomizer need not be known in advance, it just has to be at least as large as the largest list to be shuffled later. One can then use [enlist:raw<randomizer>last<numberOfItems>] to get a matching sublist.

Alternatively, one could generate a randomizer of the correct length for a given list with an ActionWidget on demand.

I verified that the list shuffled this way is truly random. Here is a histogram of the 5th item of the shuffled list for 100,000 repetitions (each starting with an unshuffled list, of course):

image

Since I used MATLAB, my unshuffled list was not “a b c …”, but “1 2 3 …”.
The other items are distributed the same.

Have a nice shuffle
Yaisog

2 Likes

Added a 6-of-49 lotto example, which is very similar to the list scrambler above, to the plugin examples and raised the version to 0.9.1.

When playing with solutions for Is there a way to filter tiddlers based on field value alone, not the field name - #7 by arunnbabu81 , I’m finding it would be desirable if a/the random[] operator could behave like so:

If there is no argument to random[] then it treats the preceding filter run as input and spits out a random item from that list.

1 Like

I would separate this to a different filter operator. As of now, random[] is a math operator, and as such shouldn’t do listops. I’d rather extend the nth[] operator to take the parameter random or some such. Would be more semantic, I guess? Or something completely new like chooserandom[].

PS (next day): A new filter would have the option of returning a variable number of items from the list, like chooserandom[<numberOfItems>]. As one can see from the scrambler example above or the Lotto example in the plugin doc, removing an already chosen item from the list before choosing the next one is messy. That could be done much cleaner in JavaScript. An optional suffix like :all would return all items of the input list, but shuffled– it is the same as settting <numberOfItems> above to the total number of items in the input list, but without having to determine that number in advance.
Since all of this is difficult to do even with having a filter that returns a single random item as you proposed, a new filter with this additional functionality would actually cover a lot more usecases, like showing a random selection of neighbors when there are many.

2 Likes

The list shuffle becomes a lot simpler with the choose-random filter:
https://talk.tiddlywiki.org/t/filter-operator-to-select-random-titles-from-the-input/6045

I think I’ve just used js random before @random in conjunction with the mathcell plugin.

https://steachertests.tiddlyhost.com/#Random%20Question%20with%20marking