How to roll an imaginary 6-sided die in your head

Um, “my $n = ord(uc($l)) - 64;” and “split(//, $_)” would argue that I haven’t violated that Perl Law :wink:

I’ve always enjoyed the “Swiss army chainsaw” term…

I know a lot of languages, but really not all that many “scripting” languages. So I know Python is in vogue now, but I’m not too well versed in it (nor Ruby).

I write a lot of Swift, and the Swift version would have been more readable, but Swift really really really leans into String being a collection of Unicode runes…so I’m not sure this would have been quite this easy. Or maybe more accurately it is one of the little corners of Swift that has experienced ht most change so I would be writing for “Swift of the last 18 months or so” not Swift…and given how niche Swift is already, Perl seems like a better choice.

I could have made it more compact and more cryptic by using APL though, but that has an even smaller audience of people that could have used it :wink:

3 Likes

OT (“on-topic” in this case): There are a number of Perl-based mindhacks in the book. That is all.

If you really could write the APL for this, you have my not only my respect, but a fair bit of awe. One university course of APL (okay, a third of a course in APL), was enough to let me see that therein lay deep magic and I have not the arcane blood flowing through my brains to handle it. (Lisp was bad enough…)

(Earlier today, I was just reading an article about J that included full source for an interpreter in a few dozen lines… Deep, deep magic.)

I keep meaning to dive into Swift, but as I get older, it gets harder to pick up languages that aren’t just re-skins of the old fascist trifecta: Pascal, Java and C#. I need a really intriguing project to justify the effort of learning it. (Boy do I miss being 30).

I will say that if I never have to learn another domain-specific language that started out as an internal tool from people who didn’t want to create a language, I will consider myself blessed. Of course I’d have harsher words for such people if I hadn’t accidentally made a configuration protocol for a tool into a Turing-complete language in an effort to make the tool flexible enough for the myriad of uses people wanted to use it for. In the end, I’d replaced programming in a good language (changing the source) with programming in my awful home-brew (changing the configuration) and no-one but a programmer could do either :-(.

2 Likes

that’s a funny observation. Parent is quite readable compared to some of the Perl I’ve seen (and truth be told written) in the past

Seems you could, instead of using the numerology thing, use the Major system of memorizing numbers. So “dog” would be 17, hence mod9 8, so thrown out. “hammer” would be 34, so mod9 7, hence thrown out. Umm . . . frog? 847, so mod9 . . . 1! Got a number!

Benefit of this approach would be that you’d have the Major system (which I’ve been using since I was 16 (or, you know, “dish”) years old) for free, and not have to do a lot of addition in your head. I doubt it’d be nearly as random, though. Still, the Major system is hella useful.

Yahtzee!

image

6 Likes

How to roll dice in your head:

First get your left temple trepanned…

4 Likes

hi book author: I do recognize that. But over in physics and computerese, “pseudorandom” not only means a pattern that’s not predictable, but a pattern that produces numbers uniformly distributed across the domain. Aside from my attempt at humor, I was pointing out that there’s a lot of bias in the set of words that will come to a given person’s mind. I am willing to bet that if you had a bunch of test subjects generate a few thousand dice rolls this way, you could predict with decent accuracy which of them would be responsible for a new set of rolls which show a particular distribution.

3 Likes

Hmmm, it would be quite a challenge actually, I don’t think I have used APL in anger for over 30 years. I use to write a lot of it though. An AD&D character generator, a cross reference tool for APL source code, a lot of bad games. I’m not actually 100% sure how to convert letters to ordinal values anymore. Maybe something like Quad-CS search (is that veridic iota?) some vector of letters? If I had that part summing each row is easy (plus reduce), modulo is I think the varadic vertical bar? Yeah, if I had an APL interpreter laying around I could probably do it. Hmmm, forget Quad-CS actually because you can make your own array of letters, and if you have an APL that happens to have lower case you can just use mod26 to map it the way you like.

Yeah, I could do it given an APL interpreter and a spare hour or so. Which is weak because someone who actually uses APL day to day should take under five minutes to do it.

It is a fun language. I use it to drive a laser cutter (and I don’t actually mean like control what the stepper motors do, I mean create SVG designs to carve game box inserts, or once Boing Boing ran that jigsaw puzzle thing, I have a imitation of that, and some other fun stuff). It is indirectly C-derived so it won’t be completely alien. However if you haven’t used something that makes heavy use of ad-hoc extensions (or as Obj-C calls them categories, or Modulo-3’s partial revelations) it will be somewhat mind expanding.

Really though the compelling use-case for Swift is making applications/apps on Apple’s platforms. So if you don’t have any interest in iOS apps, or watch apps, or TVos apps (does anyone?), or Mac apps, it is a harder sell. It runs on other platforms, but just isn’t as exciting there.

If you do want to code on Apple’s platforms though it really is nicer then the other choices almost all the time (once in a great while I miss ObjC’s aggressive dynamism…and conceptual simplicity)

1 Like

I think it is only readable because the problem domain is very simple…but thanks anyway! :wink:

@cjankelson: Fair enough; I am probably misusing the word “pseudorandom” – I researched the hack almost 10 years ago. Thanks for setting me straight. However, hacks are quick and dirty. This one is meant to provide some fun on road trips, poolside, and other places you’re unlikely to have any physical dice or be able to roll them if you do. Doing high end statistical analysis of it to make big money in Vegas is silly. (Analyzing it for curiosity or s/g is not, of course.) Moreover, if you want a less lumpy spread of numbers, as I mentioned, pick words from your environment instead of from your head.

1 Like

It’s not random if it comes out of a deterministic machine. There are plenty of non-deterministic machines that generate random sequences which pass the relevant statistical tests. https://en.wikipedia.org/wiki/Hardware_random_number_generator

3 Likes

I don’t know perl from mindfuck, but I wrote a python script to download a book from Project Gutenberg and then do the same analysis:

from urllib import request
from nltk import word_tokenize
import numpy as np
from scipy.stats import chi2
from tabulate import tabulate

# get book from Project Gutenberg
url = "http://www.gutenberg.org/files/2554/2554-0.txt" # Crime and Punishment
#url = "https://www.gutenberg.org/files/1342/1342-0.txt" # Pride and Prejudice
#url = "https://www.gutenberg.org/files/84/84-0.txt" # Frankenstein
#url = "https://www.gutenberg.org/files/98/98-0.txt" # A Tale of Two Cities

response = request.urlopen(url)
raw = response.read().decode('utf-8-sig')
tokens = word_tokenize(raw)

# filter out non-alpha characters and make all lowercase
filt_words = [w.lower() for w in tokens if w.isalpha()]
# filter out all words length 2 or less
filt_words = [w for w in filt_words if len(w)>2]
    
mod9List = []
mod6List = []

for I in range(len(filt_words)):
#for I in range(1000):
    word = filt_words[I]
    sum = 0
    for J in range(len(word)):
        sum = sum + ord(word[J])-96
    
    if np.mod(sum,9)<6: # only keep 0-5
        mod9List.append(np.mod(sum,9))
    
    mod6List.append(np.mod(sum,6))

# convert to np arrays
mod9List = np.array(mod9List)
mod6List = np.array(mod6List)

## calculate chi-squared for each list
# total number of numbers in each list
N9 = len(mod9List)
N6 = len(mod6List)

# make similar array using numpy.random.randit
randArray = np.random.randint(0,6,N6)

# expected number of rolls for each number if perfectly fair and random
expect9 = N9/6
expect6 = N6/6

# count actual number of times each number appears
mod9Num = np.zeros(6)
mod6Num = np.zeros(6)
randNum = np.zeros(6)
for I in range(6):
    mod9Num[I] = np.count_nonzero(mod9List==I)
    mod6Num[I] = np.count_nonzero(mod6List==I)
    randNum[I] = np.count_nonzero(randArray==I)

# sum of squares of [difference between actual and ideal] divided by ideal
SSE6 = np.sum(np.square(mod6Num-expect6)/expect6)
SSE9 = np.sum(np.square(mod9Num-expect9)/expect9)
SSErand = np.sum(np.square(randNum-expect6)/expect6)
SSE = [SSE6,SSE9,SSErand]
methodName = ['mod(N,6) method','mod(N,9) method','rand function']

# calculate confidence critical value
confLim = 0.9999
confVal = chi2.ppf(confLim,5)

# print results to screen as table
arr1 = [methodName[0]]
arr1.extend(mod6Num.tolist())
arr2 = [methodName[1]]
arr2.extend(mod9Num.tolist())
arr3 = [methodName[2]]
arr3.extend(randNum.tolist())
tableDat = [arr1,arr2,arr3]
print('Book has {:d} words'.format(len(filt_words)))
print('')
print(tabulate(tableDat, headers = ['method',1,2,3,4,5,6]))
print('')
for I in range(len(SSE)):
    print('Normalized sum of squared error (SSE) for {} = {:5g}'.format(methodName[I],SSE[I]))
print('Critical value of chi-squared distribution for 99.99% confidence level = {:5g}'.format(confVal))
print('')
for I in range(len(SSE)):
    if SSE[I]>confVal:
        printstr = '{} SSE is greater than critical value, {:5g}% confidence level that numbers are not distributed randomly'
        print(printstr.format(methodName[I],100*confLim))

The thing is, I end up with wildly non-random distributions. For Crime and Punishment:

method               1      2      3      4      5      6
---------------  -----  -----  -----  -----  -----  -----
mod(N,6) method  24500  43816  23020  29030  23315  15064
mod(N,9) method  15804  20820  14081  14960  19924  12867
rand function    26610  26573  26465  26457  26359  26281

With the following chi-squared analysis:

Normalized sum of squared error (SSE) for mod(N,6) method = 17510
Normalized sum of squared error (SSE) for mod(N,9) method = 3183.74
Normalized sum of squared error (SSE) for rand function = 2.92951
Critical value of chi-squared distribution for 99.99% confidence level = 25.7448

mod(N,6) method SSE is greater than critical value, 99.99% confidence level that numbers are not distributed randomly
mod(N,9) method SSE is greater than critical value, 99.99% confidence level that numbers are not distributed randomly
rand function SSE is less than critical value, 99.99% confidence level that numbers are distributed randomly

Both are horrible compared to just using the rand() function, though the mod(N,6) method is even worse than the mod(N,9) method. I got similar results from Pride and Prejudice, Frankenstein, and A Tale of Two Cities.

I wonder if I did something wrong with my calculation method? I ran the chi-squared test on your numbers, and both the %6 and %9 methods passed the test quite well, with sum squared errors of 6.5 and 2.4 respectively. That’s within range of using the rand() function, which usually gives values between 2 to 6.

Anyway, since I really went overkill on this, I uploaded it to github here if anyone is interested in tinkering with it.

2 Likes

My guess: “the” and “and” create huge spikes in the distribution.

That makes a lot of sense. Both of those words are very common, and will return the same number every time. Whereas using /usr/share/dict/words will only have each word exactly once, so there is no repetition at all. I’ll try modifying the script to throw out all word repetitions.

Sure enough, that did it. Removing all repeated words gives me the following for Crime and Punishment:

method              1     2     3     4     5     6
---------------  ----  ----  ----  ----  ----  ----
mod(N,6) method  1584  1600  1551  1610  1493  1526
mod(N,9) method  1052  1057  1069  1084  1049  1035
rand function    1574  1517  1560  1546  1608  1559

Normalized sum of squared error (SSE) for mod(N,6) method = 6.66339
Normalized sum of squared error (SSE) for mod(N,9) method = 1.36464
Normalized sum of squared error (SSE) for rand function = 2.91115

Both the %6 and %9 are on par with the rand() function, just like your results.

3 Likes

Interesting summary, thanks! Quite a lot of the methods were new to me.
Lavarand gets extra points for whimsy.

Loving all the math(s, depending on your country) here, but even though I’ve tried many times to write a truly random num generator for fun I’m quite happy seeing the dice roll in my imagination - maybe I spent too much time playing Kingdom Come: Deliverance of late :wink:

I’m just posting to assure the silent many that if those instructions turned further and further into a gray blur as you continued to read, you aren’t alone.

Along those lines: Divisibility Tricks - Numberphyle