Added functions to do numeric base conversions:
hex2dec [0, 1, 2, "a", "1a", "1f", 20]
Examples:
input <- "01001001 00101100 00100000 01100110 01101111 01110010 00100000 01101111 01101110 01100101 00101100 00100000 01110111 01100101 01101100 01100011 01101111 01101101 01100101 00100000 01101111 01110101 01110010 00100000 01101110 01100101 01110111 00100000 01110010 01101111 01100010 01101111 01110100 00100000 01101111 01110110 01100101 01110010 01101100 01101111 01110010 01100100 01110011 00100001 00001010"
robot2english <- glue(dec2asc(bin2dec(split)))
robot2english(input)
Fixed bug in ApplyAST/apply when argument is an object but not an array. Allows the following definition of robot2english work:
robot2english <- _ |> split |> bin2dec |> dec2asc |> glue
robot2english(input)
Fixed apply
function to work more consistently with the apply infix operator @@
.
Changed the default separators in
Added more HTML functions:
Examples:
grid(caption("Superheroes"), heading["Name","City","Alter-Ego"], row ["Clark Kent","Metropolis","Superman"], row ["Bruce Wayne","Gotham","Batman"])
span ["H", sub "2", "0"]
paragraph ["E = m c", sup "2"]
color("red", "DOES NOT COMPUTE!")
color("green", "Money!")
color("blue", "Sky")
span [strike("Delete Me"), " ", u("and add me instead!")]
Added translate
function for character-by-character translation.
translate("abcdefg","abc","xyz")
translate("abcdefg",{"a": "x", "b": "y", "c":"z"})
translate("abcdefg",["a", "b", "c"], ["x", "y", "z"])
/* Replace a set of chars with 1 char */
translate("abcdefg","abc","z")
translate("abcdefg",["a", "b", "c"], ["z"])
/* Using translate to delete a character */
translate("abcdefg","abc","")
translate("abcdefg",["a", "b", "c"], [])
Added padleft/padright(string, width, padding_character) for formatting strings.
Added array functions. Note: these are non-destructive:
Examples:
shift [2,3,5]
unshift([2,3,5], 11)
unshift([2,3,5],[11,13])
Added back print
function
Added debug
function to show info about internal representation of objects.
Only changed the editor to process query string arguments:
echo on
inside the Palamedes interpreter does nothing. The echo checkbox is the only way to effect output in this way.Examples:
This URL will load and run The Most Powerful Dice script file with the code window hidden:
editor.html?script=MostPowerfulDice.pal&hide=1
This URL simply rolls a d20. It turns on echoing so that you can see the output without using any HTML functions:
This URL also rolls a d20, leaves echoing off, but wraps the output in HTML with the text function:
Changed the Unicode for set
to the wave arrow ↜
and the ascii to <~
. Mnemonic: squiggle means set
. Removed all the other Unicode and ascii set
symbols, because they were eyesores. Sorry for changing this so much. This is the very last time. Promise.
Added ascii symbol ++
for concat
.
Added new interface to edit and run lengthy scripts.
Added ability to output html:
LU_Surprise <- lookup [d6, 1 => "Surprised 1 round", 2 => "Surprised 2 rounds", 3--6 => "No surprise"]
chargen <- ["str", "int", "wis", "con", "dex", "cha", "gold"] -> (6#3d6)++[10*3d6]
html [ ...
h1("Big Heading"), ...
text("Here is an ordered list: "), ...
ol("item "+[1..6]), ...
hr(), ...
text("The sum of squares 1 to 10 is "), ...
em(Σ([1..10]^2)), ...
br(), ...
text("The code to do this is "), ...
code("Σ([1..10]^2)"), ...
blockquote("The sum of the square roots of any two sides of an isosceles triangle is equal to the square root of the remaining side!\n-- Homer J. Simpson"), ...
table(pc(p(3d6))), ...
barchart(p(2d6)), ...
table(LU_Surprise), ...
comment("This is an HTML comment!"), ...
choice("class", [0 => "Choose a class...", 1 => "Fighter", 2 => "Magic User", 3 => "Cleric"]), ...
button("Roll up a character...", "table(chargen)"), ...
input("text", "name", "Frobozz", "Character's name? "), br(), ...
button("Roll for surprise", "text(LU_Surprise)"), br(), ...
text("Find out more at "), ...
link("http://palamedes.altervista.org","Palamedes") ]
Changed map to allow you to iterate over a JSON object with a function that takes a key/value pair. Variable _1
is the key, _2
the value. For example,
map(_1 in ["b", "c"] ? _2^2 : _2, {"a": 3, "b": 2})
(Removed merge
and aggregate
functions introduced in a60. These were not documented.)
Added merge
function that combines two json objects using a function:
merge(_1 in ["b", "c"] ? _2 + _3, {"a": 3, "b": 2}, {"b": 7, "c": 11})
merge(_1 in ["b", "c"] ? [_2, _3], {"a": 3, "b": 2}, {"b": 7, "c": 11})
Parentheses around the arguments of functions that take multiple parameters are now mandatory!
orderstat d20, 3, 1 /* Do NOT do this anymore */
orderstat(d20, 3, 1) /* Do this instead */
binom 6, 1/6 /* Do NOT do this anymore */
binom(6, 1/6) /* Do this instead */
map
is now implemented as a higher-order function that takes a function and an array as arguments:
map 2 * n^2 + 1, n, [1..10] /* Do NOT do this anymore */
map(2 * _^2 + 1, [1..10]) /* Do this instead */
Ditto for filter
.
There is also a structure-preserving version of map
called fmap
(for flat map) that operates elementwise on nested arrays like matrices, tensors, trees and graphs.
You may create the array [1..10]
more simply as 1--10
.
lookup
now works differently than if
. It still takes an array argument. The first element of the array is a distribution. The rest are pairs of arrays of ranges and lookup values. If a random variate from the distribution falls with a range, the corresponding lookup value is returned. You may pass a lookup to the p
function in order to find its probability distribution.
Examples:
MISCELLANEOUS_WEAPONS <- lookup [z100, ...
01--25 => "10 Magic Arrows", ...
26--40 => 3d10 + " Magic Arrows", ...
41--55 => "Dagger +1 vs Man-Sized Opponents, +2 vs Goblins and Kobolds", ...
56--60 => "Dagger +2 vs Man-Sized Opponents, " + ...
"+3 vs Orcs, Goblins and Kobolds", ...
61--65 => "Magic Bow", ...
66--70 => "Axe +1", ...
71--80 => "Mace +2", ...
81--85 => "War Hammer +1", ...
86--89 => "War Hammer +2", ...
90 => "War Hammer +3, 6 in. Throwing Range with Return", ...
91--96 => "Spear +1", ...
97--99 => "Spear +2", ...
00 => "Spear +3"]
'MISCELLANEOUS_WEAPONS NB. review its definition
p MISCELLANEOUS_WEAPONS NB. find its prob. distribution
MISCELLANEOUS_WEAPONS NB. generate one randomly
10 # MISCELLANEOUS_WEAPONS//listify NB. generate ten randomly
The tests subdirectory in the source code ZIP archive contains a fairly complete and working treasure generator. Magic swords and jewels aren't implemented yet. Hope to get everything complete and debugged in version a61.
There is a new operator introduced to simplify this application. Expressions like this:
10--60: 35%
work as follows:
If `flip(35%)` is true, then
If 60/10 is an integer, then
return {10}d{60/10}
Else
return choose 10--60
Else
return 0
Another helpful feature added is that if the variable json_formatter
is defined, then all JSON objects will pass through this function before being output. In this app, we use it to format the keys cp, sp and gp with the function commify(1000*_). See the code for the exact implementation. To get rid of this formatting, execute void json_formatter
.
Tables and matrices are now output in HTML format unless you set the plaintext
variable to true, which may be done with plaintext on
or set plaintext = true
.
There is a new autosize
variable, true by default, which will resize the first column of a table to the width of its longest value.
Plaintext tables still use the colwidth
and colgap
variables if autosize
is off.
You can now produce a barchart of a distrtibution by passing it to the barchart function. Barcharts are output in HTML format unless you set the plaintext
variable to true.
In[16] := 2d6//p//barchart
Out[16] :=
key value
2 █████
3 ███████████
4 █████████████████
5 ███████████████████████
6 █████████████████████████████
7 ███████████████████████████████████
8 █████████████████████████████
9 ███████████████████████
10 █████████████████
11 ███████████
12 █████
You can lump a distribution into bins with the bin
function.
Examples:
In[1] := modifiers <- {"-3": [3], "-2": 4--5, "-1": 6--8, "0": 9--12, "1": 13--15, "2": 16--17, "3": [18]}
Out[1] := modifiers
In[2] := table modifiers
Out[2] :=
key value
-3 [3]
-2 [4,5]
-1 [6,7,8]
0 [9,10,11,12]
1 [13,14,15]
2 [16,17]
3 [18]
In[3] := ranges <- bin(3d6, [3,4--5,6--8,9--12,13--15,16--17,18])
Out[3] := ranges
In[4] := tab ranges
Out[4] :=
key value
[13,14,15] 21.30%
[16,17] 4.17%
[18] 0.46%
[3] 0.46%
[4,5] 4.17%
[6,7,8] 21.30%
[9,10,11,12] 48.15%
In[5] := prob_of_modifier <- ranges after modifiers
Out[5] := prob_of_modifier
In[6] := tab prob_of_modifier
Out[6] :=
key value
-3 0.46%
-2 4.17%
-1 21.30%
0 48.15%
1 21.30%
2 4.17%
3 0.46%
You may now use arbitrary expressions as values in JSON expressions.
Examples:
Generate D&D characters:
chargen <- {"str": 3d6, "int": 3d6, "wis": 3d6, "con": 3d6, "dex": 3d6, "cha": 3d6, "gold": 10*3d6}
'chargen NB. review definition
chargen NB. randomly generate 1 character
10#chargen//listify NB. randomly generate 10 characters and format as list
Nest two JSON objects and do arithmetic on their values via fmap:
mu <- {"alpha": 1, "beta": 2, "gamma": 3}
nu <- {"delta": 10, "epsilon": [1..4], "zeta": mu}
fmap(_^2, mu)
fmap(2*_+1, nu)
Tables of complex objects should print corectly
In[3] := mytable <- {"alpha": {"beta": 2, "gamma": 3}, "delta": 4, "epsilon": {"zeta": 5, "eta": 6}, "omega": 00--09}
Out[3] := mytable
In[4] := table mytable
Out[4] :=
key value
alpha {"beta":2,"gamma":3}
delta 4
epsilon {"zeta":5,"eta":6}
omega [0,1,2,3,4,5,6,7,8,9]
list
just lists the command history in a table.run
re-runs the command history, but outputs the results in one big array, with the output of the first command going in the first array element, and so on.new
resets all variables and clears the command history.cls
scrolls the screen up to clear it.vars
makes a table of all variable definitionsdefine variable_name
shows the definition of just one variable; the short form is 'variable_name
(an apostrophe prefixing the variable name)help function_name
(see below)There are also new commands to selectively delete history, functions, variables, SQL tables, and conditional probabilities (see below).
The following REPL cmds only work in the nodejs cli:
dir
displays the files in the pwd; or you can specify a pathshow
dispalys the contents of a given filesave
saves your command history to "dice.sav" by default or you can specify a filename; the file may be loaded and run with load
load
loads and runs the commands in the file "dice.sav" by default or you specify a filenamescript
transcribes command input and output to the file "dice.out". To choose a different filename, use the function Transcribe
instead -- script
is just a macro that calls this function with the default filename. This function appends to the file. Since the transcription contains both input and output, it is unsuitable for loading with the load
command, but you can display its content with the show
command.noscript
is a macro to stop transcribing command input and output to the file "dice.out"These variables are pre-defined:
mob ← 1 - cdf + p /* meets-or-beats function */
tab ← table(percent) /* format as % and display in table */
tbl ← table(percent(p)) /* calc prob, format %, display in table */
pct ← percent(_) /* format as % w/ 2 decimals */
pc ← percent(_,0) /* format as % w/o decimals */
Example:
In[6] := tbl(2d6)
Out[6] :=
key value
2 2.78%
3 5.56%
4 8.33%
5 11.11%
6 13.89%
7 16.67%
8 13.89%
9 11.11%
10 8.33%
11 5.56%
12 2.78%
There is an experimental framework for a help system.
help
: Entering help function_name
will display a short help string on the given function. However, only a quarter of the functions have help strings so far. Hope to document all of them in the next release, a61.functions
: lists the names of all builtin functions. Use vars
to see pre-defined and user-defined functions and variables.operators
: only stubbed out now, but in release a61 or a62, this will show a list of all operators, and help operator_name
will display a help string for the given operator.Use these functions to delete portions of the program.
forget history_number
deletes* the command corresponding to the number in the command history. Useful when you want to get rid of certain commands prior to using run
to re-run the entire program. (* Replaces it with noop
to be exact.)void variable_name
deletes the given variable. Use with caution: Some builtin functions rely on constants defined as variables.efface function_name
deletes the given builtin function. Useful b/c sometimes the names of un-needed builtins conflict with names you'd like to use. Use with caution.trash table_name
deletes a SQL table.cut
resets conditional probabilities.||>
Examples:
In[17] := [1..10] ||> take 3
Out[17] := [1, 2, 3]
In[18] := foo <- _1 + _2
Out[18] := foo
In[19] := [1..3] ||> foo [3,5,7]
Out[19] := [4, 7, 10]
In[20] := foo <- _1^_2
Out[20] := foo
In[21] := [1..3] ||> foo [3,5,7]
Out[21] := [3, 25, 343]
Examples:
/* Multiply 3 times 5 to get 15 */
apply(_1 * _2, 3, 5)
[3,5] |> _1 * _2
_1 * _2 @@ [3,5]
/* map takes 1 list and applies function to each element */
/* first element is the array [3,5] */
/* here map returns [15, 14], i.e., [3*5, 7*2] */
map(_1 * _2, [[3,5], [7,2]])
/* mapcar takes 2 lists, while map takes only 1 list */
/* here mapcar returns [21,10], i.e., [3*7, 5*2] */
/* NOTE: mapcar is not the same as map!!! */
apply(_1 * _2, [3,5], [7,2])
[[3,5], [7,2]] |> _1 * _2
_1 * _2 @@ [[3,5], [7,2]]
/* Not meaningful... throws exception */
[[3,5]] |> _1 * _2
apply(_1 * _2, [3,5])
_1 * _2 @@ [[3,5]]
/* Also not meaningful, b/c fmap works elementwise */
/* it never has two operands to work with... */
fmap(_1 * _2, [[3,5], [7,2]]) NB. ERROR!
Normally we supply map
with a function and an array as arguments like so:
In[1] := map(sin, [0, pi/6, pi/4, pi/2]) |> exact
Out[1] := [0, 1/2, √2/2, 1]
But we might want to create a function out of map
in which the array part is variable. In this case, the value of the function must be held like so:
In[2] := func <- map(hold(sin), _)
Out[2] := func
In[3] := func [0, pi/6, pi/4, pi/2] |> exact
Out[3] := [0, 1/2, √2/2, 1]
And in other cases, we might want to create a function out of map
in which the function part is variable. In that case, the value of the function must be evaluated immediately, like so:
In[4] := func <- map(eval, [0, pi/6, pi/4, pi/2])
Out[4] := func
In[5] := func sin |> exact
Out[5] := [0, 1/2, √2/2, 1]
Note: Evaluation control is purely exprimental and may change in behavior soon.
Use whatever style you want by defining macros. Exception: dash-style is not supported b/c it looks like subtraction. Examples using lc
, the function to convert a string to lower case
lower_case ← lc /* snake_style */
lowerCase ← lc /* humpStyle */
LowerCase ← lc /* MixedCaseStyle */
LOWERCASE ← lc /* UPPERCASESTYLE */
lowercase ← lc /* lowercasestyle */
LOWER ← lc
LCASE ← lc
toLowerCase ← lc
lowcase ← lc
lower ← lc
lazy
to define
. Used to show the definition of a variable. Same as apostrophe, e.g., define tbl
is the same as 'tbl
eval
to work with higher order functions as explained above. No other uses anymore.cond()
function returns a JSON object which lists all conditional probabilities. Use with the table
function to put in tableform.cut()
function clears/resets all conditional probabilities.ls
. Use vars
and define
reset
/clear
. Replaced with new
It's now possible to combine builtin unary functions with basic arithmetic operations (+, -, *, /, mod, ^, √) and constants (numeric, string, boolean), in order to define new functions, and apply them in a multitude of ways.
EXAMPLE: To find the probability of meeting or beating a target number, we proceed as follows. The cumulative distribution function (cdf) is the probability of being less than or equal to a target number. So it's complement, 1 - cdf
, is the probability of being greater than a target number. Finally, 1 - cdf + p
is the probability of meeting or beating a target number. We can define this in Palamedes as follows:
mob ← 1 - cdf + p
Now we can apply it just like any other unary function, using either prefix or postfix notation. For example, to show a table of probabilities of 3d6 meeting or beating a target number, we can do any of the following:
table(percent(mob(3d6))) NB. prefix notation
mob 3d6//QQ//table NB. prefix with postfix formatting
3d6 |> mob |> percent |> table NB. postfix notation
Note that when we define functions like this, we use "pointless" or point-free style, which is to say: we do not identify the arguments (or "points") on which these functions operate.
For example, we don't do this...
mob(a) ← 1 - cdf(a) + p(a) NB. DON'T DO THIS!!!
We do this instead...
mob ← 1 - cdf + p
When defining functions this way, don't overlook the value of the id
function, which you can substitute where ever you would have used the "point". For example, to define g(a) = 2*(1+a), we can do this:
g ← 2*(1+id)
Then, to apply g to 20, we can do any of the following:
g(20)
g<|20
20|>g
g 20
It's possible to use simple functions like 1 - cdf + p
without assigning them to an identifier like mob
. For example, all of the following produce equivalent results:
apply (1 - cdf + p) (x20 + x10) NB. `apply` operator
1 - cdf + p <| x10 + x20 NB. backward pipe notation
x10 + x20 |> 1 - cdf + p NB. forward pipe notation, F\#-style
x10 + x20//1 - cdf + p NB. forward pipe notation, Mathematica-style
We can apply the g function discussed above to 20 anonymously using use apply
, pipe forward or backward in several ways:
In[1] := apply (2*(1+id)) 20
Out[1] := 42
In[2] := 20 |> 1 + id |> 2 * id
Out[2] := 42
In[3] := 2*(1+id) <| 20
Out[3] := 42
Postfix notation with nullary functions is now supported:
table(History()) NB. Prefix notation with nullary function
() |> History |> table NB. Postfix notation with nullary function
()//History//table NB. Ditto
For your convenience, the meet-or-beat function mob
has been pre-defined as
mob ← 1 - cdf + p
but you are free to redefine it.
<|
because it works like forward pipe |>
in reverse. However, that particular ASCII symbol was already used by Set
. So Set
's ASCII symbol was renamed to <:
. We kept Set
's Unicode sysmbol ↜
intact. And added ↦
and :>
so that Set
expressions could be written backwards, e.g., you can write 2d6 + 1 :> damage
instead of damage <: 2d6 + 1
if that's more readable.<|
can only used once per pipeline!apply
and apply2
that were used to thread arrays of functions over arrays of values, useful in outer product expressions. These were renamed to thread
and thread2
respectively.isprime
to test the primality of a given number or array of numbers.swap(a, b, c, ...)
function to swap the values of a list of 2 or more variables. It will rotate the values of these variables one position to the left and return an array with the values in the new order.rename(a, aa)
and forget b
functions to return the values of the variables being altered.In the new cli interface, the input prompt looks like this:
In[42] :=
and in both the new web and cli interfaces, the output is formatted like this:
In[42] := 4d6k3
Out[42] := 16
Out[number or array]
is a new function that takes a number (or array of numbers) as input and returns the output corresponding to the given number(s). For example,
In[43] := 10*Out[42]
Out[43] := 160
Since Out
takes an array of numbers, you can do something more complicated, like the following. We roll 3d6 six separate times and then aggregate the outputs together in a new D&D character named merlin:
In[0] := 3d6
Out[0] := 6
In[1] := 3d6
Out[1] := 12
In[2] := 3d6
Out[2] := 7
In[3] := 3d6
Out[3] := 5
In[4] := 3d6
Out[4] := 10
In[5] := 3d6
Out[5] := 8
In[6] := set merlin = ["str","int","wis","con","dex","cha"] → Out[0..5]
Out[6] := {"str":6,"int":12,"wis":7,"con":5,"dex":10,"cha":8}
In[7] := merlin |> table
Out[7] :=
key value
cha 8
con 5
dex 10
int 12
str 6
wis 7
There's also a new History()
function that returns a JSON object with your command history. You probably want to pipe it through table
to format it:
In[8] := History() |> table
Out[8] :=
key value
0 3d6
1 3d6
2 3d6
3 3d6
4 3d6
5 3d6
6 merlin ↜ ["str", "int", "wis", "con", "dex", "cha"] → Out([0 .. 5])
7 table(merlin)
Optionally, History()
takes a start number and an end number. It will display the history starting with first number, up to but not including the second number. These numbers default to 0 and the number of the pending input, respectively.
The expression history
has been pre-defined as table(History())
, but you can re-define it.
Caveat: History()
shows expressions after they were parsed, not how they were input. This is useful for debugging expressions that don't do what was expected.
There is also a Counter()
function that has the number of the pending input.
Similarly to Out
, there is a new In[number or array]
function that can be used to redo the given line or lines of input:
In[0] := 6 # 4d6k3
Out[0] := [12, 7, 5, 15, 12, 15]
In[1] := In[0]
Out[1] := [10, 12, 11, 7, 13, 12]
In[2] := In[0]
Out[2] := [13, 9, 16, 14, 10, 14]
To test set membership, use either in
in all lowercase or the Unicode symbol ∈.
As shown earlier, the new arrow operator (→) constructs mappings (JSON objects) from two input arrays:
Dice> c gets ["a","b","c"] arrow [1,2,3]
c
Dice> c
{"a":1,"b":2,"c":3}
Dice> c after "b"
2
Dice> chargen ← ["str","int","wis", "con", "dex", "cha", "gold"] → (6 # 3d6) ⊎ [10 * 3d6]
chargen
Dice> chargen
{"str":12,"int":14,"wis":9,"con":5,"dex":9,"cha":11,"gold":140}
Dice> chargen
{"str":10,"int":11,"wis":11,"con":13,"dex":10,"cha":10,"gold":120}
Dice> set frobozz = chargen
{"str":8,"int":14,"wis":11,"con":12,"dex":12,"cha":7,"gold":130}
Dice> frobozz ∘ "str"
8
Logical implication has been renamed from "→" to "⊃"|"IMPLIES"|"Implies"|"implies".
There's new Unicode shorthand for sort
(⍋) and reverse sort (⍒) that work on arrays and strings:
Dice> ⍋[6, 4, 3, 2, 8, 7, 9, 10, 5, 1]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Dice> ⍒[6, 4, 3, 2, 8, 7, 9, 10, 5, 1]
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
Dice> ⍋"Jesse Custer"
[" ", "C", "J", "e", "e", "e", "r", "s", "s", "s", "t", "u"]
The new anagram
function works on arrays and strings:
Dice> anagram("","")
true
Dice> anagram("dog","cat")
false
Dice> anagram("Jesse Custer","Secret Jesus")
true
Dice> anagram([],[])
true
Dice> anagram([1,2,3],[3,2,1])
true
Dice> anagram([1,2,3],[3,5,1])
false
Convert numbers between Roman and Arabic numerals:
Dice> roman [1974, 2016]
[MCMLXXIV, MMXVI]
Dice> arabic ["MCMLXXIV", "MMXVI"]
[1974, 2016]
These functions are inverses of each other:
Dice> arabic(roman [1974, 2016])
[1974, 2016]
Dice> roman(arabic ["MCMLXXIV", "MMXVI"])
[MCMLXXIV, MMXVI]
Implemented affine ciphers. affine(plaintext, multiplier, shift, direction)
produces the corresponding ciphertext. By default, direction=1 means to encrypt and -1 means decrypt. For example, multiplier=25 and shift=25 results in the Atbash cipher, and multiplier=1, shift=13 results in ROT13. There are convenience functions for atbash
and rot13
, which are their own inverse. Examples:
Dice> atbash("Affine Cipher")
"Zuurmv Xrksvi"
Dice> atbash(atbash("Affine Cipher"))
"Affine Cipher"
Dice> rot13("Affine Cipher")
"Nssvar Pvcure"
Dice> rot13(rot13("Affine Cipher"))
"Affine Cipher"
Dice> affine("Affine Cipher", 5, 8)
"Ihhwvc Swfrcp"
Dice> affine("Ihhwvc Swfrcp", 5, 8, -1)
"Affine Cipher"
Range constructors now work with letters too. Here's a round about way to make a lowercase routine:
Dice> toLower gets ["A".."Z"] concat ["a".."z"] arrow ["a".."z"] concat ["a".."z"]
toLower
Dice> glue(toLower after split("WTF",""),"")
wtf
Better to use the builtin lc
function instead:
Dice> lc("WTF")
"wtf"
Here's the ATBASH cipher the hard way:
Dice> ATBASH gets ["A".."Z"] concat [unquote " "] arrow ["Z".."A"] concat [unquote " "]
ATBASH
Dice> ATBASH
{"A":"Z","B":"Y","C":"X","D":"W","E":"V","F":"U","G":"T","H":"S","I":"R","J":"Q","K":"P","L":"O","M":"N","N":"M","O":"L","P":"K","Q":"J","R":"I","S":"H","T":"G","U":"F","V":"E","W":"D","X":"C","Y":"B","Z":"A"," ":" "}
Dice> secret gets "the quick brown fox jumps over the lazy dog"
secret
Dice> glue(ATBASH after split(uc secret, ""), "")
GSV JFRXP YILDM ULC QFNKH LEVI GSV OZAB WLT
Dice> glue(ATBASH after ATBASH after split(uc secret, ""), "")
THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
Better to use the atbash
builtin function instead.
Replaced the primes(n)
function that returns the primes less than n with the Prime[number or array of 1-based indices]
function that returns the array of primes with the given indices. Examples:
In[0] := Prime(1) NB. return the first prime
Out[0] := 2
In[1] := Prime[1..10] NB. return the first ten primes
Out[1] := [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
In[2] := Prime(Prime[1..10]) NB. return the first ten Super-primes
Out[2] := [3, 5, 11, 17, 31, 41, 59, 67, 83, 109]
Super-primes are sometimes called "higher order primes" or "Prime-Index-Primes" or "PIPs." They are sequence A006450 in the OEIS.
Caveat: The Prime
function only knows about the first 1000 primes (and subsequently the first 168 Super-primes).
TODO: Create a Sieve(n)
function that will find all the primes up to the limit n. See the Sieve of Eratosthenes.
TODO: Create an IsPrime[number or array of numbers]
function that will test the primality of the input number (or numbers).
Version()
is now a function that returns a JSON object:
In[0] := version()
Out[0] := {"Name":"Palamedes 1.0-a58 (2016-08-08)","Project":"Palamedes","Version":"1.0-a58","Major Version":1,"Minor Version":0,"Stage":"Alpha","Build":58,"Date":"2016-08-08"}
In[1] := version() |> table
Out[1] :=
key value
Build 58
Date 2016-08-08
Major Version 1
Minor Version 0
Name Palamedes 1.0-a58 (2016-08-08)
Project Palamedes
Stage Alpha
Version 1.0-a58
In[2] := version() ∘ "Build" NB. Just get the build component of the version
Out[2] := 58
Internally, the version is now kept as a JSON file, rather than an HTML file.
The expression version
has been pre-defined as table(Version())
, but you may redefine it as desired.
The save
command has been updated to save your entire command history. It takes an optional string with a file name. By default, the filename is "dice.sav".
The load
command has been updated to load a file of Palamedes commands line-by-line and execute them. It takes an optional string with a file name. By default, the filename is "dice.sav".
The transcript
command has been updated to append the output of each subsequent command to a file. It takes an optional string with a file name. By default, the filename is "dice.out".
The show
command has been updated to display the contents of a file to the screen. It takes a string with a file name. This is useful in conjunction with the previous commands, e.g., show "dice.sav"
or show "dice.out"
.
The cli now takes the following options
Short option | Long Option | Use |
---|---|---|
-e | --exec | execute the following command and exit |
-h | --help | print CLI usage and exit |
-v | --version | print Palamedes version info and exit |
Examples:
node cli.js -e "6#3d6"
will roll 3d6 6 times, print the results as an array, then exit.
node cli.js < tests/dreidel.pal
will execute the given test script, then exit.
node cli.js
will enter interactive mode. You can exit with any of the commands bye
, close
, end
, quit
, stop
or Ctrl-D.
The web interface only accepts 1 line at a time and does not accept partial statements any more. However, there is a link to open a form with a textarea and a submit button that accepts longer scripts, and these scripts may contain lines with partial statements followed by an ellipsis, just like the old (version a57 and prior) interface.
Omega(input)
(alias: Ω
) as a shorthand for domain(prob(input))
, i.e., the domain of the probability measure of a given input. In other words, the sample space.quantile(r,p)
(alias: Q
). Given a random variable r and a probability p, the quantile function Q(r,p)
returns the smallest value q, for which cdf(q) >= p
.fivenum(input)
as a shorthand for Q(input, [0%, 25%, 50%, 75%, 100%])
, i.e., the five-number summary.median(r)
to return all values m of the random variable r such that prob(r <= m) >= 1/2 and prob(r >= m) >= 1/2
. E.g., median(3d6) --> [10,11]
. The median median(3d6)
is different from the 50th percentile Q(3d6,50%)
since that's only the smallest value q of r such that prob(r <= q) >= 50%
.range(input)
function that returns max(input) - min(input)
. This is conventional meaning of range in statistics.range
to codomain
(alias: image
) to avoid conflict with new definition. In set theory, range means either codomain or image. Nota Bene: This change breaks the TALI example, so that's been re-written to reflect the new terminology.midrange(input)
function that returns (max(input) + min(input))/2
. This is the conventional meaning of midrange in statistics.Added a bunch of aliases to existing functions. See below for details.
Added reduce
(AKA fold, accumulate, aggregate, compress, or inject). E.g., reduce {_1 + _2}, [1 .. 4] → 10
scan
(AKA prefix sum, cumulative sum or running sum). E.g., scan{_1+_2},[1..4] → [1, 3, 6, 10]
plusminus
operator (and shorthand pm
and aliases as ascii +/-
and as unicode ±
). For example;-1 +/- sqrt(2) → [-2.414213562373095, 0.41421356237309515]
-1±√2 |> exact → [-(1+√2), -1+√2]
dr(n)
function that returns the digital root of a number n, i.e., n - 9 * floor((n - 1)/9)
.sizeof(input)
function that returns the length if input is an array, the count if input is an object, the length if input is a string, input itself if input is a number, 1 if input is true, 0 if input is false. Note that the size
function actually returns the shape of an input array/matrix/tensor, and has now been aliased shape
.even(input)
function that returns sizeof(input) = 0 (mod 2)
odd(input)
function that returns sizeof(input) = 1 (mod 2)
symmetric(input)
function. See below for some examples.entropy(distr)
function that returns the entropy of a distribution, i.e., -(codomain(prob(distr)) dot log_b(codomain(prob(distr))))
. Nota Bene: you must set the value of b
first! Entropy is shannon for b=2, nat for b=e, and hartley for b=10.log(a,b)
now takes 2 parameters---it takes the logarithm of a to base b; however b defaults to 10 (common logarithm), so you don't need to explicitly provide it if that's what you intend.log_b(a)
only takes 1 param, and it takes the logarithm to base b
, where b
is a variable you must set. If b
is unset, it defaults to 10, except in an entropy computation, which will throw an exception.ISO | Base | Name |
---|---|---|
lb | 2 | binary logarithm |
ln | e | natural logarithm |
lg | 10 | common logarithm |
Removed the juxtaposition operator from the AST and coded the following cases directly into the parser:
dice_expression negative_number --> subtraction
E.g., 2d6-1
(juxtaposition) same as 2d6 -1
(juxtaposition) same as 2d6 - 1
(subtraction)
dice_expression positve_number --> sampling operator
E.g., 6#3d6
same as 3d6 6
same as 3d6 6 times
(if times = 0
and base = 1
)?
array negative_number --> subtraction
array positive_integer --> subscripting index operator
number IDENTIFIER --> multiplication
IDENTIFIER negative_number --> subtraction
IDENTIFIER positive_number --> function call if identifier is a function, error otherwise
E.g., 2 π
same as 2*pi
E negative_number --> subtraction
E positive_number --> exponentiation
number E --> multiplication
E.g., E 2
same as e^2
or exp(2)
number number --> base n1 + n2
number number number --> base^2 n1 + base n2 + n3
etc.
By default, base = 1
, so 1 2 3 4
same as 1+2+3+4
(--> 10). But set base=2
and you can do binary: 1 0 1 --> 5
and 1 1 0 --> 6
and 1 1 1 --> 7
etc.
symmetric(3d6) → true
symmetric(4d6k3) → false
palindrome "Never odd or even" → true
symmetric "palindrome" → false
symmetric 1001 → true
symmetric 1901 → false
symmetric [1, 0, 0, 1] → true
symmetric [1, 9, 0, 1] → false
symmetric [[1, 0], [0, 1]] → true
symmetric [[1, 9], [0, 1]] → false
Proposed naming convention: When it makes sense, operators/functions/commands ought to have up to 4 names:
shape
for size
rational
and QQ
for rat
, since rat
sounds like an animal. QQ
is asciiml, mimicking balckboard bold Q, short for quoziente, Italian for "quotient."reverse
for rev
, because "rev" sounds like what you do to your enginerotate
for rot
, because "rot" sounds like what happens to garbageinverse
for inv
transpose
for trans
probability
and p
for prob
, i.e., the probability measureproduct
for prod
exact
for human
unique
for uniq
variance
for var
quantile
and Q
are aliasespalindrome
and symmetric
are aliasesimage
and codomain
are aliasesentropy
and H
are aliaseslookup
/if
and without
to take objects other than pure arrays. However, optimizations (specifically, limiting what gets evaluated) only happen if pure arrays are input. So you can now do this 4 without [1, 25%] concat [2, 50%, 3, 25%]
or if condition zip consequent
. But don't depend on side-effects from only one consequent being evaluated! In other words,10 # {a set if[flip, "heads", otherwise, "tails"]; a}
10 # {a set if [flip, "heads"] concat [otherwise, "tails"]; a}
10 # {a set if [flip, otherwise] zip ["heads", "tails"]; a}
if
being pure, and not zip'd, concat'd or otherwise constructed from smaller parts: 10 # {if[flip, a set "heads", otherwise, a set "tails"]; a}
10 # {if [flip, a set "heads"] concat [otherwise, a set "tails"]; a}
Added string functions:
lc(string)
- convert input string to lower case; one argument; threadable/listableglue(string, separator)
- glues the array of input strings together after removing quotes; 2 arguments; not threadable/listablesplit(string, separator, limit)
- split input string into array using separator up to limit times; 3 arguments; not threadable/listablesplit2(string, separator)
- split input string into array using separator; only 2 arguments; threadable/listabletc(string)
- convert input string to title case; one argument; threadable/listabletoNumber(string)
- convert input string to a number; one argument; threadable/listabletrim(string)
- trim whitespace from both ends of input string after removing quotation marks; one argument; threadable/listableuc(string)
- convert input string to upper case; one argument; threadable/listableChanged the sum
function.
sum
returns it.sum
concatenates them, after removing quotation marks.Change the case of a string:
Dice> lc "STOP SHOUTING"
lc("STOP SHOUTING") → "stop shouting"
Dice> uc "talk louder!"
uc("talk louder!") → "TALK LOUDER!"
Dice> tc "a day no pigs would die"
tc("a day no pigs would die") → "A Day No Pigs Would Die"
Split an array of strings on whitespace using split2
and regexes (in single quotes). This produces a matrix of strings. Note that quoes are removed from the inputs:
Dice> split2( ["A Day No Pigs Would Die","The Catcher in the Rye"], '\s+')
split2(["A Day No Pigs Would Die", "The Catcher in the Rye"],'\s+') →
[[ A, Day, No, Pigs, Would, Die ], ...
[ The, Catcher, in, the, Rye ]]
Split a string into an array of characters using split
:
Dice> split("a day no pigs would die", "")
split("a day no pigs would die","") → [a, , d, a, y, , n, o, , p, i, g, s, , w, o, u, l, d, , d, i, e]
Use the 3rd/limit parameter to split
:
Dice> split("a day no pigs would die", "")
split("a day no pigs would die","") → [a, , d, a, y, , n, o, , p, i, g, s, , w, o, u, l, d, , d, i, e]
Dice> split("a day no pigs would die", "", 5) drop 2
split("a day no pigs would die","",5) drop 2 → [d, a, y]
As a reminder, you can repeat a string using multiplication *
of a string by a number (in any order), e.g.,
Dice> 3 * " die" + "!"
3 * " die" + "!" → " die die die!"
Dice> " die" * 3 + "!"
" die" * 3 + "!" → " die die die!"
Note that trim
automatically chops off quotation marks:
Dice> 3 * " die" + "!" |> trim
trim(3 * " die" + "!") → die die die!
Dice> 3 * " die" + "!" |> trim |> quote
quote(trim(3 * " die" + "!")) → "die die die!"
glue
works like JavaScript's join
. It takes an array of strings and a separator and glues them together:
Dice> glue(["hello", "world"], ", ")
glue(["hello", "world"],", ") → hello, world
Note that the join
infix operator does a natural join between tables.
The function sum
has been altered, so that if you apply it to an array of strings, it joins them using the empty string as the separator:
Dice> sum( tc["hello", "world"])
sum(tc(["hello", "world"])) → HelloWorld
Dice> sum ["123", "543"]
sum(["123", "543"]) → 123543
And sum
applied to a single string returns the string itself:
Dice> sum( "hello" )
sum("hello") → "hello"
To coerce strings to numbers, use the new toNumber
function:
Dice> toNumber ["123", "543"]
toNumber(["123", "543"]) → [123, 543]
Dice> ["123", "543"] |> toNumber |> sum
sum(toNumber(["123", "543"])) → 666
select
operator has been renamed pick
. This was done because, Palamedes now supports SQL expressions that operate on matrices. See below for more info.select Name, Age from Person where Age > 39 order by Age desc
. There is support for natural joins and group-by clauses. See SQL for complete details.high
/low
and take
/drop
has changed (again), this time to make them work like other pipe expressions. The syntax is expression TAKE number
take
and drop
have new short forms ↑
and ↓
respectively.high
and low
have been expanded to work on JSON objects in addition to arrays. Given a JSON object, they return a new object with the highest/lowest number keys. Similarly, take
/drop
have been expanded to work on JSON objects---they will take/drop the given number of keys at random.gets
/set
.ceil
and floor
functions.after
operator a pipe so you can write Person group by Age after 34 after 1 after 0
without parens.The syntax for the set
statement
logger ↜ true
logger <: true
logger set true
has been supplemented with some new forms which may read better:
set logger true
set logger=true
The set
statement has two new forms: +=
for adding to a variable and *=
for multiplying a variable. Example:
set myvar = 5
set myvar += 5 NB. myvar is now 10
set myvar *= 5 NB. myvar is now 50
We may add more assignment operators such as -=
, /=
, %=
, &=
, |=
, ^=
, ↑=
.
The gets
statement
yourcar ← myvar + 1
yourcar <- myvar * 2
yourvar gets myvar ^ 3
has now been supplemented with one additional form which may read better:
let yourvar = myvar^3
Remember: set
evaluates its RHS and stores the value, whereas gets
does no evaluation and stores the expression. Call the variable itself to evaluate its stored value:
yourvar
or use the '
or lazy
operator to see the stored expression:
'yourvar
lazy yourvar
The boolean value true
now has aliases yes
and on
. The following 5 expressions all do the same thing:
set logger = true NB. TRUE and True also work
set logger = yes
set logger = on
set logger = ⊤ NB. Most concise version
set logger = otherwise NB. Verbose but useful in an if/lookup statement, e.g.,
let anniversary = if [year=1 ⇒ "Cotton", year=2 ⇒ "Paper", …
year=3 ⇒ "Leather", year=4 ⇒ "Fruit and flowers", …
otherwise ⇒ error "divorce"]
False may be written ⊥
, no
or off
.
These statements have changed once again. BEWARE: The forms TAKE number, array
and TAKE number from array
are no longer supported! The only supported form is expression TAKE number
. There is an short form: expression ↑ number
. Examples:
/* Pick a number 1 to 10 */
shuffle [1..10] take 1
/* Shuffle and deal 5 cards */
([2..10] ∪ ["J", "Q", "K", "A"]) × ["♠", "♡", "♢", "♣"] |> shuffle ↑ 5
Drop works in a similar fashion. It has the synonym ↓
.
High and Low now also work with JSON objects:
{"1000": "Biggest", "2": "Small", "0": "Smallest", "1": "Smaller", "100": "Bigger", "10": "Big"} high 3
{"1000": "Biggest", "2": "Small", "0": "Smallest", "1": "Smaller", "100": "Bigger", "10": "Big"} low 3
Take and drop also work with JSON. If you wanted to take random sub-objects of size 3, you could do this:
{"1000": "Biggest", "2": "Small", "0": "Smallest", "1": "Smaller", "100": "Bigger", "10": "Big"} take 3
Palamedes now supports the standard math notation ⌈1.5⌉
and ⌊3/2⌋
for the ceiling and floor functions. These
⌈ 1.5 ⌉
⌊ 1/2 + 1 ⌋
are equivalent to
ceil(1.5)
floor(0.5 + 1)
JSON op JSON
form (in the r_op2
internal function) so that it produces a new JSON object whose keys are the union of the keys of its inputs. (In prior versions, the result was a JSON object whose keys were the intersection of the keys of its inputs.) If no value exists, then zero is assumed. This is very useful for calculating weighted sums of probability distributions. See below for examples.all
and some
operators so that they work with JSON objects as inputs. They test whether all/some values in the object are true.Command | Results |
---|---|
/* Base value of jewelry in D&D Vol 2 Monsters and Treasure, p. 40 */ /* Define it as a weighted sum */ JewelryProb ← 20%*prob(100*3d6) + 60%*prob(1000*d6) + 20%*prob(1000*d10) /* Apply `select` to the weighted sum to make a random jewelry generator */ Jewelry ← select JewelryProb /* Generate one piece of jewelry */ Jewelry /* Generate array of ten pieces of jewelry */ 10#Jewelry /* Generate ten pieces of jewelry displayed as a list with commas and "gp" */ 10#Jewelry + " gp"//unquote//commify//listify /* Applying `prob` to Jewelry same as JewelryProb */ all(JewelryProb = prob(Jewelry)) /* Show the probability table as rationals */ Jewelry//prob//rat//table /* Verify the probabilities sum to unity */ Jewelry//prob//sum /* Show a table of descriptive statistics */ Jewelry//stats//round//commify//table |
JewelryProb ← 0.2 * prob(100 * 3d6) + 0.6 * prob(1000 * d6) + 0.2 * prob(1000 * d10) Jewelry ← select JewelryProb Jewelry → 1000 10 # Jewelry → [3000, 2000, 1000, 9000, 2000, 400, 3000, 2000, 800, 3000] listify(commify(unquote(10 # Jewelry + " gp"))) → 6,000 gp 6,000 gp 3,000 gp 1,000 gp 6,000 gp 2,000 gp 2,000 gp 9,000 gp 5,000 gp 1,000 gp ∀ (JewelryProb = prob(Jewelry)) → true table(rat(prob(Jewelry))) → key value 300 1/1080 400 1/360 500 1/180 600 1/108 700 1/72 800 7/360 900 5/216 1000 29/200 1100 1/40 1200 5/216 1300 7/360 1400 1/72 1500 1/108 1600 1/180 1700 1/360 1800 1/1080 2000 3/25 3000 3/25 4000 3/25 5000 3/25 6000 3/25 7000 1/50 8000 1/50 9000 1/50 10000 1/50 sum(prob(Jewelry)) → 1 table(commify(round(stats(Jewelry)))) → key value count 25 kurt -0.03 max 10,000 mean 3,410 median 3,000 min 300 mode 1,000 sd 2,325.92 skew 0.79 var 5,409,900 |
Fixed bugs in the internal functions r_op1
and r_op2
used to thread operators over structured arguments. In cases where one argument was the empty array []
, an exception might have been thrown or a wrong result returned. Here are examples of behavior before and after this change:
Before | After |
---|---|
[] + 1 → 1 fix([]) → exception {"key": []}//round → exception |
[] + 1 → [] fix([]) → [] {"key": []}//round → {"key": []} |
mode
where the argument is an array. For example, mode [1..5]
was erroneously producing the result [1,2,3,4]
instead of []
.r_op1
and r_op2
used to thread operators over structured arguments. In cases where one argument was a JSON object, these functions weren't recursing when the key-value pairs themselves had structured values. For example, rat(stats(3d6))
wasn't applying rat
to the value [10,11]
of the key "mode".skew
function which computes the skewness. Unfortunately, several types of skewness are defined, and the terminology is confusing. For an input distribution, the formula we use is γ₁ = μ₃/(μ₂)^(3/2)
. For an input array, we compute the estimator g₁ = k₃/(k₂)^(3/2)
. For more info, please see the article on Skewness at Wolfram MathWorld.kurt
function to compute the kurtosis "excess". For an input distribution, the formula is γ₂ = μ₄/μ₂² - 3
. For an input array, we compute the estimator g₂ = k₄/k₂² - 3
. For more info, please see the article on Kurtosis at Wolfram MathWorld.mode
and argmax
where an error occurs if there is no mode/all values are modes.take
and drop
were echoed in reverse order.[]
eval
inside JSON objects such as mage gets {"name": "Gandalf", "abilities": eval(6#3d6)}
xerxes2 gets "stuff"
. Beware: x2
is still an exploding d2 dice roll, not a valid name for a variable!save
and load
commands.stringify
which converts array into a comma-separated string. No longer calls JSON.stringify internally.vars
so that it is a variable containing the expression table(ls())
function. See below for info about the new ls()
function.take
and drop
. Beware: Old form TAKE array, n
no longer works. There are now two forms: array TAKE n
or TAKE n, array
which may be written TAKE n FROM array
. More complicated forms of array are now permitted without parens. See below for examples.seed()
function so that automatically generated seeds are simply the number of seconds since the beginning of the epoch. That makes it easier to save/load them.tableform()
and matrixform()
functions to work with save
and load
commands.concat
operator to work with two JSON objects. Overlapping properties in the second argument overwrite the first.\n
is replaced with a newline character.without
and alias s̄
(abbreviation for sine in Latin) operator for sampling without replacement from a universe with unequal probabilities. Operand is an array or JSON object in the form value1, prob1, ..., valueN, probN
. See below for examples.#
: c̄
(abbreviation for cum in Latin) operator for sampling with replacementtrue
: otherwise
so that you can write expressions like lookup [p <= 50%, "this", p <= 100%, "that", otherwise, error "bug!"]
like
infix operator (synonym =~
, antonym !~
) to compare strings to regular expressions. Added like2()
function for use in outer()
function. See below for examples.ls()
function. Returns JSON object which lists all variables. To show in tableform do one of the following: table(ls())
, ls() |> table
, or ls()//table
forget()
function to clear a variable.reset()
function to clear all variables and reset them to the system defaults.rename(a,b)
function to rename variable a to b.|
. There are two main ways to use it:'tril'i
do this: domain(ls()) like 'tril'i | domain(ls())
'tril'i
and their values in tableform do this domain(ls()) like 'tril'i | domain(ls()) | ls() |> table
length()
function to count length of string. length()
threads over arrays/matrices/tensors of strings. Note that count
will not work on strings.error()
function. Takes a string argument. It stops execution and throws and exception.argmin()
and argmax()
functions for arrays and objects. These return array of indices corresponding to min/max values.listify()
function. Converts array into a newline-separated string.Chisqcdf(x, df)
, tcdf(x, df)
, Gammacdf(x, alpha, beta)
, Betacdf(x, alpha, beta)
, Expocdf(x, lambda)
Chisqtest(M)
function which takes a contingency matrix and returns the chi-square statistic, degrees of freedom, and p-value.cov2(x1,x2)
function to find sample covariance between two arrays x1 and x2. The function cov(x1, x2, ..., xN)
will find the NxN sample covariance matrix.corr2(x1,x2)
function to find sample correlation between two arrays x1 and x2. The function corr(x1, x2, ..., xN)
will find the NxN sample correlation matrix.slope(x1,x2)
, interecept(x1,x2)
functions to find slope and intercept of simple linear regression between two arrays x1 (independent) and x2 (dependent). linreg(x1,x2)
returns the array containing the intercept and slope.se
function to finf the standard error of an array of values.skew
function. Applied to an array it returns the sample skew. Applied to a distribution it finds the poulation skew. This information is returned part of the stats
function.echo off
to the UI. Turns off command echoing. Turn back on with echo on
. Do not use on compund line with statements separated by semicolons.quiet on
to the UI. Turns off output. Turn back on with quiet off
. Do not use on compund line with statements separated by semicolons.Input | Output |
---|---|
1+2+3+4 1+2+3+4+5 echo off 1+2+3+4 1+2+3+4+5 quiet on 1+2+3+4 1+2+3+4+5 quiet off last echo on last |
1 + 2 + 3 + 4 → 10 1 + 2 + 3 + 4 + 5 → 15 10 15 15 last → 15 |
The syntax for take
and drop
have changed. They now have 3 alternate forms. Here is an example that uses take
in conjunction with shuffle
to sample from a distribution with equal probabilities without replacement. All three forms of take
are shown:
Input | Output |
---|---|
echo off ranks ← [2..10] ∪ ["J", "Q", "K", "A"] suits ← ["♠", "♡", "♢", "♣"] cards ← ranks × suits shuffle cards take 5 take 5 from shuffle cards take 5, shuffle cards |
ranks ← [2 .. 10] ∪ ["J", "Q", "K", "A"] suits ← ["♠", "♡", "♢", "♣"] cards ← ranks × suits [[ "J", "♢" ], ... [ 2, "♢" ], ... [ "Q", "♠" ], ... [ "Q", "♢" ], ... [ "A", "♠" ]] [[ "J", "♢" ], ... [ 8, "♡" ], ... [ 8, "♣" ], ... [ 4, "♣" ], ... [ 9, "♢" ]] [[ 5, "♢" ], ... [ 8, "♠" ], ... [ 10, "♡" ], ... [ 9, "♠" ], ... [ 5, "♣" ]] |
without
is used to sample from a distribution with unequal probabilities without replacement. Here are some examples:
Input | Output |
---|---|
echo off 0 without [1, 25%, 2, 50%, 3, 25%] 1 without [1, 25%, 2, 50%, 3, 25%] 2 without [1, 25%, 2, 50%, 3, 25%] 3 without [1, 25%, 2, 50%, 3, 25%] 4 without [1, 25%, 2, 50%, 3, 25%] |
[] [2] [1, 3] [3, 1, 2] [2, 1, 3] |
Input | Output |
---|---|
echo off "The list of " + ["A","B"] "The list of " + stringify(unquote(["A","B"])) listify(["A","B"]) listify(unquote(["A","B"])) |
["The list of A", "The list of B"] "The list of A, B" "A" "B" A B |
Input | Output |
---|---|
echo off "Hello" like 'hel'i "Hello" like 'heck'i "Hello" like 'hel' "Hello" =~ 'Hel' "Hello" !~ '^Hel' "Hello" like ['^Hel', 'lo', '^lo'] ["hello","help", "hollow"] LIKE '^Hel' ["hello","help", "hollow"] !~ '^Hel' ["hello","help", "hollow"] =~ '^Hel'i ["hello","help", "hollow"] !~ '^Hel'i outer(like2,["Hello","Help", "hollow"],['^Hel', 'lo', '^lo']) |
true false false true false [true, true, false] [false, false, false] [true, true, true] [true, true, false] [false, false, true] [[ true, true, false ], ... [ true, false, false ], ... [ false, true, false ]] |
Input | Output |
---|---|
echo off domain(ls()) like 'tril'i | domain(ls()) domain(ls()) like 'tril'i | domain(ls()) | ls() domain(ls()) like 'tril'i | domain(ls()) | ls() |> table |
[trillion, trillionth] {"trillion":"1000000000000","trillionth":"1e-12"} key value trillion 1000000000000 trillionth 1e-12 |
Input | Output |
---|---|
echo on $x gets [1,2,3,4,5] $y gets [1,2,1.3,3.75,2.25] var($x) var($y) cov2($x,$y) cov($x,$y) corr2($x,$y) corr($x,$y) |
$x ← [1, 2, 3, 4, 5] $y ← [1, 2, 1.3, 3.75, 2.25] var($x) → 2.5 var($y) → 1.14925 cov2($x,$y) → 1.0625 cov($x,$y) → [[ 2.5, 1.0625 ], ... [ 1.0625, 1.14925 ]] corr2($x,$y) → 0.6268327489789575 corr($x,$y) → [[ 1, 0.6268327489789575 ], ... [ 0.6268327489789575, 1 ]] |
The Linear regression example may be redone more simply as:
$x gets [1,2,3,4,5] /* independent data: array */ $y gets [1,2,1.3,3.75,2.25] /* dependent data: array */ linreg($x,$y)
This will output an array whose first element is the intercept and whose second is the slope.
Refer to the example at Chi-Square Test for Independence. This can be done quite simply by passing the contingency table to the Chisqtest
function like so:
Input | Output |
---|---|
Chisqtest [[200, 150, 50],[250, 300, 50]]//table |
key value chisq 16.203703703703702 df 2 p-value 0.0003029775487248809 |
stringify
function mostly for using arrays in calls to the print
functionDefinition. A Bayesian network is a probabilistic model that represents a set of random variables and their conditional dependencies. They have extensive applications in areas such as
Pr(E|G)
and innocent Pr(E|~G)
. But we need to infer the probability he's guilty given the evidence Pr(G|E)
.Pr(test|~disease)
and Pr(~test|disease )
. But we need to infer the probability the patient has the disease given a positive test result Pr(disease|test)
.History. Harvard Professor Joel Blitzstein says, "The soul of statistics is conditioning." The method for "inverting" a conditional probability to determine the probability of an unobserved variable is Bayes Law, developed by the Reverend Thomas Bayes in his Essay towards solving a Problem in the Doctrine of Chances in 1763.
More than a millenium prior, the rhethoritician Gorgias (483—375 B.C.E.) presented the archetype of a legal argument based on the conditional probabilities connecting evidence to guilt. This speech is entitled The Defense of Palamedes. I have an essay on it: On Gorgias' Defense of Palamedes. The centrality of Palamedes in this seminal work is yet another important reason this software bears his name!
Fallacies. One motivation for automating Bayesian inference is that many people find it difficult to reason about conditional probabilities, and there are many common fallacies. Two prominent fallacies are
Pr(A|B) ≈ Pr(B|A)
. Example: "Terrorists tend to have an engineering background; so, engineers have a tendency towards terrorism."In the paper Probabilistic reasoning in clinical medicine: Problems and opportunities by David M. Eddy (1982), a hundred physicians were asked about the probability of a having a disease based on a positive test result; 95% of these doctors committed the inverse fallacy in their answer. The Sally Clark case is a recent example of an expert witness committing the prosecutor's fallacy.
Random variables. Palamedes allows a user to input relationships among a set of random variables. It then can answer probabilistic queries about them.
Right now, Palamedes' Bayes net model only supports binary random variables, e.g., guilty
and ~guilty
(meaning "not guilty" or "innocent"). In a future version, random variables will be able to take arbitrary values, e.g., Weather=snow
, Weather=rain
, or Weather=sunny
.
Probability statements. Probabilities are input or queried into the system via statements of the form Pr(Conjunction1 of random variables | Conjunction2 of random variables)
. The bar |
(meaning "given") and the second conjunction are optional. Here are a few examples:
Command | Notes |
---|---|
Pr(guilty) gets value0 | Example of inputting an unconditional probability |
Pr(evidence1|~guilty) gets value1 | Example of inputting a conditional probability |
Pr(guilty|evidence1,evidence2,evidence3) | Example of querying a conditional probability involving a conjunction of random variables |
Conjunctions between random variables may be input using commas; the word "and" or the symbol &&
; or the word "intersect" or the symbol ∩
.
Implementation. Exact and approximate methods exist for inferring unobserved variables in Bayesian networks. Common exact methods include exponential enumeration and variable elimination. Approximate methods include importance sampling, belief propagation, and stochastic MCMC simulation.
Palamedes uses an exact method that takes an AI/logic programming approach. It uses pattern matching to match user inputs against mathematical rules for making deductions until the current goal is achieved. The rules are simply what you would find in any probability and statistics textbook about conditional probabilities:
Pr(A∩B) = PR(A|B)*Pr(B)
Pr(~A) = 1 - Pr(A)
Pr(A|B) = Pr(B|A)*Pr(A)/Pr(B)
Pr(A|B) = Pr(A|B∩C)*Pr(C) + Pr(A|B∩~C)*Pr(~C)
To see a table of all of the deductions made by the system, use the command bayes()
. Note: These deductions are memoized. If you need to clear them because you've added new info or changed values, simply pass an argument to the bayes function, e.g., bayes(0)
.
This implementation is inspired by Ivan Bratko's interpreter for belief networks, figure 15.11, in the book Prolog Programming for Artificial Intelligence. It has comparable performance to variable elimination.
Limitations. The main limitation of any exact method is that it has computational complexity O(k*2^k)
. So approximate methods are needed to deal with large belief networks. In a future version, we will implement an MCMC method for dealing with large nets.
Example 1.
The following example of legal reasoning in a court case example comes from the paper Teaching an Application of Bayes' Rule for Legal Decision-Making: Measuring the Strength of Evidence, pp. 10-12.
Command | Results |
---|---|
/* 3.1.1 Calculation of Posterior Probability after Evidence 1 (E₁), p. 10 */ Pr( G) ← 0.5 Pr(~G) NB. Should be 0.5 Pr(E₁|G) ← 1 Pr(E₁|~G) ← 0.45 Pr(G|E₁) NB. Should be 0.69 /* 3.2.1 Calculation of Posterior Probability after the Second Piece of Evidence (E₂), p. 11 */ Pr(~G|E₁) NB. Should be 0.31 Pr(E₂|G and E₁) ← 1 Pr(E₂|~G and E₁) ← 0.21 Pr(G|E₁ and E₂) NB. Should be 0.914 /* 3.3.1 Calculation of Posterior Probability after Evidence 3 (E₃), p. 12 */ Pr(~G|E₁ and E₂) NB. Should be 0.086 Pr(E₃|G and E₁ and E₂) ← 1 Pr(E₃|~G and E₁ and E₂) ← 0.017 Pr(G|E₁ and E₂ and E₃) NB. Should be 0.998 | Pr(G) ← 0.5 Pr(~G) → 0.5 Pr(E₁ | G) ← 1 Pr(E₁ | ~G) ← 0.45 Pr(G | E₁) → 0.6896551724137931 Pr(~G | E₁) → 0.31034482758620685 Pr(E₂ | E₁ ∩ G) ← 1 Pr(E₂ | E₁ ∩ ~G) ← 0.21 Pr(G | E₁ ∩ E₂) → 0.9136592051164916 Pr(~G | E₁ ∩ E₂) → 0.08634079488350843 Pr(E₃ | E₁ ∩ E₂ ∩ G) ← 1 Pr(E₃ | E₁ ∩ E₂ ∩ ~G) ← 0.017 Pr(G | E₁ ∩ E₂ ∩ E₃) → 0.998396076702777 |
Example 2.
The following example comes from Figure 14.2 on page 512 in the book Artificial Intelligence: A Modern Approach by Stuart Russell and Peter Norvig.
Command | Results |
---|---|
/* Input probability tables */ Pr(JohnCalls| Alarm) gets 0.90 Pr(JohnCalls|~Alarm) gets 0.05 Pr(MaryCalls| Alarm) gets 0.70 Pr(MaryCalls|~Alarm) gets 0.01 Pr(Alarm| Burglary and Earthquake) gets 0.95 Pr(Alarm| Burglary and ~Earthquake) gets 0.94 Pr(Alarm|~Burglary and Earthquake) gets 0.29 Pr(Alarm|~Burglary and ~Earthquake) gets 0.001 Pr(Burglary) gets 0.001 Pr(Earthquake) gets 0.002 /* Query the system */ Pr(JohnCalls, MaryCalls, Alarm, ~Burglary, ~Earthquake) NB. Should be 0.000628 Pr(Burglary | JohnCalls, MaryCalls) NB. Should be 0.28 | Pr(JohnCalls | Alarm) ← 0.90 Pr(JohnCalls | ~Alarm) ← 0.05 Pr(MaryCalls | Alarm) ← 0.70 Pr(MaryCalls | ~Alarm) ← 0.01 Pr(Alarm | Burglary ∩ Earthquake) ← 0.95 Pr(Alarm | Burglary ∩ ~Earthquake) ← 0.94 Pr(Alarm | Earthquake ∩ ~Burglary) ← 0.29 Pr(Alarm | ~Burglary ∩ ~Earthquake) ← 0.001 Pr(Burglary) ← 0.001 Pr(Earthquake) ← 0.002 Pr(Alarm ∩ JohnCalls ∩ MaryCalls ∩ ~Burglary ∩ ~Earthquake) → 0.0006281112599999999 Pr(Burglary | JohnCalls ∩ MaryCalls) → 0.28417183536439294 |
In order to see the deductions made by the system in Example 2, use the bayes()
command. You'll see the following table:
bayes() → key value Alarm ∩ JohnCalls ∩ MaryCalls ∩ ~Burglary ∩ ~Earthquake| Pr(JohnCalls) * Pr(Alarm ∩ MaryCalls ∩ ~Burglary ∩ ~Earthquake | JohnCalls) Alarm ∩ MaryCalls ∩ ~Burglary ∩ ~Earthquake|JohnCalls Pr(MaryCalls | JohnCalls) * Pr(Alarm ∩ ~Burglary ∩ ~Earthquake | JohnCalls ∩ MaryCalls) Alarm ∩ ~Burglary ∩ ~Earthquake|JohnCalls ∩ MaryCalls Pr(Alarm | JohnCalls ∩ MaryCalls) * Pr(~Burglary ∩ ~Earthquake | Alarm ∩ JohnCalls ∩ MaryCalls) Alarm| Pr(Alarm | Burglary ∩ Earthquake) * Pr(Burglary ∩ Earthquake) + Pr(Alarm | Burglary ∩ ~Earthquake) * Pr(Burglary ∩ ~Earthquake) + Pr(Alarm | Earthquake ∩ ~Burglary) * Pr(Earthquake ∩ ~Burglary) + Pr(Alarm | ~Burglary ∩ ~Earthquake) * Pr(~Burglary ∩ ~Earthquake) Alarm|Alarm ∩ Burglary 1 Alarm|Alarm ∩ JohnCalls 1 Alarm|Alarm ∩ JohnCalls ∩ ~Burglary 1 Alarm|Alarm ∩ JohnCalls ∩ ~Burglary ∩ ~Earthquake 1 Alarm|Alarm ∩ ~Burglary 1 Alarm|Alarm ∩ ~Burglary ∩ ~Earthquake 1 Alarm|Burglary Pr(Alarm | Burglary ∩ Earthquake) * Pr(Burglary ∩ Earthquake | Burglary) + Pr(Alarm | Burglary ∩ ~Earthquake) * Pr(Burglary ∩ ~Earthquake | Burglary) + Pr(Alarm | Earthquake ∩ ~Burglary) * Pr(Earthquake ∩ ~Burglary | Burglary) + Pr(Alarm | ~Burglary ∩ ~Earthquake) * Pr(~Burglary ∩ ~Earthquake | Burglary) Alarm|Burglary ∩ Earthquake 0.95 Alarm|Burglary ∩ JohnCalls Pr(Alarm | Burglary) * Pr(JohnCalls | Alarm ∩ Burglary) / Pr(JohnCalls | Burglary) Alarm|Burglary ∩ ~Earthquake 0.94 Alarm|Earthquake ∩ ~Burglary 0.29 Alarm|JohnCalls Pr(Alarm) * Pr(JohnCalls | Alarm) / Pr(JohnCalls) Alarm|JohnCalls ∩ MaryCalls Pr(Alarm | JohnCalls) * Pr(MaryCalls | Alarm ∩ JohnCalls) / Pr(MaryCalls | JohnCalls) Alarm|~Burglary Pr(Alarm | Burglary ∩ Earthquake) * Pr(Burglary ∩ Earthquake | ~Burglary) + Pr(Alarm | Burglary ∩ ~Earthquake) * Pr(Burglary ∩ ~Earthquake | ~Burglary) + Pr(Alarm | Earthquake ∩ ~Burglary) * Pr(Earthquake ∩ ~Burglary | ~Burglary) + Pr(Alarm | ~Burglary ∩ ~Earthquake) * Pr(~Burglary ∩ ~Earthquake | ~Burglary) Alarm|~Burglary ∩ ~Earthquake 0.001 Burglary ∩ Earthquake| Pr(Burglary) * Pr(Earthquake | Burglary) Burglary ∩ Earthquake|Burglary Pr(Burglary | Burglary) * Pr(Earthquake | Burglary) Burglary ∩ Earthquake|~Burglary Pr(Burglary | ~Burglary) * Pr(Earthquake | Burglary ∩ ~Burglary) Burglary ∩ ~Earthquake| Pr(Burglary) * Pr(~Earthquake | Burglary) Burglary ∩ ~Earthquake|Burglary Pr(Burglary | Burglary) * Pr(~Earthquake | Burglary) Burglary ∩ ~Earthquake|~Burglary Pr(Burglary | ~Burglary) * Pr(~Earthquake | Burglary ∩ ~Burglary) Burglary| 0.001 Burglary|Burglary 1 Burglary|JohnCalls Pr(Burglary) * Pr(JohnCalls | Burglary) / Pr(JohnCalls) Burglary|JohnCalls ∩ MaryCalls Pr(Burglary | JohnCalls) * Pr(MaryCalls | Burglary ∩ JohnCalls) / Pr(MaryCalls | JohnCalls) Burglary|~Burglary 0 Earthquake ∩ ~Burglary| Pr(~Burglary) * Pr(Earthquake | ~Burglary) Earthquake ∩ ~Burglary|Burglary Pr(~Burglary | Burglary) * Pr(Earthquake | Burglary ∩ ~Burglary) Earthquake ∩ ~Burglary|~Burglary Pr(~Burglary | ~Burglary) * Pr(Earthquake | ~Burglary) Earthquake| 0.002 Earthquake|Burglary Pr(Earthquake) Earthquake|Burglary ∩ ~Burglary Pr(Earthquake) Earthquake|~Burglary 1 - Pr(~Earthquake | ~Burglary) JohnCalls| Pr(JohnCalls | Alarm) * Pr(Alarm) + Pr(JohnCalls | ~Alarm) * Pr(~Alarm) JohnCalls|Alarm 0.90 JohnCalls|Alarm ∩ Burglary Pr(JohnCalls | Alarm) * Pr(Alarm | Alarm ∩ Burglary) + Pr(JohnCalls | ~Alarm) * Pr(~Alarm | Alarm ∩ Burglary) JohnCalls|Alarm ∩ ~Burglary Pr(JohnCalls | Alarm) * Pr(Alarm | Alarm ∩ ~Burglary) + Pr(JohnCalls | ~Alarm) * Pr(~Alarm | Alarm ∩ ~Burglary) JohnCalls|Alarm ∩ ~Burglary ∩ ~Earthquake Pr(JohnCalls | Alarm) * Pr(Alarm | Alarm ∩ ~Burglary ∩ ~Earthquake) + Pr(JohnCalls | ~Alarm) * Pr(~Alarm | Alarm ∩ ~Burglary ∩ ~Earthquake) JohnCalls|Burglary Pr(JohnCalls | Alarm) * Pr(Alarm | Burglary) + Pr(JohnCalls | ~Alarm) * Pr(~Alarm | Burglary) JohnCalls|~Alarm 0.05 MaryCalls|Alarm 0.70 MaryCalls|Alarm ∩ JohnCalls Pr(MaryCalls | Alarm) * Pr(Alarm | Alarm ∩ JohnCalls) + Pr(MaryCalls | ~Alarm) * Pr(~Alarm | Alarm ∩ JohnCalls) MaryCalls|Alarm ∩ JohnCalls ∩ ~Burglary Pr(MaryCalls | Alarm) * Pr(Alarm | Alarm ∩ JohnCalls ∩ ~Burglary) + Pr(MaryCalls | ~Alarm) * Pr(~Alarm | Alarm ∩ JohnCalls ∩ ~Burglary) MaryCalls|Alarm ∩ JohnCalls ∩ ~Burglary ∩ ~Earthquake Pr(MaryCalls | Alarm) * Pr(Alarm | Alarm ∩ JohnCalls ∩ ~Burglary ∩ ~Earthquake) + Pr(MaryCalls | ~Alarm) * Pr(~Alarm | Alarm ∩ JohnCalls ∩ ~Burglary ∩ ~Earthquake) MaryCalls|Burglary ∩ JohnCalls Pr(MaryCalls | Alarm) * Pr(Alarm | Burglary ∩ JohnCalls) + Pr(MaryCalls | ~Alarm) * Pr(~Alarm | Burglary ∩ JohnCalls) MaryCalls|JohnCalls Pr(MaryCalls | Alarm) * Pr(Alarm | JohnCalls) + Pr(MaryCalls | ~Alarm) * Pr(~Alarm | JohnCalls) MaryCalls|~Alarm 0.01 ~Alarm| 1 - Pr(Alarm) ~Alarm|Alarm ∩ Burglary 0 ~Alarm|Alarm ∩ JohnCalls 0 ~Alarm|Alarm ∩ JohnCalls ∩ ~Burglary 0 ~Alarm|Alarm ∩ JohnCalls ∩ ~Burglary ∩ ~Earthquake 0 ~Alarm|Alarm ∩ ~Burglary 0 ~Alarm|Alarm ∩ ~Burglary ∩ ~Earthquake 0 ~Alarm|Burglary 1 - Pr(Alarm | Burglary) ~Alarm|Burglary ∩ JohnCalls 1 - Pr(Alarm | Burglary ∩ JohnCalls) ~Alarm|JohnCalls 1 - Pr(Alarm | JohnCalls) ~Burglary ∩ ~Earthquake| Pr(~Burglary) * Pr(~Earthquake | ~Burglary) ~Burglary ∩ ~Earthquake|Alarm ∩ JohnCalls ∩ MaryCalls Pr(~Burglary | Alarm ∩ JohnCalls ∩ MaryCalls) * Pr(~Earthquake | Alarm ∩ JohnCalls ∩ MaryCalls ∩ ~Burglary) ~Burglary ∩ ~Earthquake|Burglary Pr(~Burglary | Burglary) * Pr(~Earthquake | Burglary ∩ ~Burglary) ~Burglary ∩ ~Earthquake|~Burglary Pr(~Burglary | ~Burglary) * Pr(~Earthquake | ~Burglary) ~Burglary| 1 - Pr(Burglary) ~Burglary|Alarm Pr(~Burglary) * Pr(Alarm | ~Burglary) / Pr(Alarm) ~Burglary|Alarm ∩ JohnCalls Pr(~Burglary | Alarm) * Pr(JohnCalls | Alarm ∩ ~Burglary) / Pr(JohnCalls | Alarm) ~Burglary|Alarm ∩ JohnCalls ∩ MaryCalls Pr(~Burglary | Alarm ∩ JohnCalls) * Pr(MaryCalls | Alarm ∩ JohnCalls ∩ ~Burglary) / Pr(MaryCalls | Alarm ∩ JohnCalls) ~Burglary|Burglary 0 ~Burglary|~Burglary 1 ~Earthquake|Alarm ∩ JohnCalls ∩ MaryCalls ∩ ~Burglary Pr(~Earthquake | Alarm ∩ JohnCalls ∩ ~Burglary) * Pr(MaryCalls | Alarm ∩ JohnCalls ∩ ~Burglary ∩ ~Earthquake) / Pr(MaryCalls | Alarm ∩ JohnCalls ∩ ~Burglary) ~Earthquake|Alarm ∩ JohnCalls ∩ ~Burglary Pr(~Earthquake | Alarm ∩ ~Burglary) * Pr(JohnCalls | Alarm ∩ ~Burglary ∩ ~Earthquake) / Pr(JohnCalls | Alarm ∩ ~Burglary) ~Earthquake|Alarm ∩ ~Burglary Pr(~Earthquake | ~Burglary) * Pr(Alarm | ~Burglary ∩ ~Earthquake) / Pr(Alarm | ~Burglary) ~Earthquake|Burglary 1 - Pr(Earthquake | Burglary) ~Earthquake|Burglary ∩ ~Burglary 1 - Pr(Earthquake | Burglary ∩ ~Burglary) ~Earthquake|~Burglary 1 - Pr(Earthquake)
To clear these deductions, use the command bayes(0)
.
Added the variables colwidth
(default 35) and colgap
(default 5) which govern the output of the first column in a table. Example: To see the first column of the table displayed by the bayes()
command above, I first had to do colwidth gets 60
.
There is a function called js
that allow you to execute arbitrary JavaScript. It is currently commented-out on line 62 of AST.js because "eval can be harmful." I find it useful for debugging and developing, and you may find it useful too, so grab the source code, uncomment and rebuild using the notes in the README.
The logger
variable introduced in the last release now defaults to false
. The logger is a way to display info and warnings to the user. It's useful for development and debugging but may not make it to a beta release.
Started coding support for conditional probabilities and Bayesian reasoning. Still at a very early stage. Right now, the grammar accepts statements like Pr(A|B)
where both A and B are conjunctions/interesections of events or their complements. Given some input, the system will attempt to infer output based on Bayes' Rule, the Complement Rule for conditional probabilities, and the Law of total probability.
BEWARE: The system doesn't yet know the Chain Rule for conditional probabilities or rules such as Fienberg and Schervish (1986), or Lindley (1977). In other words: Only the simplest cases work! Here are a few examples:
Command | Results |
---|---|
/* Simple example: infer complement */ Pr(A) ← 0.45 Pr(~A) /* Bayes' Theorem and Cancer Screening */ /* Source: https://www.youtube.com/watch?v=j2tNxIaGpR4 */ Pr(cancer) gets 1% Pr(tests_positive|cancer) gets 90% Pr(tests_positive|~cancer) gets 10% NB. False positive Pr(cancer|tests_positive) NB. Should be 1/12 or 0.08333333333333333 /* Turn off logger */ logger gets false /* Source: http://stattrek.com/probability/bayes-theorem.aspx */ Pr( A ) gets 5/365 NB. It rains 5 days out of the year Pr( B | A ) gets 90% NB. When it rains, the weatherman predicts rain 90% of the time Pr( B | ~A ) gets 10% NB. When it does not rain, the weatherman predicts rain 10% of the time Pr( A | B ) NB. Probability it rains given forecast for rain. Should be 1/9 or 0.1111111111111111 /* Teaching an Application of Bayes’ Rule for Legal Decision-Making: Measuring the Strength of Evidence */ /* Source: http://www.amstat.org/publications/jse/v22n1/satake.pdf */ Pr(guilt) ← 50% NB. Prior probability of guilt Pr(~guilt) NB. Prior probability of innocence. Should be 0.5 Pr(evidence | guilt) ← 100% NB. Probability of evidence based on presumption of guilt Pr(evidence | ~guilt) ← 45% NB. Probability of evidence based on presumption of innocence Pr(guilt | evidence) NB. Posterior probability of guilt after seeing the evidence. Should be 0.69 Pr(~guilt | evidence) NB. Posterior probability of innocence after seeing the evidence. Should be 0.31 | Pr(A) ← 0.45 NB. Applied complement rule: Pr(~A) = 1 - Pr(A) Pr(~A) → 0.55 Pr(cancer) ← 0.01 Pr(tests_positive | cancer) ← 0.9 Pr(tests_positive | ~cancer) ← 0.1 NB. Trying to apply Bayes' rule to: Pr(cancer | tests_positive) NB. Checking inverse: Pr(tests_positive | cancer) NB. Checking inverse prior: Pr(cancer) NB. Checking prior: Pr(tests_positive) NB. Trying to apply LOTP to: Pr(tests_positive) NB. Found LOTP candidate Pab: Pr(tests_positive | cancer) NB. Checking Pb: Pr(cancer) NB. Checking PNb: Pr(~cancer) NB. Checking prior: Pr(tests_positive | ~cancer) NB. Applied complement rule: Pr(~cancer) = 1 - Pr(cancer) NB. Applied LOTP to candidate: Pr(tests_positive) = Pr(tests_positive | cancer) * Pr(cancer) + Pr(tests_positive | ~cancer) * Pr(~cancer) NB. Applied Bayes' rule: Pr(cancer | tests_positive) = Pr(tests_positive | cancer) * Pr(cancer) / Pr(tests_positive) NB. Trying to apply LOTP to: Pr(tests_positive) NB. Found LOTP candidate Pab: Pr(tests_positive | cancer) NB. Checking Pb: Pr(cancer) NB. Checking PNb: Pr(~cancer) NB. Checking prior: Pr(tests_positive | ~cancer) NB. Applied complement rule: Pr(~cancer) = 1 - Pr(cancer) NB. Applied LOTP to candidate: Pr(tests_positive) = Pr(tests_positive | cancer) * Pr(cancer) + Pr(tests_positive | ~cancer) * Pr(~cancer) NB. Applied complement rule: Pr(~cancer) = 1 - Pr(cancer) Pr(cancer | tests_positive) → 0.08333333333333333 logger ← false Pr(A) ← 0.0136986301369863 Pr(B | A) ← 0.9 Pr(B | ~A) ← 0.1 Pr(A | B) → 0.1111111111111111 Pr(guilt) ← 0.5 Pr(~guilt) → 0.5 Pr(evidence | guilt) ← 1 Pr(evidence | ~guilt) ← 0.45 Pr(guilt | evidence) → 0.6896551724137931 Pr(~guilt | evidence) → 0.3103448275862069 |
Note that the system tells you how it's making its inferences. You can turn this behavior off by setting the variable logger
to false
as shown above.
Pr
had to be renamed Pt
, since Pr
now starts a conditional probability.sum P
are 1. May be the new code which changed sum(comb(A₀,Ka) * 1/6^Ka * 5/6^(A₀-Ka))
to sum(prob(binom A₀, 1/6) ∘ Ka)
.print "XYZ"
no longer outputs print "XYZ" → "XYZ"
after printing XYZ.binom n, p
whereby the support was incorrectly taken to be [0 .. ∞]
instead of [0 .. n]
. The actual probabilities calculated over [0 .. n]
were correct, and the routine to generate binomial variates was correct. Example:l@ubuntu:~/work/Palamedes$ node cli.js Dice> binom 3, 1/6 |> prob |> table table(prob(binom 3, 0.16666666666666666)) → key value 0 0.5787037037037038 1 0.34722222222222227 2 0.06944444444444445 3 0.004629629629629629 Dice> binom 3, 1/6 |> prob |> sum sum(prob(binom 3, 0.16666666666666666)) → 1
/* M * N is a Hadamard/Schur/elementwise matrix product */
is now OK.NB.
which must be uppercase and include the trailing dot. Anything after this to the end-of-line is ignored. If you use mixed case, lowercase or forget the dot, then you'll either trigger a parser error or the juxtaposition operator:Good | Bad |
---|---|
Dice> 1+2+3+4 NB. This is 10 1 + 2 + 3 + 4 → 10 Dice> 1*2*3*4 NB. This is 24 1 * 2 * 3 * 4 → 24 | Dice> 1+2+3+4 nb. This is 10 1 + 2 + 3 + plus(plus(plus(plus(4,nb.),This),is),10) → 20 Dice> 1*2*3*4 nb. This is 24 1 * 2 * 3 * plus(plus(plus(plus(4,nb.),This),is),24) → 168 |
tableform()
and matrixform()
as well as the variables table_start
, table_end
, row_start
, row_end
, row_sep
, col_sep
and nl
to provide greater control how matrices are output. The function tableform()
outputs a matrix with verical bars (pipes) around each cell; matrixform()
restores the default matrix output; nl
is a newline character. Here are a few examples:Dice> matrixform() matrixform() → OK Dice> I(3) I(3) → [[ 1, 0, 0 ], ... [ 0, 1, 0 ], ... [ 0, 0, 1 ]] | Dice> row_sep set unquote(","+nl+" ") /* Get rid of othe dots after each row */ row_sep ↜ unquote("," + nl + " ") → , Dice> I(3) I(3) → [[ 1, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ]] |
Dice> tableform() tableform() → OK Dice> I(3) I(3) → | 1 | 0 | 0 | | 0 | 1 | 0 | | 0 | 0 | 1 | | Dice> col_sep set unquote("") /* Get rid of the pipes between columns */ col_sep ↜ unquote("") → Dice> I(3) I(3) → | 1 0 0 | | 0 1 0 | | 0 0 1 | |
Raising a matrix to the power of T
now performs matrix transpose. This is more cosmetically appealing alternative to the trans
function:
Dice> M gets [[1,5],[-2,3]] M ← [[1, 5], [-2, 3]] Dice> M M → [[ 1, 5 ], ... [ -2, 3 ]] Dice> M^T M ^ T → [[ 1, -2 ], ... [ 5, 3 ]]
The diag
function now takes an array and constructs a matrix with it on the diagonal, everything else zeroed out:
Dice> diag [5,7,2] diag([5, 7, 2]) → [[ 5, 0, 0 ], ... [ 0, 7, 0 ], ... [ 0, 0, 2 ]]
diag
still takes a matrix and returns a matrix with the same diagonal and everything else zeroed out.
TMTOWTDI: Another way to create a 3x3 identity matrix:
Dice> diag(3#1) diag(3 # 1) → [[ 1, 0, 0 ], ... [ 0, 1, 0 ], ... [ 0, 0, 1 ]]
This would be a good place to compile all the ways we have so far of creating a 3x3 identy matrix:
/* 1 */ I(3) /* Best way to do it!!! */ /* 2 */ diag(3#1) /* 3 */ matrix(lookup [r=c, 1, ⊤, 0],3,3) /* 4 */ resize([3, 3],[1] ⊎ (3 # 0)) /* 5 */ M^0 /* M any 3x3 matrix e.g., */ @ M ← [[1,2,3],[0,4,5],[1,0,6]] /* 6 */ M × M^-1 //rat /* M some invertible 3x3 matrix. Worst way to do it! */
Added car
, cdr
and cons
functions, a la Lisp.
Dice> cons(1,2) cons(1,2) → [1, 2] Dice> cons(1,[2,3]) cons(1,[2, 3]) → [1, 2, 3] Dice> car([1,2,3]) car([1, 2, 3]) → 1 Dice> cdr([1,2,3]) cdr([1, 2, 3]) → [2, 3]
Predefined the Dirac constant as if you had typed ℏ ← 6.62606957e-34 / (2 * π)
Added a juxtaposition operator, which operates on space-separated numerals, variables or arrays when no operator is given. It works on 2 inputs at a time, but automatically folds left over more than two inputs. The juxtaposition operator is useful when you just want to add a bunch of numbers together and don't want to type in a bunch of plus signs. The output is ugly, since Palamedes explains how its interpreting your input. But the input requires fewer keystrokes, and it is easy to ignore the output up to the final →
and just read the final result:
Dice> 9 32 73 53 17 26 99 71 1 3 22 58 59 5 85 11 99 37 24 2 plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(9,32),73),53),17),26),99),71),1),3),22),58),59),5),85),11),99),37),24),2) → 786
That's the same as either of these, which require more keystrokes:
Dice> 9 + 32 + 73 + 53 + 17 + 26 + 99 + 71 + 1 + 3 + 22 + 58 + 59 + 5 + 85 + 11 + 99 + 37 + 24 + 2 9 + 32 + 73 + 53 + 17 + 26 + 99 + 71 + 1 + 3 + 22 + 58 + 59 + 5 + 85 + 11 + 99 + 37 + 24 + 2 → 786 Dice> sum [9, 32, 73, 53, 17, 26, 99, 71, 1, 3, 22, 58, 59, 5, 85, 11, 99, 37, 24, 2] sum([9, 32, 73, 53, 17, 26, 99, 71, 1, 3, 22, 58, 59, 5, 85, 11, 99, 37, 24, 2]) → 786
By default the juxtaposition operator is set to plus
. Change it to any 2-argument-or-greater function name (e.g., plus, times, min, min2, max, max2, minus, div, mod, lcm2, pow, list, cons, and2, or2) sans quotes as shown in the following example:
Dice> 2 3 4 5 plus(plus(plus(2,3),4),5) → 14 Dice> 'juxtaposition juxtaposition → plus Dice> juxtaposition set unquote "pow" juxtaposition ↜ unquote("pow") → pow Dice> 2 3 4 5 pow(pow(pow(2,3),4),5) → 1152921504606847000 Dice> 2^3^4^5 /* same as this */ 2 ^ 3 ^ 4 ^ 5 → 1152921504606847000 Dice> juxtaposition set unquote "times" juxtaposition ↜ unquote("times") → times Dice> 2 3 4 5 times(times(times(2,3),4),5) → 120 Dice> 2 * 3 * 4 * 5 /* same as this or prod[2..5] or 5! */ 2 * 3 * 4 * 5 → 120 Dice> juxtaposition set unquote "max" juxtaposition ↜ unquote("max") → max Dice> 2 3 4 5 max(max(max(2,3),4),5) → 5 Dice> juxtaposition set unquote "min" juxtaposition ↜ unquote("min") → min Dice> 2 3 4 5 min(min(min(2,3),4),5) → 2 Dice> juxtaposition set unquote "list" juxtaposition ↜ unquote("list") → list Dice> 2 3 4 5 list(list(list(2,3),4),5) → [2, 3, 4, 5]
You can use variables or arrays with the juxtaposition operator too. Here we use the last
variable to continue a sum:
Dice> 1 2 3 4 plus(plus(plus(1,2),3),4) → 10 Dice> last 3 1 plus(plus(last,3),1) → 14
Beware: But you can't use juxtaposition with dice throws! At least not yet.
Dice> d4 d6 d8 d12 d20 /* ERROR! */
Beware: If the first variable is the same as a function name it applies the function instead! First we add 1 to N
. The variable N
is initially 0. But switching the order of N
and 1 applies the normal random function to 1, returning a variate with mean 1 and variance 1:
Dice> 1 N plus(1,N) → 1 Dice> N N → 0 Dice> N 1 N(1) → 1.1506646051606864
Beware of precedence rules---The juxtaposition operator is assumed where no operator is specified, and it takes precedence over other operators, but not other functions. Juxtaposition folds to the left. Using a function to the left is OK, but using a function on the right produces an error.
Dice> 1 2^3 plus(1,2) ^ 3 → 27 Dice> 1 2^3 1+3 plus(1,2) ^ plus(3,1) + 3 → 84 Dice> min(2,3) 4 plus(min(2,3),4) → 6 Dice> 2 min(3,4) /* ERROR! */
Using plus
as the default juxtaposition operator behaves "correctly" with negative values:
Dice> 1-3 plus(1,-3) → -2 Dice> 1 -3 plus(1,-3) → -2 Dice> 1+-3 /* not juxtaposition! ordinary addition */ 1 + -3 → -2 Dice> 1 - 3 /* not juxtaposition! ordinary subtraction */ 1 - 3 → -2 Dice> 1--3 /* not juxtaposition! subtracting a negative */ 1 - -3 → 4
Same examples with a variable:
Dice> b set 7 b ↜ 7 → 7 Dice> b-3 plus(b,-3) → 4 Dice> b -3 plus(b,-3) → 4 Dice> b - 3 /* not juxtaposition! ordinary subtraction */ b - 3 → 4 Dice> b+-3 /* not juxtaposition! ordinary addition */ b + -3 → 4
Aliased ¼
for 0.25, ½
for 0.5 and ¾
for 0.75
Identifiers can now use either:
Palamedes doesn't have any builtin functions for computing covariances or correlations. That's because its matrix capabilities are so good, it's rather trivial just to apply the definitions. See the following links containing background info on these calculations:
So here is the same example redone in Palamedes including the conversion from a covariance matrix to correlation matrix:
/* Input */ N ← 5 Math ← [90, 90, 60, 60, 30] English ← [60, 90, 60, 60, 30] Art ← [90, 30, 60, 90, 30] Scores ← [Math, English, Art] xbar ← [mean Scores]^T ones ← [N#1] Covariance ← N^-1 * (Scores - xbar × ones) × (Scores - xbar × ones)^T; Covariance Correlation ← sqrt(diag(Covariance))^-1 × Covariance × sqrt(diag(Covariance))^-1; Correlation//round |
/* Output */ N ← 5 Math ← [90, 90, 60, 60, 30] English ← [60, 90, 60, 60, 30] Art ← [90, 30, 60, 90, 30] Scores ← [Math, English, Art] xbar ← [mean(Scores)]^T ones ← [N # 1] Covariance ← N ^ -1 * (Scores - xbar × ones) × (Scores - xbar × ones)^T Covariance → [[ 504, 360, 180 ], ... [ 360, 360, 0 ], ... [ 180, 0, 720 ]] Correlation ← sqrt(diag(Covariance)) ^ -1 × Covariance × sqrt(diag(Covariance)) ^ -1 round(Correlation) → [[ 1, 0.85, 0.3 ], ... [ 0.85, 1, 0 ], ... [ 0.3, 0, 1 ]] |
Note: If you apply var
or sd
to an array or matrix, it computes the sample standard deviation. So if you need the population statistics, you must use a correction factor. For example, if you wanted to convert the Correlation matrix back to the population Variance-Covariance matrix, do this:
factor ← sqrt((N - 1)/N) sd ← factor * sd Scores diag(sd) × Correlation × diag(sd)//round
Note: The whitespace space around the minus sign in N - 1
is needed. It's needed here because N
does double duty as a random variate function, so N-1
would actually compute N(-1,1)
. If we had used lowercase n
, which isn't a function name, then we could write n-1
but this would then implicitly call the juxtaposition operator. If it were the default value of plus
that wouldn't be a problem in this case. The reason for all this complexity is that without preceding space, the minus sign is part of the number: -1 is a negative number, not a subtraction.
But if you really wanted the sample Covariance matrix, you could simply write:
sd ← sd Scores diag(sd) × Correlation × diag(sd)
/* Happy Hanukkah! (Sunset Tuesday, December 16) */ sides gets {"yiddish": ["nisht", "halb", "gants", "shtel ayn"], "english": ["nothing", "half", "all", "put in"], "hebrew": ["נ","ה","ג","ש"]} dreidel gets quote(choose(sides after "yiddish")) dreidel; dreidel; dreidel 3 # dreidel 100000 # dreidel |> freq |> round
index
) for position of object in arrayin
) to be set membership. Used to be index of object in arraynotin
) for negation of set membershipimplies
for logical implication'
(ascii apostophe) as valid part of variable name, e.g., A'
for A prime. Note that 'A
is still quote A
, i.e., the definition of A
.≠
(unicode not equal) for !=
or ne
⊤
(unicode down tack) for true
⊥
(unicode up tack or falsum) for false
⇒
(unicode rightward double arrow) for ,
(comma)if
for lookup
while
block now returns last executed statementwhile
block was limited to 10 loops to prevent infinite loops; increased limit to 100,000 loops[a .. b]
now prints itself with whitespace around the double dot. BEWARE: In some cases, using a double dot immediately after a numeric subscript without intervening whitespace may produce an undesired result on some platforms.There is an example of computing stats for a simplified version of Chainmail combat using two completely different methods:
The following simplifications to the rules were made:
Both sides simply face each other and slug it out until one or both sides has no troops left (an absorption state).
Both methods compute the expected number of turns to absorption, variance on the number of turns to absorption, and other information. But the Absorbing Markov chain model computes exact results. The Monte Carlo approximation agrees with these exact results:
For complete details, including the Palamedes program listings, follow these links:
Added tensor(expr,q,m,n)
tensor constructor to create m-by-n-by-p tensor. It varies p
(flor "planes") over 0..q-1
, r
(for "rows") over 0..m-1
and c
(for "columns") over 0..n-1
and evaluates expr
at each point to get the value of the new tensor T[p,r,c]
. The expression expr
should be written in terms of the variables p, r and c. The values in these variables will not be permanently effected---they are saved and later restored.
Added matrix(expr,m,n)
matrix constructor to create an m-by-n matrix. It varies r
(for "rows") over 0..m-1
and c
(for "columns") over 0..n-1
and evaluates expr
at each point to get the value of the new matrix M[r,c]
. The expression expr
should be written in terms of the variables r and c. The values in these variables will not be permanently effected---they are saved and later restored. Example: Another way to create a 3-by-3 identity matrix...
Dice> matrix(lookup [r=c, 1, true, 0],3,3) matrix(lookup [r = c, 1, true, 0],3,3) → [[ 1, 0, 0 ], ... [ 0, 1, 0 ], ... [ 0, 0, 1 ]]
Added array(expr,m)
matrix constructor to create an array of length m. It varies i
(for "index") over 0..m-1
and evaluates expr
at each point to get the value of the new array A[i]
. The expression expr
should be written in terms of the variables i. The values in this variable will not be permanently effected---it is saved and later restored. Example:
Dice> array(i^2+4,5) array(i ^ 2 + 4,5) → [4, 5, 8, 13, 20]
Added update(T,value,p,r,c)
, update(M,value,r,c)
and update(A,value,i)
functions. Updates the given object (tensor, matrix or array) with the given value at the given point, e.g., update(M,3,r,c)
sets M[r,c] = value
. But update
returns a new object---the old value of the object (T, M or A) is not effected. Example:
Dice> update(I,9,0,1) update(I,9,0,1) → [[ 1, 9 ], ... [ 0, 1 ]]
Note that this doesn't really change the value of I
unless you use an assignment, e.g., I set update(I,9,0,1)
.
Added I(n)
function to create an n-by-n identity matrix. Another way to create a 3-by-3 identity matrix:
Dice> I(3) I(3) → [[ 1, 0, 0 ], ... [ 0, 1, 0 ], ... [ 0, 0, 1 ]]
Added diag(M)
function to create an diagonal matrix from M. Off-diagonal elements are zeroed out. The Original matrix M is uneffected.
Dice> diag(matrix(2^(r^2+2*c+1),3,3)) diag(matrix(2 ^ (r ^ 2 + 2 * c + 1),3,3)) → [[ 2, 0, 0 ], ... [ 0, 16, 0 ], ... [ 0, 0, 512 ]]
Modified Tokenizer so that Unicode subscripts and superscripts are valid chracaters in identifiers, e.g., σ²
is a valid variable name, and yₜ ← [0..10]; yⁱ ← e ^ yₜ
is a valid expression. Note: By themselves d, f, k, x, z are not valid variable names -- they are dice operators. But x₀, x₁, x², etc. are now valid names.
Modified Tokenizer: 1/6
is lexed as the NUMBER 0.1666666... whereas 1 / 6
is lexed as the EXPRESSION 1 '/' 6
. This is so I can write expressions like NB 5, 1/6
instead of NB 5, (1/6)
. I still need to write NB 5, (1/6^3+1/4)
. Note that 2/6^3
is (2/6)^3
but 2 / 6^3
is 2/(6^3)
. I'm not sure if I will keep this behavior or simply rewrite all the discrete probability distribution operators NB
, geom
, pois
and so on as functions, in which case I would always need to use outer parens around the arg list: NB(5, 1/6)
but could then write NB(5, 1/6^3+1/4)
without parens around the second arg. In short, this behavior is experimental and perhaps temporary. This behavior will be stabilized when the code moves from the alpha to the beta stage.
Fixed a bug in prob(max(a,b))
and prob(min(a,b))
that could result in bogus results when arguments a,b not iid.
Calling mean[d6,d10,d20]
returns a scalar with the mean of the array containing elements sampled randomly from the d6, d10, and d20 dice distributions, so you'll get a different result each time. But mean(d6,d10,d20)
returns an array with the means of the d6, d10 and d20 dice distributions respectively. N.B. the difference is that [d6,d10,d20]
is an array whereas (d6,d10,d20)
is a tuple. Arrays are first class citizens in the Palamedes language. But tuples are just part of the grammar for function calls, only understood by the parser.
Additionally, mean[prob(d6),prob(d10),prob(d20)]
returns an array of means. And mean([[1, 2, 3], [1, 3, 5], [0, 10, 20]])
returns an array with the means of each row in the given matrix.
These tricks only works with the functions mean
, sd
and var
functions so far.
Added pair
function to turn a pair of arrays into an array of pairs. Note that this...
Dice> pair([1..6],[1..6]) pair([1..6],[1..6]) → [[ 1, 1 ], ... [ 2, 2 ], ... [ 3, 3 ], ... [ 4, 4 ], ... [ 5, 5 ], ... [ 6, 6 ]]
is the similar to this...
Dice> resize([6,2], [1..6] zip [1..6]) resize([6, 2],[1..6] ⇈ [1..6]) → [[ 1, 1 ], ... [ 2, 2 ], ... [ 3, 3 ], ... [ 4, 4 ], ... [ 5, 5 ], ... [ 6, 6 ]]
...except that resize will repeat its inputs as often as necessary to create the matrix of the given size while pair will not. Also resize
is more general and can create matrices of any size whereas pair(a,b)
only creates Nx2 matrices where N=min(count a, count b)
.
Previously, min
and max
functions only computed probabilities for samples of iid random variables. Now they work on tuples or arrays of independent rv's, even if they're not identical, e.g., prob(max(2d6,3d6))//rat//table
or stats(max(4d6,5d6+1,7d6+1))//table
.
Added var
function to compute variance. Note that var [1..6]
computes the sample variance of the given array while var(d6)
computes the population variance of the d6 dice distribution. Added var
to the list of descriptive statistics returned by stats
.
Example node.js command-line session using the Palamedes module:
Dice> prob(4d6k3)//table table(prob(4d6k3)) → key value 3 0.0007716049382716049 4 0.0030864197530864196 5 0.007716049382716049 6 0.016203703703703703 7 0.029320987654320986 8 0.047839506172839504 9 0.07021604938271606 10 0.0941358024691358 11 0.11419753086419752 12 0.12885802469135801 13 0.13271604938271606 14 0.12345679012345678 15 0.10108024691358025 16 0.07253086419753087 17 0.041666666666666664 18 0.016203703703703703 Dice> 4d6k3 |> prob |> rat |> table table(rat(prob(4d6k3))) → key value 3 1/1296 4 1/324 5 5/648 6 7/432 7 19/648 8 31/648 9 91/1296 10 61/648 11 37/324 12 167/1296 13 43/324 14 10/81 15 131/1296 16 47/648 17 1/24 18 7/432 Dice> prob(4d6k3)//stats//table table(stats(prob(4d6k3))) → key value count 16 max 18 mean 12.2445987654321 median 12 min 3 mode 13 sd 2.846844445311498 Dice> prob(4d6k3)//sum sum(prob(4d6k3)) → 1
The lexer now translates 99%
into 0.99
. But that only works with numeric literals: variable%
is a parse error, while d%
still rolls a percentile die (1-100), z%
(0-99) and x%
an exploding percentile (add and reroll on 100). Additionally, the variables bp
and bps
are now predefined as 10^-4
, i.e., basis points, a percent of a percent. Added percent
function to format output as percentage; an optional precision may be specified. Examples:
Dice> 12% 0.12 → 0.12 Dice> variable% Caught exception executing 'variable%': ... Dice> d%; 10000#d%//min; 10000#d%//max d% → 99 min(10000 # d%) → 1 max(10000 # d%) → 100 Dice> z%; 10000#z%//min; 10000#z%//max z% → 69 min(10000 # z%) → 0 max(10000 # z%) → 99 Dice> x%; 10000#x%//min; 10000#x%//max x% → 32 min(10000 # x%) → 1 max(10000 # x%) → 299 Dice> LIBOR_1_yr gets 0.56% LIBOR_1_yr ← 0.005600000000000001 Dice> LIBOR_1_yr + 50*bps LIBOR_1_yr + 50 * bps → 0.010600000000000002 Dice> percent(0.0666,[0..3]) /* format with 0 to 3 digits after decimal */ percent(0.0666,[0..3]) → [7%, 6.7%, 6.66%, 6.660%] Dice> 0.0666//percent /* default precision is two digits after decimal */ percent(0.0666) → 6.66%
If the random variable S is distributed as NB r, p
: r is number of failures until the experiment is stopped, and p is the success probability in each experiment. The r.v. S is the number of successes before r failures.
NB r, p
generates a random variate from this distribution. size # NB r, p
generates a sample of size size
.
prob(NB r, p)
or NB r, p//prob
generates a JSON object with the probabilities of S successes. You can further format this expression with rat
or human
or percent
, and then display it in a table with table
.
stats(NB r, p)
or NB r, p//stats
generates a JSON object with summary statistics. Again, you can further format this expression with rat
or human
or percent
, and then display it in a table with table
.
Example: A door-to-door salesman has a 40% chance of success. What is the chance of n
successes before 5 failures? We can make a table of probabilities, expressed as percents, as follows:
Dice> NB 5, 40%//prob//percent//table table(percent(prob(NB 5, 0.4))) → key value 0 7.78% 1 15.55% 2 18.66% 3 17.42% 4 13.93% 5 10.03% 6 6.69% 7 4.20% 8 2.52% 9 1.46% 10 0.82% 11 0.45% 12 0.24% 13 0.12% 14 0.06% 15 0.03% 16 0.02% 17 0.01%
The table contains additional 0.00%
values not shown here. They are actually greater than zero, however, the percent
formatting function rounded them down to zero.
Example: The average number of homes sold by the Acme Realty company is 2 homes per day. What is the probability that exactly 3 homes will be sold tomorrow?
Dice> prob(pois 2) after 3//percent percent(prob(pois 2) ∘ 3) → 18.04%
Generate 10 random variates from this distribution:
Dice> 10#pois 2 10 # pois 2 → [3, 1, 5, 0, 2, 1, 2, 3, 1, 4]
Compute summary statistics for this distribution:
Dice> pois 2//stats//human//table table(human(stats(pois 2))) → key value count 17 max 16 mean 2 median 2 min 0 mode 1,2 sd √2
Annuity-immediate functions for present value and future value: a(n,i)=(1-(1+i)^-n)/i
and s(n,i)=((1+i)^n-1)/i
. And annuity-due functions a_due(n,i)=(1+i)*a(n,i)=a(n-1,i)+1
and s_due(n,i)=(1+i)*s(n,i)=s(n+1,i)-1
. Perpetuties: a(∞,i)=1/i
and a_due(∞,i)=1/discount
where discount=i/(1+i)
. See Annuities and Perpetuities for more details.
Example. List the present values of a 5 year annuity immediate with nominal annual interest rates at 6%, 12%, and 24% and monthly payments of $100:
Dice> 100*a(5*12,[6%,12%,24%]/12)//fix//commify commify(fix(100 * a(5 * 12,[0.06, 0.12, 0.24] / 12))) → [5,172.56, 4,495.50, 3,476.09]
Example. List the present values of 1, 5 and 10 year annuities immediate with a nominal annual interest rates of 12% and monthly payments of $100:
Dice> 100*a([1,5,10]*12,12%/12)//fix//commify commify(fix(100 * a([1, 5, 10] * 12,0.12 / 12))) → [1,125.51, 4,495.50, 6,970.05]
Note that if you input two arrays such as a([1,5,10]*12,[6%,12%,24%]/12)
, then the 1-year annuity gets paired with the 6% interest rate, the 5-year with 12% and the 10-year with 24%. If instead you want to see a 3x3 matrix with all possible matchups of the three terms with the three rates, use the outer
product function as follows. Note that the 100*...
payment size multiplies the outer
function, not the a
function. That's because at present the first argument to outer
must be a function rather than an expression. In the output matrix, years are rows and rates are columns:
Dice> 100*outer(a, [1, 5, 10]*12, [6%,12%,24%]/12)//fix//commify commify(fix(100 * outer(a,[1, 5, 10] * 12,[0.06, 0.12, 0.24] / 12))) → [[ 1,161.89, 1,125.51, 1,057.53 ], [ 5,172.56, 4,495.50, 3,476.09 ], [ 9,007.35, 6,970.05, 4,535.54 ]]
You may input the unicode symbols ä and s̈ for the present and future values of annuities due, but the lexer changes ä to a_due
and s̈ to s_due
to avoid potential I/O issues with combining diacritics. Example:
Dice> s̈(84,0.0075)*100//fix//commify commify(fix(s_due(84,0.0075) * 100)) → 11,730.01
If you input ∞
or inf
as the term for a present value, it computes a the value of a perpetuity. Example:
Dice> ä(∞,6%) a_due(∞,0.06) → 17.666666666666668 Dice> discount ← 6%/(1+6%) discount ← 0.06 / (1 + 0.06) Dice> a_due(∞,6%)=1/discount a_due(∞,0.06) = 1 / discount → true
Note that using I
and i
for interest rates will overwrite their definitions for complex numbers and matrices. You can save the old values to temporary variables by doing oldi set i; oldI set I
and restore them by doing the inverse.
Find the first derivative of the function 3*y^2 - 6
with respect to y at the point y=2:
Dice> deriv(3*y^2 - 6, y, 2, 1) deriv(3 * y ^ 2 - 6,y,2,1) → 12.00000004046644
The function deriv
takes 4 arguments:
deriv(3*y^2 - 6, y, 2, 1) ^ ^ ^ ^ | | | |__ nth derivative, n can be 1 to 4, first derivative in this case | | |_______ point at which we're taking derivative, y = 2 | |____________ variable wrt we're taking derivative |________________________ expression we're taking derivative of
Remember x
means exploding die roll and z
means zero-biased die roll, so do not use x or z as variable names!!!. Don't use d
, f
or k
either, because they're part of dice roll syntax also.
We use a 9-point numerical technique to approximate the derivative. For a description of how this is done, read the articles on Numerical differentiation and Finite difference coefficient.
The function solve(expression,variable,starting_guess)
tries to solve for the value of variable
that makes the equation expression = 0
true using the given starting guess. The Newton–Raphson method is used. This in turn uses the deriv
function just mentioned. The starting_guess
should be chosen as close to the solution as possible, and it should not be an extremal point (i.e., a point where the expression has derivative zero). At most, one solution close to the starting guess will be found---this function will not find all solutions. Examples:
Approximate the square root of 2 by solving the formula y^2 - 2 = 0
for y. Remember not to enter the = 0
part. Then compare this appromination to JavaScript's value of Math.sqrt(2)
.
Dice> solve(y^2 - 2, y, 1) solve(y ^ 2 - 2,y,1) → 1.414213562373095 Dice> abs(last - sqrt(2)) abs(last - sqrt(2)) → 2.220446049250313e-16
Note that the difference ε = 2.22e-16
is Machine epsilon, i.e., the smallest possible roundoff error---anything smaller added to 1 is just 1. Here is a node.js session which illustrates this:
> ε=2.22e-16 2.22e-16 > 1+ε 1.0000000000000002 > 1+ε/2 1
I put $1,000 at the end of each year in a bond that gets me 6% per year. How many years must I do this for the investment to have a present value of $4,212.36? Start with a guess of 3 years...
Dice> solve(1000*a(y,6%) - 4212.36, y, 3)//round round(solve(1000 * a(y,0.06) - 4212.36,y,3)) → 5
I put $1,000 at the end of each year in a bond that for 5 years. What's the rate for the investment to have a present value of $4,212.36? Start with a guess of 3%...
Dice> solve(1000*a(5,y) - 4212.36, y, 3%)//percent percent(solve(1000 * a(5,y) - 4212.36,y,0.03)) → 6.00%
For matrix inversion/determinant, I had taken an algorithm from a journal article; it was supposed to be fast and efficient. However, it turned out unreliable and produced incorrect results in many cases, e.g., when there were zeros on the diagonal.
So I wrote a new matrix inversion/determinant function from scratch based on good-old Gauss-Jordan elimination. It has one optimization: it uses partial pivoting (exchanges rows to move the entry with the largest absolute value to the pivot position), in order to decrease JavaScript rounding error.
Anyway, this should fix any bugs encountered with the old matrix inversion/determinant code.
A bug in the internal r_op1
function for threading over tensors didn't return a result, e.g., in croots(3,c(1,0))//cstr
the cstr
function wouldn't return a result, so the GUI reports a result of undefined
. This has been fixed.
Added ellipses to ends of lines in multi-line matrix output, to make it easier to copy and paste them back in as inputs.
Updated cstr
function to ouput a complex number in complex cartesian notation 2*I - 3*i//cstr
yields c(2, -3)
. Note that cstr
implicitly calls human
to format numbers.
Added polar notation operator for complex numbers: r∠ϕ
or r polar ϕ
. Note that if either r (or ϕ) is not a number or an identifier, it must be wrapped in parens, e.g.,
Dice> 1 ∠ (pi/2)//human human(1 ∠ (π / 2)) → [[ 0, -1 ], ... [ 1, 0 ]] Dice> 1 ∠ (pi/2)//cstr cstr(1 ∠ (π / 2)) → c(0, 1)
Added cpolar
function to ouput a complex number in polar notation. Note that cpolar
implicitly calls human
to format numbers. Example:
Dice> c(√2/2, √2/2)//cpolar cpolar(c(√2 / 2,√2 / 2)) → 1 ∠ (1/4*π)
croots(n,z)
: nth roots of the complex number z.
/* The three third roots of 1 */ Dice> croots(3,c(1,0))//cstr cstr(croots(3,c(1,0))) → [c(1, 0), c(-1/2, √3/2), c(-1/2, -√3/2)]
cexp
function computes the complex exponential, e.g.,
Dice> cexp(c(2,pi/4))//cpolar cpolar(cexp(c(2,π / 4))) → (e^2) ∠ (1/4*π)
As yet, e^c(r,ϕ)
does not produce e^r*(cos ϕ + i sin ϕ)
. Instead it just computes elementwise expontiation of the matrix representation of c(r,ϕ)
. In the near future, I may alter the code so that e^c(r,ϕ)
does complex exponentiation and e^matrix
does matrix exponentiation---I'm debating whether elementwise exponentiation is more useful. In the meantime, call the cexp
function.
cln(z,k)
: Complex logarithm. Unlike logarithms of real numbers, a complex number r∠ϕ has an infinite number of natural logs which correspond to the argument ϕ±2πk, where k is any integer. Therefore, you must specify k as the second parameter. E.g., cln(i,0)
/* Several branches of the natural logarithm of i calculated at once */ Dice> cln(i,[0..2])//cstr cstr(cln(i,[0..2])) → [c(0, 1/2*π), c(0, 5/2*π), c(0, 9/2*π)]
Added F#'s forward pipe operator expression |> function
. Gave it the name pipe
, the single unicode character ↦
, and the alias //
because it works like Mathematica's postfix function application ("slash slash") operator: expression // function
, namely it yields function(expression)
. This can be very useful when you want to focus on an important calculation (the expression in this case), and push the formatting (the function in this case) out of the way. For example, 10*atan(1/7) + 4*atan(3/79) |> human
first does a computation (the LHS expression) and then makes the result human-readable with the human
function. The forward pipe operator has two restrictions:
In the following example, we compute the probability mass function of rolling 3d6. This normally returns a JSON object in which the values are floating-point decimals. So we use the forward pipe operator first with the rat
function to convert the decimals to rationals and second with the table
function to convert the JSON object to a tabular format.
Dice> prob(3d6) |> rat |> table table(rat(prob (3d6))) → key value 3 1/216 4 1/72 5 1/36 6 5/108 7 5/72 8 7/72 9 25/216 10 1/8 11 1/8 12 25/216 13 7/72 14 5/72 15 5/108 16 1/36 17 1/72 18 1/216
You could have also written this 3d6 |> prob |> rat |> table
or 3d6//prob//rat//table
.
Examples:
Dice> 1e+6 1e+6 → 1000000 Dice> 1e6 1e6 → 1000000 Dice> 1e-6 1e-6 → 0.000001 Dice> sci(1000000) sci(1000000) → 1e+6 Dice> 1000//sci sci(1000) → 1e+3 Dice> 1E6//commify commify(1E6) → 1,000,000
Since matrix math is already implemented, we decided to implement complex math via 2x2 rotation matrix representations... i
is just a 90° counterclockwise rotation:
Dice> i i → [[ 0, -1 ], [ 1, 0 ]] Dice> I I → [[ 1, 0 ], [ 0, 1 ]] Dice> 3*I - 2*i 3 * I - 2 * i → [[ 3, 2 ], [ -2, 3 ]]
The convenience functions c
and cstr
allow for re-writing pairs of numbers as matrix reps of complex numbers and for re-writing a matrix rep of a complex number in terms of I
and i
:
Dice> c(2,-3) c(2,-3) → [[ 2, 3 ], [ -3, 2 ]] Dice> c(2,-3)//cstr cstr(c(2,-3)) → 2*I - 3*i
The functions Re
(real part), Im
(imaginary part), mag
(magnitude AKA modulus or absolute value), arg
(argument), and conj
(conjugate) are implemented:
Dice> Re(3*I - 2*i) Re(3 * I - 2 * i) → 3 Dice> Im(3*I - 2*i) Im(3 * I - 2 * i) → -2 Dice> mag(3*I - 2*i) |> human human(mag(3 * I - 2 * i)) → √13 Dice> arg(3*I - 2*i) |> human human(arg(3 * I - 2 * i)) → -1581/8447*π Dice> conj(3*I - 2*i) conj(3 * I - 2 * i) → [[ 3, -2 ], [ 2, 3 ]]
Addition, subtraction, and complex^integer
exponentiation work as usual. e^complex
not implemented yet, since we don't have e^matrix
implemented yet.
Dice> (3*I - 2*i)^2 (3 * I - 2 * i) ^ 2 → [[ 5, 12 ], [ -12, 5 ]]
Important: Since complex numbers are really matrices, you must remember to multiply them via matrix multiplication cross
or ×
rather than elementwise multiplication *
. And also to divide via multiplication by a matrix inverse. Examples:
Dice> (3*I - 2*i) cross (3*I - 2*i) (3 * I - 2 * i) × (3 * I - 2 * i) → [[ 5, 12 ], [ -12, 5 ]] Dice> (3*I - 2*i) cross (3*I - 2*i)^-1 |> human human((3 * I - 2 * i) × (3 * I - 2 * i) ^ -1) → [[ 1, 0 ], [ 0, 1 ]]
Warning: These functions may not work consistently across browsers, because date-parsing algorithms are implementation-dependent.
Unless stated otherwise, the following date functions may take arguments in the following formats:
- An empty/undefined argument. E.g., cal()
returns a calendar for the current month.
- A string such as "9/9/2014", "now", or "today". Or an array of such strings. E.g., cal ["10/1/2014", "now"]
. "now" corresponds to the current date and time whereas "today" corresponds to the current date at 12am.
- The number of milliseconds since the epoch; or an array of such numbers.
- An array of arrays of years, months, days, hours, minutes, seconds, milliseconds, where not all components are mandatory. E.g., cal [[2014,10]]
prints a calendar for October while cal [[2014,10],[2014,11]]
prints calendars for October and November. Note that this is an array of arrays, so as not to be confused with an array of milliseconds since the epoch.
- JSON object defining the date; or an array of such objects. For example, cal {"year": 2014, "month": 10}
produces a calendar for October while cal [{"year": 2014, "month": 10}, {"year": 2014, "month": 11}]
produces calendars for October and November.
The functions:
- date()
returns the milliseconds since the start of the epoch 1-Jan-1970 00:00:00 GMT.
- datestr()
returns a string representation of a date. E.g., datestr()
returns something like "Tue Sep 09 2014 01:08:07 GMT-0400 (EDT)".
- timestr()
returns just the time portion, e.g., timestr()
returns something like "01:08:11 GMT-0400 (EDT)".
- dateobj()
returns a JSON object representating a date. For example,
Dice> dateobj()//table table(dateobj()) → key value date Tue Sep 09 2014 01:10:08 GMT-0400 (EDT) day name Tuesday day of month 9 day of week 3 day of year 252 days in month 30 days in year 365 hour 1 julian date 2456909.715375081 julian day number 2456909 last day of Feb false leap year false millisecond 407 minute 10 month 9 month name September ms since epoch 1410239408407 second 8 week 37 year 2014
cal
returns a calendar month as a matrix. cal()
returns the current month. For example,Dice> cal [[2014,10],[2014,11]] cal([[2014, 10], [2014, 11]]) → [ [[ Oct, , , , , , 2014 ], [ Sun, Mon, Tue, Wed, Thu, Fri, Sat ], [ , , , 1, 2, 3, 4 ], [ 5, 6, 7, 8, 9, 10, 11 ], [ 12, 13, 14, 15, 16, 17, 18 ], [ 19, 20, 21, 22, 23, 24, 25 ], [ 26, 27, 28, 29, 30, 31, ]], [[ Nov, , , , , , 2014 ], [ Sun, Mon, Tue, Wed, Thu, Fri, Sat ], [ , , , , , , 1 ], [ 2, 3, 4, 5, 6, 7, 8 ], [ 9, 10, 11, 12, 13, 14, 15 ], [ 16, 17, 18, 19, 20, 21, 22 ], [ 23, 24, 25, 26, 27, 28, 29 ], [ 30, , , , , , ]] ]
The following thee functions compute differences between two input days. They are not compatible with the [[year1,month1,day1],[year2,month2,day2]]
or JSON object input format. You can pass in:
- 2 strings or number representing ms since the epoch.
- a string (or number representing ms since the epoch) and an array of strings (or numbers representing ms since the epoch), in which case the differences are computed between the scalar and each element of the array.
- two arrays of strings (or numbers representing ms since the epoch). In this case, the differences are computed pairwise.
days
computes the number of whole days between the two inputs, plus remainder hours mod 24, plus remainder minutes mod 60, etc. Note that two days with zero time offset can differ by a number of whole days +/-one hour, the extra hour attributable to a change from standard to daylightsaving time or vice versa. The result is a JSON object.duration
computes the fractional days, hours, minutes, and seconds between two input dates. It computes the whole number of milliseconds between the dates. It also computes the number of months between the inputs using the formula: 12*(Y2-Y2)+(M2-M1)
. And the number of years via months/12
. It returns a JSON object. This object is the direct product (or union) of many different methods of determining the time difference; each member is a complete answer to the question how much time is there between these two days, as opposed to days
which returns an object, each member of which tells only part of the answer.days360
computes the factor "days in the accrual period / days in the year" using the 30/360 accounting method with adjustments for US bonds. There is an EOM
flag initially set to false referenced by this function. It returns a floating point number. See Day count convention.The addmonths(date,months)
function adds the given number of months to the given date. The addyears(date,years)
function adds the given number of years to the given date. The date argument to these functions must be a string (or number representing ms since the epoch) or an array of strings or number. JSON objects and dates in the array format [year1,month1,day1]
are not supported.
There are also the following constants for datetime computations:
seconds: 1000 minutes: 1000*60 hours: 1000*60*60 days: 1000*60*60*24 weeks: 1000*60*60*24*7 year: 1000*60*60*24*365 leapyear: 1000*60*60*24*366 averageyear: 1000*60*60*24*365.2425
For example,
Dice> datestr(date("9/6/2014") + 3*days) datestr(date("9/6/2014") + 3 * days) → "Tue Sep 09 2014 00:00:00 GMT-0400 (EDT)" Dice> date("9/9/2014") + 4*weeks + 1*days//datestr datestr(date("9/9/2014") + 4 * weeks + 1 * days) → "Wed Oct 08 2014 00:00:00 GMT-0400 (EDT)"
Simple interval arithmetic has been implemented:
Dice> interval(plus,[1, 2],[3, 4]) interval(plus,[1, 2],[3, 4]) → [4, 6] Dice> interval(times,[1, 2],[3, 4]) interval(times,[1, 2],[3, 4]) → [3, 8] Dice> interval(minus,[1, 2],[3, 4]) interval(minus,[1, 2],[3, 4]) → [-3, -1] Dice> interval(div,[1, 2],[3, 4])//human human(interval(div,[1, 2],[3, 4])) → [1/4, 2/3]
Predefined the following constants for convenience; you may overwrite them: - million, M: 10^6 - billion, B: 10^9 - trillion, T: 10^12 - trillionth, pico, p: 10^-12 - billionth, n, nano: 10^-9 - millionth, µ, micro: 10^-6 - thousandth, m, milli: 10^-3 - tenth, d, deci: 10^-1
Added the ÷
operator to perform Euclidean division. a÷b
produces the array [q,r]
where q is the quotient and r is the remainder. Example:
Dice> 13÷3 13 ÷ 3 → [4, 1]
This is just a handy shortcut for doing:
Dice> [floor(13/3),13 mod 3] [floor(13 / 3), 13 mod 3] → [4, 1]
It works on array inputs to produce a matrix. TMTOWTDI: Here's another way to produce a 2x2 identity matrix:
Dice> [2,1]÷2 [2, 1] ÷ 2 → [[ 1, 0 ], [ 0, 1 ]]
Note that the table of probabilities prob(d4÷d4)
is not isomorphic to prob(d4/d4)
, because 1÷4=1÷3=1÷2=[0,1]
but 1/4≠1/3≠1/2≠1/4
.
The following operators have been converted to functions: cdf
, count
, domain
, freq
, max
, mean
, median
, min
, mode
, prob
, range
, sd
, shuffle
, sort
, stats
, tally
, uniq
. This means, among other things, their names are now case sensitive and they may be used with the forward pipe operator:
Dice> 2#d6|>min|>cdf|>human|>table table(human(cdf(min(2 # d6)))) → key value 1 11/36 2 5/9 3 3/4 4 8/9 5 35/36 6 1 Dice> choose [0,0,0,5,5,5] + choose [1,2,3,4,5]//prob//human//table table(human(prob(choose([0, 0, 0, 5, 5, 5]) + choose([1, 2, 3, 4, 5])))) → key value 1 1/10 2 1/10 3 1/10 4 1/10 5 1/10 6 1/10 7 1/10 8 1/10 9 1/10 10 1/10 Dice> d6 - d6 |> abs |> prob |> human |> table table(human(prob(abs(d6 - d6)))) → key value 0 1/6 1 5/18 2 2/9 3 1/6 4 1/9 5 1/18 Dice> d6 - d6 |> abs |> prob |> sum sum(prob(abs(d6 - d6))) → 1
The QUOTE
operator (AKA '
) used to prevent an expression from evaluating was renamed LAZY
. And the functions quote
and unquote
were added to wrap/unwrap strings in double quotes.
http://palamedes.altervista.org?cmd=3d6
. Multi-line commands are supported if they are properly URI encoded, e.g., http://palamedes.altervista.org?cmd=score%20gets%203d6%0Ascore%0Atable(prob(score))
where %20
is the code for a space and %0A
is the code for a line break.…
or three dots ...
. The web- or CLI- GUI actually does the work of combining broken lines, while the lexer ignores the elipses as if they were whitespace. Example in the web GUI:
/* Input */ score gets 3d6 (6 # score) ⊎ ... [10 * score] (6 # score) ⊎ … [10 * score] |
/* Output */ score ← 3d6 (6 # score) ⊎ [10 * score] → [11, 12, 10, 7, 9, 10, 130] (6 # score) ⊎ [10 * score] → [6, 12, 10, 5, 9, 9, 70] |
Example in the node.js command-line interface:
Dice> 1+2+... Dice> 3+... Dice> 4 1 + 2 + 3 + 4 → 10
rat
function to attempt to rationalize floating point numbers. Given an input bigger than unity, it returns an improper fraction, not a mixed number. Don't use with really big numbers! rat
depends on the tol
variable (initially set to 1/10^9), and stops approximating when abs(rat-input)<tol
, so decrease tol
(e.g., tol gets 1/10^12
) to get better precision. Internally, JavaScript's crappy floats are still used. Example:Dice> table(rat(prob(3d6))) table (rat(prob (3d6))) → key value 3 1/216 4 1/72 5 1/36 6 5/108 7 5/72 8 7/72 9 25/216 10 1/8 11 1/8 12 25/216 13 7/72 14 5/72 15 5/108 16 1/36 17 1/72 18 1/216
~
operator so that with a number 0<=x<=1
returns the complement 1-x
, any other number: the opposite, booleans: the negation, and anything else: the identity.pi/2
. Examples: rot "frack this!"
and sin 1
are OK, but sin pi/2
sin(pi/2)
. Use parens to combine two or more functions: commify(fix bignum)
.prod
function to behave more sensibly on tensors, matrices, vectors, etc:prod
of list of matrices is the matrix productprod
of list of arrays is the cartesian productprod
of list of numbers is the usual productprod
of list of booleans is the boolean productprod
of list of strings is the concatenation, unless each string is a numberprod
of list of JSON objects is the compositionprod
of one JSON object is the numeric product of the valuesprod
of anything else is itselfExamples:
/*Products*/ /*cartesian product*/ prod[["A","B","C"],[1,2,3],["i","ii","iii"]] /*matrices*/ A gets [[3,2],[-1,0]] B gets [[-2,3],[4,2]] C gets [[-1,5],[1,2]] /*matrix mult*/ prod[A,B,C] /*mult list of nums*/ prod[1..5]=5! /*mult values in a JSON obj*/ prod{"a":5,"b":1,"c":2} /*compose JSON objs*/ prod[{"1": 5,"2":3},{"a":1,"b":1,"c":2}] /*boolean prod*/ prod[1>0,0>-1,5>2]
ortho1
function. Given one n-vector, it returns a list of n-1 orthogonal vectors. Any linear combination of these vectors will also be orthogonal to the input. Example:Dice> rat(ortho1 [3, 4, 5]) rat(ortho1([3, 4, 5])) → [[ -4/3, 1, 0 ], [ -5/3, 0, 1 ]]
Check that the vectors in ortho1 u
are indeed orthogonal to u via matrix multiplication cross
. The arguments to cross
must have compatible dimensions. So remember first to convert u from a vector to a 1x3 row matrix by wrapping it in square brackets [u]
, and to convert ortho1 u
from a 2x3 matrix of row vectors to a 3x2 matrix of column vectors via trans
:
Dice> [u] cross trans(ortho1 u) [u] × trans(ortho1(u)) → [[ 0, 0 ]]
proj(v,u)
function to return the projection of vector v in the direction of vector u.ortho(u,v,w...)
function to perform Gram-Schmidt orthonormalization on a list of vectors u,v,w,etc.angle(u,v)
function to compute the angle between two vectors in radians.primes(n)
function to print the primes p <= min(n,1000).mixed
function to represent numbers as mixed fractions. Example:Dice> table(mixed(10#d%/d10)) table (mixed(10 # d% / d10)) → index element 0 8 + 1/2 1 25 2 42 3 5 4 10 + 1/4 5 5 6 14 + 1/5 7 20 8 7 + 1/3 9 2 + 4/9
commify
function to put commas in big numbers. Example:Dice> table(commify(10#d% ^ d10)) table (commify(10 # d% ^ d10)) → index element 0 899,194,740,203,776 1 35,937 2 253,295,162,119,140,640 3 81 4 161,051 5 12,167 6 4,900 7 147,008,443 8 194,481 9 3,596,345,248,055,296
human
function to try to make JavaScript floating-point numbers human-readable when they represent rational multiples of pi, fractions of square roots, etc. Example: print a table of common trigonometric constants---Dice> human(outer(thread,[sin, cos, tan],[0, π / 6, π / 4, π / 3, π / 2])) human(outer(thread,[sin, cos, tan],[0, π / 6, π / 4, π / 3, π / 2])) → [[ 0, 1/2, √2/2, √3/2, 1 ], [ 1, √3/2, √2/2, 1/2, 0 ], [ 0, √3/3, 1, √3, ∞ ]]
N
that generates random variates for the normal distribution so that it expects the variance as the second parameter N(μ, σ²)
rather than the standard deviation N(μ, σ)
normal
simply to N
.GBM(percentage_drift, percentage_volatility)
which sets up a function S(S0,t)
to compute value of a stochastic process S at time t given the starting value of S=S0 at time t=0 using geometric brownian motion with the constants percentage_drift
, and percentage_volatility
. Useful for simulating Random Walks. There is also a convenience function Wiener(t)
to implement the Wiener process.Lets say we have annualized drift of 8.6% and annualized volatility of 11.9%. Simulate prices for the next 10 years. Assume the price starts at 1:
/* Input */ GBM(0.086,0.119) S(1,[1..10]) |
/* Output */ GBM(0.086,0.119) → OK. GBM setup function S(S0,t) using parameters pct_drift=0.086 and pct_volatility=0.119 S(1,[1..10]) → [1.1300624384470488, 1.0925122553647502, 1.1508143130841304, 1.44566922757506, 1.0780285016705622, 1.1258814206939816, 2.74690169911793, 2.415608120936365, 1.0069109537999272, 2.4611505066933206] |
To convert annual drift to daily, divide by 250. The constant 250 is just the number of trading days in a year. Some use 252 instead. To convert annual drift to monthly, divide by 12. To convert annual volatility to daily, divide by the square root of 250. To convert annual volatility to monthy, divide by root 12.
Let's say all you have is a list of daily prices changes over some period of time in the past, and you want to compute daily (or annual) drift and volatility and drift from historical data. Use the following method:
/* Input */ /* Daily prices over 10 day period */ prices gets [14.40, 14.20, 14.25,14.00,13.75,13.80,13.60,13.75,13.70,13.90] n gets count prices final_prices gets drop prices, 1 initial_prices gets take prices, (n - 1) /* Calculate the logarithmic price changes */ logarithmic_price_changes gets ln(final_prices/initial_prices) /* Daily volatility is their standard deviation */ daily_volatility gets sd(logarithmic_price_changes); daily_volatility /* Annual volatility is √250 times daily volatility */ annual_volatility gets root 250 * daily_volatility; annual_volatility daily_drift gets mean(logarithmic_price_changes) + daily_volatility^2/2; daily_drift annual_drift gets 250*daily_drift; annual_drift |
/* Output */ prices ← [14.40, 14.20, 14.25, 14.00, 13.75, 13.80, 13.60, 13.75, 13.70, 13.90] n ← count prices final_prices ← drop prices, 1 initial_prices ← take prices, (n - 1) logarithmic_price_changes ← ln(final_prices / initial_prices) daily_volatility ← sd (logarithmic_price_changes) daily_volatility → 0.012634706091210165 annual_volatility ← √250 * daily_volatility annual_volatility → 0.19977224407513625 daily_drift ← mean (logarithmic_price_changes) + daily_volatility ^ 2 / 2 daily_drift → -0.003846778372695374 annual_drift ← 250 * daily_drift annual_drift → -0.9616945931738435 |
For drift, we take the mean instead of the standard deviation. Then you must add the variance divided by 2. To annualize the result, multiply by 250. For formulae, see question #5 on Exam 2 for the class, the mathematics of contingent claims.
If we wanted the monthly volatility, we'd multiply daily volatility by the square root of 21, the number of trading days in a month. For monthly drift, we'd multiply daily drift by 21.
In the future, we may want to simulate correlated GBM in two dimensions. See Sigman 2007. This can wait until we add code to exponentiate matrices.
/* Input */ A gets [[12, 6, 177], [34, 9, 54], [18, 8, 80]] A B gets [[12.1,6.66,177.54318],[34,9,54.44],[18,8,80.00001]] fix(B) |
/* Output */ A ← [[12, 6, 177], [34, 9, 54], [18, 8, 80]] A → [[ 12, 6, 177 ], [ 34, 9, 54 ], [ 18, 8, 80 ]] B ← [[12.1, 6.66, 177.54318], [34, 9, 54.44], [18, 8, 80.00001]] fix(B) → [[ 12.10, 6.66, 177.54 ], [ 34.00, 9.00, 54.44 ], [ 18.00, 8.00, 80.00 ]] |
sum
function. Applied to a list of two or more matrices, it returns the matrix sum. Applied to one matrix, it returns to the row sums. Applied to a list of numbers, it returns their sum. Applied to a list of booleans, it returns the count of true values. Applied to a list of strings, it returns their lengths. Applied to a JSON object containing a probability distribution, it returns the convolution of distributions, i.e., distribution for the sum of corresponding random variables.
/* Input */ sum A /* Row sums */ sum(trans A) /* Column sums */ sum(sum A) /* Total of all elements */ fix(sum (A,B, inv A)) /* Add three matrices */ sum[1..100] |
/* Output */ sum(A) → [195, 97, 106] sum(trans(A)) → [64, 23, 311] sum(sum(A)) → 398 fix(sum(A,B,inv(A))) → [[ 24.12, 12.74, 354.44 ], [ 67.86, 17.82, 108.87 ], [ 36.01, 16.00, 159.99 ]] sum([1..100]) → 5050 |
trans
(transpose) function. For a matrix, it exchanges the rows and columns. For a tensor, the row become the columns, the columns become the planes, and the planes become the rows. It's undefined for any other inputs.inv
(inverse) function. Applied to a square, non-singular matrix, it attempts to invert it. Applied to a non-square matrix, it attempts to construct the pseudo-inverse. Applied to a list of matrices, it acts on each one. Applied to a vector of numbers, it returns the Moore-Penrose pseudo inverse. Applied to a list of strings, it reverses each one. Applied to a single nonzero number, it returns the reciprocal (multiplicative inverse). Applied to a string, it returns the reverse. Applied to a boolean, it returns the negation. Applied to an JSON object, it attempts to exchange keys and values, if the object is 1-to-1. In the future, we may implement a table with function inverses, so it will be possible to invert a function.
/* Input */ fix(inv A cross A,0) |
/* Output */ fix(inv(A) × A,0) → [[ 1, -0, -0 ], [ 0, 1, 0 ], [ -0, -0, 1 ]] |
The negative zeros are due to JavaScript rounding errors.
rot
(rotate) function. rot
applied to a matrix reverses the FIRST axis, i.e., the order of the rows. Applied to a 3rd order tensor, it reverses the first axis, i.e., the planes; in other words, applied to a list of matrices, it reverses the order of the matrices. rot
applied to a string or a list of strings performs rot13.
/* Input */ rot A |
/* Output */ rot(A) → [[ 18, 8, 80 ], [ 34, 9, 54 ], [ 12, 6, 177 ]] |
If you want to see the Seven Words You Can Never Say on Television, evaluate the following NSFW expression:
rot["fuvg", "cvff", "shpx", "phag", "pbpxfhpxre", "zbgureshpxre", "gvgf"]
rev
(reverse) function. rev
applied to a vector, matrix or list of matrices, reverses the LAST axis, i.e., the columns. Applied to a string, it reverses the string. Applied to a number it returns the additive inverse. Applied to a boolean it returns the negation. Applied to a JSON object, it inverts the keys and values, if its 1-to-1.
/* Input */ rev[1..10] rev A rev("A man, a plan, a canal: Panama") |
/* Output */ rev([1..10]) → [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] rev(A) → [[ 177, 6, 12 ], [ 54, 9, 34 ], [ 80, 8, 18 ]] rev("A man, a plan, a canal: Panama") → "amanaP :lanac a ,nalp a ,nam A" |
outer(function_name, list_1, list_2)
(outer product) function.outer
: plus
, minus
, times
, div
, mod
, pow
. E.g., outer(times, [1..12], [1..12])
constructs a multiplication table.
/* Input */ outer(times,[1..12],[1..12]) |
/* Output */ outer(times,[1..12],[1..12]) → [[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ], [ 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24 ], [ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36 ], [ 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48 ], [ 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60 ], [ 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72 ], [ 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84 ], [ 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96 ], [ 9, 18, 27, 36, 45, 54, 63, 72, 81, 90, 99, 108 ], [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120 ], [ 11, 22, 33, 44, 55, 66, 77, 88, 99, 110, 121, 132 ], [ 12, 24, 36, 48, 60, 72, 84, 96, 108, 120, 132, 144 ]] |
inner(f,g,vector_1,vector_2)
. TMTOWTDI: Here are three ways to compute a dot product. All three yield a vlaue of 3:
/* Input */ [1,3,-5].[4,-2,-1] /* most concise way */ [1,3,-5] dot [4,-2,-1] inner(plus,times,[1,3,-5],[4,-2,-1]) /* most verbose, but the most powerful */ |
and2
, or2
, gcd2
, lcm2
, max2
, min2
. E.g., min
takes a list and returns it's minimal element, whereas min2
takes 2 lists and return a list containing the pairwise minimal elements. These functions should be used with outer
and inner
as opposed to the 1-argument versions, e.g., outer(lcm2, [1..12], [1..12])
constructs a table of LCMs for the numbers 1 to 12.round
takes optional 2nd argument, like fix
, but fix
adds extra zeros, but round
won't. E.g.,
/* Input */ fix[2,2.1,2.11,2.111] round[2,2.1,2.11,2.111] |
/* Output */ fix([2, 2.1, 2.11, 2.111]) → [2.00, 2.10, 2.11, 2.11] round([2, 2.1, 2.11, 2.111]) → [2, 2.1, 2.11, 2.11] |
/* Input */ table(round(prob(3d6),3)) |
/* Output */ table (round(prob (3d6),3)) → key value 3 0.005 4 0.014 5 0.028 6 0.046 7 0.069 8 0.097 9 0.116 10 0.125 11 0.125 12 0.116 13 0.097 14 0.069 15 0.046 16 0.028 17 0.014 18 0.005 |
norm(vector, "minkowski")
to compute the Minkowski "norm" of a vectornegfirst(vector)
function to change the sign of the first component of a vectorpair
function to pair up arguments. Signature may be (scalar, scalar)
, (scalar, array)
, (array, scalar)
or (array, array)
.apply
function. In conjunction with outer
, apply
makes it possible to apply a whole list of functions to a list of arguments to get a matrix of values. In the following example, we apply the three main trigonometric functions to a handful of special values. We get a matrix of decimal approximations. Then we use the approx
comparison to test if we know the exact trigonometric constants corresponding to these values.
/* Input */ fix(outer(apply,[sin, cos, tan],[0, π/6, π/4, π/3, π/2]),4) outer(apply,[sin, cos, tan],[0, π/6, π/4, π/3, π/2]) approx [[0,1/2,√2/2,√3/2,1],[1,√3/2,√2/2,1/2,0],[0,1/√3,1,√3,anyfinitenumber]] |
/* Output */ fix(outer(apply,[sin, cos, tan],[0, π / 6, π / 4, π / 3, π / 2]),4) → [[ 0.0000, 0.5000, 0.7071, 0.8660, 1.0000 ], [ 1.0000, 0.8660, 0.7071, 0.5000, 0.0000 ], [ 0.0000, 0.5774, 1.0000, 1.7321, 16331778728383844.0000 ]] outer(apply,[sin, cos, tan],[0, π / 6, π / 4, π / 3, π / 2]) ≈ [[0, 1 / 2, √2 / 2, √3 / 2, 1], [1, √3 / 2, √2 / 2, 1 / 2, 0], [0, 1 / √3, 1, √3, anyfinitenumber]] → [[ true, true, true, true, true ], [ true, true, true, true, true ], [ true, true, true, true, false ]] |
resize
function to reshape a scalar or vector into a matrix, e.g., reshape([3,3],0)
makes a 3x3 matrix of zeros, reshape([4,4],[1,0,0,0,0])
makes a 4x4 identity matrix. The following code will make a 10x10 identity matrix:
/* Input */ resize([10, 10],[1] ⊎ (10 # 0)) |
/* Output */ resize([10, 10],[1] ⊎ (10 # 0)) → [[ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 ]] |
splice(array,position,howmanytoremove,values...)
function to modify array or matrix. Unlike JavaScript's splice function, this one is non-destructive and returns the resulting structure rather than the removed values.
/* Input */ myFish gets ["angel", "clown", "mandarin", "surgeon"] splice(myFish, 2, 0, "drum") /* Make a new array with the drum fish added after the second ("clown") fish */ splice(resize([3, 2],0),1,1,[1,1]) /* Create a new matrix with 0s on the first and third row and 1s sandwiched between */ |
/* Output */ myFish ← ["angel", "clown", "mandarin", "surgeon"] splice(myFish,2,0,"drum") → ["angel", "clown", "drum", "mandarin", "surgeon"] splice(resize([3, 2],0),1,1,[1, 1]) → [[ 0, 0 ], [ 1, 1 ], [ 0, 0 ]] |
DdEeFfKkXxZz
as long as they are at least two letters long, and the second letter isn't a digit or an "F" or "f". That's so we can properly interpret input like d6
anddF
as dice throws rather than variables.
/* Input */ /* Corrected linear regression example */ $x gets [1,2,3,4,5] /* independent data: array */ $y gets [1,2,1.3,3.75,2.25] /* dependent data: array */ n gets count $y /* n: sample size */ p gets 2 /* p: number of predictors */ ν gets n-p /* nu: degrees of freedom */ y gets trans [$y] /* n x 1 matrix */ ones gets n # 1 $X gets trans [ones, $x] /* design matrix: n x p matrix */ y; $X /* show what we have so far */ /* y = $X cross β */ /* Therefore inv $X cross y = inv $X cross $X cross β = β */ /* N.B. No need to construct a pseudo-inverse anymore: this is done automatically */ β gets inv $X cross y; β /* response coefficients: p x 1 matrix */ /* break out the slope and intercept then show them */ intercept gets β after 0 after 0; intercept slope gets β after 1 after 0; slope ŷ gets $X cross β; ŷ /* fitted values */ residuals gets ŷ - y; residuals /* residuals from the regression */ /* ss: OLS estimate for the variance in the error term */ ss gets ((trans residuals cross residuals) after 0 after 0)/ν; ss /* standard error of the regression (SER) AKA standard error of the equation (SEE) */ sqrt(ss) |
/* Output */ $x ← [1, 2, 3, 4, 5] $y ← [1, 2, 1.3, 3.75, 2.25] n ← count $y p ← 2 ν ← n - p y ← trans([$y]) ones ← n # 1 $X ← trans([ones, $x]) y → [[ 1 ], [ 2 ], [ 1.3 ], [ 3.75 ], [ 2.25 ]] $X → [[ 1, 1 ], [ 1, 2 ], [ 1, 3 ], [ 1, 4 ], [ 1, 5 ]] β ← inv($X) × y β → [[ 0.7849999999999988 ], [ 0.42500000000000027 ]] intercept ← β ∘ 0 ∘ 0 intercept → 0.7849999999999988 slope ← β ∘ 1 ∘ 0 slope → 0.42500000000000027 ŷ ← $X × β ŷ → [[ 1.209999999999999 ], [ 1.6349999999999993 ], [ 2.0599999999999996 ], [ 2.485 ], [ 2.91 ]] residuals ← ŷ - y residuals → [[ 0.20999999999999908 ], [ -0.36500000000000066 ], [ 0.7599999999999996 ], [ -1.2650000000000001 ], [ 0.6600000000000001 ]] ss ← ((trans(residuals) × residuals) ∘ 0 ∘ 0) / ν ss → 0.93025 sqrt(ss) → 0.9644946863513557 |
while(a){b;c;d}
to display as while(a){b,c,d}
with commas instead of semicolons.recur
operator to generate recurrences. E.g., recur (_1+_2),[0,1]
generates the Fibonacci sequence to ∞ which is set to 100 by default. The format is recur expr, initial
where expr
is an expression in parens. This expression may contain variables of the form _k
which get set to result[n-k]
where n
is the number of results accumulated so far. And initial
is the initial value of the result array. The expr
has to be an actual expression -- it can't be a variable holding an expression, but this constraint may disappear in a future release. See below for more examples.sqrt
function. This is different from root
which is a dyadic operator rather than a function, e.g., 3 root 10
produces the cube root of 10. In general, functions need parens, operators don't. But the sqrt
function is threadable over vectors/matrices/tensors, and parens are optional around things enclosed in square brackets, e.g., sqrt [0..4]
is OK. TMTOWTDI: sqrt(2)
produces the same result as 2 root 2
and √2
and 2^.5
cot
, coth
, csc
, csch
, sec
, sech
sign
function to return the sign of a number: 1, 0 or -1.fix(x,n)
function to display decimal numbers x with a fixed number n of decimal places. The number n defaults to 2. This function is threadable, so parens are optional around vectors and matrices as long as the default number of decimal places is used.size
function to return the size of its input: 0 means its a scalar; a positive number n
means its a vector of length n; and an array [m,n]
means its a matrix with m
rows and n
columns. Additionally, -1 indicates its a JSON object; -2 indicates its a JavaScript function; and -3 indicates its type is unknown.tr
. Input is a square matrix. Output is sum of diagonal elements. This is a non-threadable function--at most one input which must be in parens.norm
function to return the p-norm of a vector or matrix. By default, p is 2. But see the detailed notes below.For vectors, norm([3,4])
is the same as norm([3,4],2)
, i.e., the Euclidean norm. In general, norm([a,b,...,c],p)
is (abs(a)^p + abs(b)^p + ... + abs(c)^p)^(1/p)
. A special case is norm(v,∞)
or norm(v,inf)
which returns the max-norm of the given vector or matrix: norm([a,b,...,c],∞)
is max [abs(a), abs(b), ..., abs(c)]
. Note that ∞
is usually set to 100, so norm(v,100)
would actually give the max-norm norm(v,∞)
, which is the limiting value of the p-norm as p goes to infinity. Another special case the 0-"norm", which returns the number of non-zero elements in the structure.
For matrices:
norm(m,"max")
returns the element of m
with the maximum absolute value.Note that norm
doesn't thread over its arguments and that parens are required: norm([3,4])
is OK, but norm [3,4]
is a syntax error. That's to eliminate ambiguity over what to do with expressions like norm [[1,2],[3,4]]
-- is norm
supposed to thread over two vectors or one matrix?
Here are some examples of the new features.
Take the square roots of 0 to 10:
Dice> sqrt [0..10]
sqrt([0..10]) → [0, 1, 1.4142135623730951, 1.7320508075688772, 2, 2.23606797749979, 2.449489742783178, 2.6457513110645907, 2.8284271247461903, 3, 3.1622776601683795]
Fix the vector of square roots above to 4 decimal places:
Dice> fix(sqrt [0..10],4)
fix(sqrt([0..10]),4) → [0.0000, 1.0000, 1.4142, 1.7321, 2.0000, 2.2361, 2.4495, 2.6458, 2.8284, 3.0000, 3.1623]
Fix pi to the default 2 decimal places:
Dice> fix(pi)
fix(π) → 3.14
Roll d6-d6 ten times and take the signs of the results. Save the results in vector v
:
Dice> v<:sign(10#d6-d6)
v ↜ sign(10 # d6 - d6) → [1, 0, 1, -1, 1, 0, 1, -1, 0, -1]
Now count the non-zero elements in v using the 0-norm:
Dice> norm(v,0)
norm(v,0) → 7
Take the Euclidean-norm (2-norm) of the vector [3,4]
:
Dice> norm([3,4])
norm([3, 4]) → 5
Find the trace of the given matrix:
Dice> m<:[[1,0,2],[0,3,4],[5,-12,0]]
m ↜ [[1, 0, 2], [0, 3, 4], [5, -12, 0]] → [[1, 0, 2], [0, 3, 4], [5, -12, 0]]
Dice> tr(m)
tr(m) → 4
Use matrix norms to find the non-zero count, maximum absolute column sum, maximum absolute row sum, and maximum absolute element of the same matrix m
:
Dice> norm(m,0) /* non-zero count */
norm(m,0) → 6
Dice> norm(m,1) /* maximum absolute column sum */
norm(m,1) → 15
Dice> norm(m,inf) /* maximum absolute row sum */
norm(m,∞) → 17
Dice> norm(m,"max") /* maximum absolute element */
norm(m,"max") → 12
Generate the first ∞=100 elements of the Fibonacci sequence:
Dice> recur (_1+_2), [0,1]
recur (_1 + _2), [0, 1] → [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986, 102334155, 165580141, 267914296, 433494437, 701408733, 1134903170, 1836311903, 2971215073, 4807526976, 7778742049, 12586269025, 20365011074, 32951280099, 53316291173, 86267571272, 139583862445, 225851433717, 365435296162, 591286729879, 956722026041, 1548008755920, 2504730781961, 4052739537881, 6557470319842, 10610209857723, 17167680177565, 27777890035288, 44945570212853, 72723460248141, 117669030460994, 190392490709135, 308061521170129, 498454011879264, 806515533049393, 1304969544928657, 2111485077978050, 3416454622906707, 5527939700884757, 8944394323791464, 14472334024676220, 23416728348467684, 37889062373143900, 61305790721611580, 99194853094755490, 160500643816367070, 259695496911122560, 420196140727489660, 679891637638612200, 1100087778366101900, 1779979416004714000, 2880067194370816000, 4660046610375530000, 7540113804746346000, 12200160415121877000, 19740274219868226000, 31940434634990100000, 51680708854858330000, 83621143489848430000, 135301852344706760000, 218922995834555200000]
Approximate the square root of 2. Note that by the fifth iteration this sequence converges to the same decimal approximation produced by JavaScript's Math.sqrt(2)
function call:
Dice> recur ((1/2)*(_1+2/_1)), [1.5]
recur ((1 / 2) * (_1 + 2 / _1)), [1.5] → [1.5, 1.4166666666666665, 1.4142156862745097, 1.4142135623746899, 1.414213562373095, ..., 1.414213562373095]
JavaScript's builtin Math.random()
function isn't seedable, so it can't produce a repeatable pseudorandom sequence that is completely predictable. Therefore, we incorporated David Bau's seedrandom.js Javascript module to produce a determinstic sequence of pseudorandom numbers. It replaces Math.random()
and adds a Math.seedrandom()
function that allows us to set a random seed.
The Palamedes UI now has a seed
variable to track the last value passed to Math.seedrandom()
and a seed()
function that allows us to pass a value to Math.seedrandom()
. Here is a sample script that shows how to use this new functionality:
seed("Palamedes") /* Call the seed() function to set seed to "Palamedes" */
10 # d6 /* Generate some dice throws */
10 # d6
seed /* seed variable saved the last seed value */
seed(seed) /* reseed with the same value */
10 # d6 /* now running same dice throws produces same results */
10 # d6
The output from this script should look like this:
seed("Palamedes") → Ok
10 # d6 → [1, 3, 6, 3, 2, 2, 2, 2, 4, 2]
10 # d6 → [6, 6, 4, 6, 6, 5, 4, 3, 4, 1]
seed → "Palamedes"
seed(seed) → Ok
10 # d6 → [1, 3, 6, 3, 2, 2, 2, 2, 4, 2]
10 # d6 → [6, 6, 4, 6, 6, 5, 4, 3, 4, 1]
Calling seed()
without a seed value will reseed the PRNG with a new seed value constructed from the date and time. You can see this value by inspecting the seed
variable. You should see something like this:
seed → Ãï±.â!¿Ö³y\-ðN9âún©½×p¶SC2ç¥(}¶D[XgÇôÆ«¾aý¡v®ö ÑBº¤_ÂØÔ6p¹2qò9V¶&#É|U~k\!ï°`MwR7m@LmÅíîÐ8QÌÑj½»Móýßs&fÒU×O0ÑåÆå:gGzWûÍnÿÄßÓHpè®\=Ísä!¬«Öñà0ìè"¼âxeeS^ILVG`+Ûí±HÐ\ß<ygáÖð"¾=EFFº&Bõ7Ááÿs¡á(¦dÚ&n`VP×ÅrÞ
If you aren't interested in predictable/repeateable pseudorandom number sequences, Don't Worry! By default, Palamedes autoseeds the PRNG, and you never have to use any of this new functionality!!! This functionality only exists for people who need to run the same set of experiments over and over again and get the same results.
-2
is parsed as -2
rather than -1 * 2
. The trade-off is that you must write Level - 1
with whitespace around the minus symbol, rather than Level-1
or you get a syntax error. 2 - -2
(=4) and 2 + -2
(=0) are syntactically OK. This change affects the D&D Men Attacking example, where the minus had been written without whilespace. In the future, I may change this so that 2-2
is again valid subtraction and ¯2
becomes the unary minus symbol as in 2-¯2
(=4).abs
, ln
and floor
threading over arrays. So the expression abs [-0.1,.5,-.5,1,-2,3,-4]
yields [0.1, 0.5, 0.5, 1, 2, 3, 4]
instead of []
abs(-0.1)
is OK, but abs -0.1
is a syntax error. No parens necessary if threading over an array, so abs [-0.1,.5,-.5,1,-2,3,-4]
is OK. The functions that require parens are: U
, N
, Φ
, Phi
, probit
, abs
, acos
, acosh
, asin
, asinh
, atan
, atanh
, atan2
, ceil
, comb
, cos
, cosh
, exp
, floor
, ln
, log
, perm
, round
, sin
, sinh
, tan
, tanh
. This change affects the tali example, which used the comb
function without parens.U
, N
, Φ
, Phi
, probit
, abs
, acos
, acosh
, asin
, asinh
, atan
, atanh
, atan2
, ceil
, comb
, cos
, cosh
, exp
, floor
, ln
, log
, perm
, round
, sin
, sinh
, tan
, tanh
U
function to generate random variates from a continuous uniform distribution over given range. U()
defaults to U(0,1)
Phi
(or Φ
) function: Cumulative distribution function for standard normal distribution.probit
function: quantile function (or inverse cumulative distribution function) for standard normal distribution.N
function to generate random variates from a normal distribution. N()
defaults to the standard normal distribution N(0,1)
. Otherwise N(mu,sigma)
defaults to a normal distribution with mean mu
and standard deviation sigma
.The following code computes stats on a random sample of 1000 IQ scores:
Dice> stats(1000#floor(N(100,15)))
stats (1000 # floor (N(100,15))) → {"min":55,"max":142,"mean":99.305,"median":100,"mode":["101"],"sd":14.899260483761038,"count":1000}
U
, N
, Φ
, etc. are distinct from the variables U
, N
, Φ
, etc.This allows you to write:
Dice> N ← 5; N!; N(100,15); 2*N
N ← 5
N! → 120
N(100,15) → 134.2878484013724
2 * N → 10
log
function: base 10 logarithm. ln
is still base e
logarithmexp(x)
function as alternative to e^x
cos
, sin
, tan
. Added their inverses: acos
, asin
, atan
cosh
, sinh
, tanh
. Added their inverses: acosh
, asinh
, atanh
ceil
and round
functions. These are JavaScript implementations. May add alternative rounding functions in the future.inf
(or Inf
or INF
or ∞
) special variable used as upper limit for operations that compute a sequence, series or limit.$
. Question marks ?
may be used except in the initial position.Here are some examples:
Dice> $x ← 10; $X$ ← 100; A?B ← 50; C? ← 5
$x ← 10
$X$ ← 100
A?B ← 50
C? ← 5
domain
function that caused it to fail on singleton objects. This made the knucklebones example fail.union
in a way that breaks backward compatibility. See below for details.concat
, powerset
and permute
array operators.intersect
, diff
(difference) and sdiff
(symmetric difference), respectively, ∩, \, and Δ.subset
/⊂ and subseteq
/⊆.all
/∀ and some
/∃ quantifiers that apply to arrays or matrices of boolean values.approx
/≈ equality comparison and the tol
variable which stores the tolerance to use to check approximations.prod
/Π operator to take the cartesian product of an array of arrays. Note that Π is uppercase pi, not to be confused with lowercase pi
/π which is the constant 3.141592653589793lcm
and gcd
functions. These each operate on an array of integers.Concat
does array concatenation. It uses the mathematical symbol for multiset union ⊎. It does not work the same as union, since order and duplicates matter:
Dice> [1,2,3] concat [3,3,1,4,5] /* array concatenation */
[1, 2, 3] ⊎ [3, 3, 1, 4, 5] → [1, 2, 3, 3, 3, 1, 4, 5]
Union
, intersect
, diff
and sdiff
work on mathematical sets, i.e., order and duplicates don't matter!
Dice> [1,2,3] union [3,3,1,4,5] /* set union */
[1, 2, 3] ∪ [3, 3, 1, 4, 5] → [1, 2, 3, 4, 5]
Dice> [1,2,3] intersect [3,3,1,4,5] /* set intersection */
[1, 2, 3] ∩ [3, 3, 1, 4, 5] → [3, 1]
Dice> [1,2,3] diff [3,3,1,4,5] /* set difference */
[1, 2, 3] \ [3, 3, 1, 4, 5] → [2]
Dice> [1,2,3] sdiff [3,3,1,4,5] /* set symmetric difference */
[1, 2, 3] Δ [3, 3, 1, 4, 5] → [2, 4, 5]
BEWARE! This new definition of union is not backward compatible with previous builds. To make old code work, you may need to change x union y
to x concat y
; further you may need to cast x or y as arrays; and you may need to flatten the result with the flatten operator, e.g., flatten ([x] concat [y])
Infix set and array operators now have precedence between relational expressions and additive expressions. No more are parentheses needed to string them together:
Dice> [1,2] union [3,4] union [5,6]
[1, 2] ∪ [3, 4] ∪ [5, 6] → [1, 2, 3, 4, 5, 6]
No more parens needed to access elements of matrices:
Dice> [[1,2],[3,4]] of 0 of 1
[[1, 2], [3, 4]] ∘ 0 ∘ 1 → 2
Dice> [[1,2],[3,4]] of 0 of 0
[[1, 2], [3, 4]] ∘ 0 ∘ 0 → 1
Dice> [[1,2],[3,4]] of 1 of 0
[[1, 2], [3, 4]] ∘ 1 ∘ 0 → 3
Dice> [[1,2],[3,4]] of 1 of 1
[[1, 2], [3, 4]] ∘ 1 ∘ 1 → 4
Subset
/⊂ and subseteq
/⊆ have same precedence as the relational operators >, <, ≤, ≥
Dice> [1,2,3] subset [3,2,3,1,4,5]
[1, 2, 3] ⊂ [3, 2, 3, 1, 4, 5] → true
Dice> [1,2,3,10] subset [3,2,3,1,4,5] /* fails b/c 10 not in RHS */
[1, 2, 3, 10] ⊂ [3, 2, 3, 1, 4, 5] → false
Dice> [1,2,3] subset [3,2,3,1] /* fails b/c LHS not a proper subset of RHS */
[1, 2, 3] ⊂ [3, 2, 3, 1] → false
Dice> [1,2,3] subseteq [3,2,3,1] /* OK even tho' LHS not a proper subset of RHS */
[1, 2, 3] ⊆ [3, 2, 3, 1] → true
Powerset
finds the powerset of the given array...
Dice> powerset [1,2,3]
powerset [1, 2, 3] → [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
Permute
computes all permutations of the given array...
Dice> permute [1,2,3]
permute [1, 2, 3] → [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
Applying a logical relation between two matrices returns a matrix of booleans. To reduce this matrix of booleans to a single boolean value, use the new all
and some
quantifiers:
Dice> A gets [[3,2],[-1,0]]
A ← [[3, 2], [-1 * 1, 0]]
Dice> B gets [[-2,3],[4,2]]
B ← [[-1 * 2, 3], [4, 2]]
Dice> C gets [[-1,5],[1,2]]
C ← [[-1 * 1, 5], [1, 2]]
Dice> R gets [[11,36],[-5,4]]
R ← [[11, 36], [-1 * 5, 4]]
Dice> S gets [[11,35],[-5,4]]
S ← [[11, 35], [-1 * 5, 4]]
Dice> T gets [[10,35],[-3,7]]
T ← [[10, 35], [-1 * 3, 7]]
Dice> A cross B cross C
A × B × C → [[11, 36], [-5, 4]]
Dice> A cross B cross C = R
A × B × C = R → [[true, true], [true, true]]
Dice> A cross B cross C = S
A × B × C = S → [[true, false], [true, true]]
Dice> A cross B cross C = T
A × B × C = T → [[false, false], [false, false]]
Dice> all (A cross B cross C = R)
∀ (A × B × C = R) → true
Dice> all (A cross B cross C = S)
∀ (A × B × C = S) → false
Dice> some (A cross B cross C = S)
∃ (A × B × C = S) → true
Dice> some (A cross B cross C = T)
∃ (A × B × C = T) → false
Here's an example of the approx
equality and the use of the tol
variable, which is a ten-thousandth by default. You may set tol
bigger or smaller to suit your needs:
Dice> tol
tol → 0.0001
Dice> 1 approx 1.0001
1 ≈ 1.0001 → true
Dice> 1 approx 1.001
1 ≈ 1.001 → false
Dice> tol gets 1 /* make the tolerance bigger */
tol ← 1
Dice> 1 approx 1.99999999999999
1 ≈ 1.99999999999999 → true
Dice> 1 approx 2
1 ≈ 2 → false
Though the cross
operator is overloaded to take a cartesian product of its arguments (as long as they're not matrices or 3-vectors), this is only good for taking the cross product of two arrays. To take a cross product of more than two arrays, use the new prod
/Π operator. This example demonstrates how to make tuples for a 3-level outline. Note that prod
takes an array of arrays as its argument:
Dice> prod [["I","II","III"],["A","B","C"],[1,2,3]]
Π [["I", "II", "III"], ["A", "B", "C"], [1, 2, 3]] → [["I", "A", 1], ["I", "A", 2], ["I", "A", 3], ["I", "B", 1], ["I", "B", 2], ["I", "B", 3], ["I", "C", 1], ["I", "C", 2], ["I", "C", 3], ["II", "A", 1], ["II", "A", 2], ["II", "A", 3], ["II", "B", 1], ["II", "B", 2], ["II", "B", 3], ["II", "C", 1], ["II", "C", 2], ["II", "C", 3], ["III", "A", 1], ["III", "A", 2], ["III", "A", 3], ["III", "B", 1], ["III", "B", 2], ["III", "B", 3], ["III", "C", 1], ["III", "C", 2], ["III", "C", 3]]
Here are examples of gcd
and lcm
:
Dice> gcd [57,0,-45,-18,90,447]
gcd [57, 0, -1 * 45, -1 * 18, 90, 447] → 3
Dice> lcm [-50,25,-45,-18,90,447]
lcm [-1 * 50, 25, -1 * 45, -1 * 18, 90, 447] → 67050
cross
or × function. Note that × is not the letter "eks" x. Overloaded cross
to handle:trans
function to transpose an array or matrixdet
function to calculate determinant of matrixinv
function to do matrix inversematrix op matrix
matrix op array
matrix op scalar
You can now intersperse C-style comments in your code. They'll be ignored by the parser:
Dice> /* hi there */
Dice> 1 + /* hi there */ 1
1 + 1 → 2
Dice> [1, /* two */ 2, 3]
[1, 2, 3] → [1, 2, 3]
Here's a different way to simulate a deck of 52 cards, shuffling, and dealing out 5. This time, a "card" is a pair (i.e., a two-element array) whose first component is the rank and whose second is the suit:
Dice> ranks ← [2..10] ∪ ["J", "Q", "K", "A"]
ranks ← [2..10] ∪ ["J", "Q", "K", "A"]
Dice> suits ← ["♠", "♡", "♢", "♣"]
suits ← ["♠", "♡", "♢", "♣"]
Dice> cards ← ranks × suits
cards ← ranks × suits
Dice> count cards
count cards → 52
Dice> shuffle cards
shuffle cards → [["Q", "♡"], [4, "♣"], [10, "♣"], [8, "♣"], [2, "♣"], [4, "♢"], ["A", "♢"], [9, "♡"], [6, "♢"], [7, "♡"], [6, "♡"], [10, "♢"], ["J", "♡"], ["A", "♣"], ["Q", "♢"], [4, "♠"], [4, "♡"], ["J", "♣"], [7, "♢"], [3, "♢"], ["K", "♡"], [5, "♡"], [8, "♡"], [7, "♠"], [9, "♢"], [3, "♠"], [5, "♠"], [8, "♠"], ["J", "♢"], [9, "♠"], [9, "♣"], [10, "♠"], ["Q", "♠"], [2, "♠"], [8, "♢"], ["A", "♠"], [2, "♢"], [5, "♣"], [6, "♣"], ["A", "♡"], ["K", "♠"], [6, "♠"], ["K", "♣"], [5, "♢"], ["J", "♠"], [10, "♡"], ["Q", "♣"], [2, "♡"], [3, "♡"], ["K", "♢"], [3, "♣"], [7, "♣"]]
Dice> take (shuffle cards), 5
take (shuffle cards), 5 → [[2, "♡"], [10, "♢"], [3, "♡"], [10, "♠"], [4, "♡"]]
Here's a 1-line constructor for a Pinochle deck:
pinochle <- (flatten(2#[9,10,"J","Q","K","A"]))×["♠", "♡", "♢", "♣"]
If a and b are 3-D vectors, then a × b is a vector that is perpendicular to a and b, whose direction is determined by the right-hand-rule, and whose magnitude is the area of the parallelogram spanned by a and b.
Dice> a gets [3,-3,1]; b gets [-12,12,-4]
a ← [3, -1 * 3, 1]
b ← [-1 * 12, 12, -1 * 4]
Dice> a cross b
a × b → [0, 0, 0]
Dice> b gets [4,9,2]
b ← [4, 9, 2]
Dice> a cross b
a × b → [-15, -2, 39]
Dice> √((a × b) . (a × b)) /* magnitude of a × b */
√((a × b) . (a × b)) → 41.83300132670378
Dice> √((a × b) . (a × b)) = 5*root 70
√((a × b) . (a × b)) = 5 * √70 → true
Dice> trans [[1,2,3],[4,5,6],[7,8,9]]
trans [[1, 2, 3], [4, 5, 6], [7, 8, 9]] → [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Dice> trans [1,2,3]
trans [1, 2, 3] → [[1], [2], [3]]
Dice> [[1, 2, 3], [4, 5, 6]] × [[7,8],[9,10],[11,12]]
[[1, 2, 3], [4, 5, 6]] × [[7, 8], [9, 10], [11, 12]] → [[58, 64], [139, 154]]
Dice> I ← [[1,0,0],[0,1,0],[0,0,1]]
I ← [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
Dice> I × [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
I × [[1, 2, 3], [4, 5, 6], [7, 8, 9]] → [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Dice> [[1, 2, 3], [4, 5, 6], [7, 8, 9]] × (trans [1,2,3])
[[1, 2, 3], [4, 5, 6], [7, 8, 9]] × (trans [1, 2, 3]) → [[14], [32], [50]]
Dice> inv [[1,2],[3,4]]
inv [[1, 2], [3, 4]] → [[-2, 1], [1.5, -0.5]]
Dice> (inv [[1,2],[3,4]]) cross [[1,2],[3,4]]
(inv [[1, 2], [3, 4]]) × [[1, 2], [3, 4]] → [[1, 0], [0, 1]]
Dice> det [[1,2],[3,4]]
det [[1, 2], [3, 4]] → -2
Solve the system A × _x = b, where A and b are given as follows:
Dice> A← [[2,-3],[4,1]]; b← trans[-2, 24]; _x← (inv A)×b; _x
A ← [[2, -1 * 3], [4, 1]]
b ← trans [-1 * 2, 24]
_x ← (inv A) × b
_x → [[4.999999999999999], [4]]
Check that the solution works (up to JavaScript rounding errors):
Dice> A × _x
A × _x → [[-2.0000000000000018], [23.999999999999996]]
We want to do a simple linear regression using the following model:
_Y = slope * _x + intercept
= _X * _b
s.t.
_Y = trans [y1, y2, ..., yN]
_b = trans [intercept, slope]
_X = trans [[1, 1, ..., 1] [x1, x2, ..., xN]]
The following sample input...
_x gets [1,2,3,4,5] /* independent data */
_Y gets [1,2,1.3,3.75,2.25] /* dependent data */
_1 gets (count _x) # 1
_X gets trans [_1, _x]
_b gets ((inv ((trans _X) cross _X)) cross (trans _X)) cross (trans _Y)
intercept gets (_b after 0) after 0
slope gets (_b after 1) after 0
slope; intercept
prints out these results...
slope → [0.42500000000000027]
intercept → [0.7849999999999988]
Try these examples...
/* Addition */
1+1 /* scalar + scalar */
1+[1,2,3] /* scalar + array */
[1,2,3] + 1 /* array + scalar */
"a" + [1, 2, 3] /* string + array */
[1, 2, 3] + "a" /* array + string */
[5,10,15]+[1,2,3] /* array + array */
[[5,10,15],[1,2,3]]+[[1,2,3],[5,10,15]] /* matrix + matrix */
1 + [[5,10,15],[1,2,3]] /* scalar + matrix */
[[5,10,15],[1,2,3]] + 1 /* matrix + scalar */
"a" + [[5,10,15],[1,2,3]] /* string + matrix */
[[5,10,15],[1,2,3]] + "a" /* matrix + string */
[[5,10,15],[1,2,3]]+[1,2] /* matrix + array */
[1,2] + [[5,10,15],[1,2,3]] /* array + matrix */
/* Exponentiation */
[[ 1, 0, 0 ],[ 0, 1, 0 ],[ 0, 0, 1 ]]^5
[[1,2],[3,4]]^-1 /* matrix inverse */
([[1, 2], [3, 4]] ^ -1) × [[1, 2], [3, 4]] /* gives the identity*/
[[1,2],[3,4]]^-2 /* matrix inverse, squared */
[[2,0],[0,3]]^[-2,-1,0,1,2,3] /* diagonal matrix to a sequence of powers */
[-1,0,1,2,3]^2 /* sequence squared */
2^[-2,-1,0,1,2,3] /* 2 to a sequence of powers */
[0,1,2,3]^[0,1,2,3] /* element-wise */
[[1,2],[3,4]]^[[1,-2],[-1,4]] /* element-wise */
2^10
Note that × doesn't yet parse as an aritmetic operator like +, so while you can do
1+2+3+4
you can't do
a×b×c×d
Until the grammar is altered, use parentheses when crossing more than two items:
((a×b)×c)×d
Mode works with uni-modal and multi-modal arrays:
Dice> mode [3,1,2,1,3,1]
mode [3, 1, 2, 1, 3, 1] → [1]
Dice> mode [3,1,2,1,3,3,1]
mode [3, 1, 2, 1, 3, 3, 1] → [1, 3]
Dice> mode [1,2,3]
mode [1, 2, 3] → [1, 2, 3]
As well as objects:
Dice> prob(d6+d4)
prob (d6 + d4) → {"2":0.041666666666666664,"3":0.08333333333333333,"4":0.125,"5":0.16666666666666666,"6":0.16666666666666666,"7":0.16666666666666666,"8":0.125,"9":0.08333333333333333,"10":0.041666666666666664}
Dice> mode(prob(d6+d4))
mode (prob (d6 + d4)) → [5, 6, 7]
Dice> mode(prob(3d6))
mode (prob (3d6)) → [10, 11]
Dice> mode(prob(2d6))
mode (prob (2d6)) → [7]
Mode is now included in the stats
function:
stats (3d6) → {"min":3,"max":18,"mean":10.500000000000002,"median":10,"mode":["10","11"],"sd":2.958039891549803,"count":16}
The bug prevented expressions like the following from working, because StatsAST incorrectly guessed that the user wants one random variate (in this case from abs(d6-d6)) rather than the entire distribution.
stats (abs (d6 - d6)) → {"min":0,"max":5,"mean":1.9444444444444446,"median":2,"mode":["1"],"sd":1.4326441064697364,"count":6}
Changed the way table works. For a pmf, decimal points should line up (still have work todo to get decimals to line up for non pmfs):
table (cdf (score)) →
key value
3 0.004629629629629629
4 0.018518518518518517
5 0.046296296296296294
6 0.09259259259259259
7 0.16203703703703703
8 0.25925925925925924
9 0.375
10 0.5
11 0.625
12 0.7407407407407407
13 0.8379629629629629
14 0.9074074074074073
15 0.9537037037037036
16 0.9814814814814814
17 0.9953703703703702
18 0.9999999999999999
Fixed bug preventing tabling when the domain/key consists of strings, e.g., this works
table (stats (abs (d6 - d6))) →
key value
count 6
max 5
mean 1.9444444444444446
median 2
min 0
mode 1
sd 1.4326441064697364
Dice> abs(-12)
abs (-1 * 12) → 12
Dice> abs [-1,5,-3]
abs [-1 * 1, 5, -1 * 3] → [1, 5, 3]
Dice> prob(abs(d6 - d6))
prob (abs (d6 - d6)) → {"0":0.16666666666666669,"1":0.2777777777777778,"2":0.2222222222222222,"3":0.16666666666666666,"4":0.1111111111111111,"5":0.05555555555555555}
Dice> sum(prob(abs(d6 - d6)))
sum (prob (abs (d6 - d6))) → 1
This is useful for displaying a JSON object or an array as a two-column table. Note that each column is 20 chars wide:
Dice> table(prob(abs(d6 - d6)))
table (prob (abs (d6 - d6))) →
key value
0 0.16666666666666669
1 0.2777777777777778
2 0.2222222222222222
3 0.16666666666666666
4 0.1111111111111111
5 0.05555555555555555
Dice> table [1..6]
table [1..6] →
index element
0 1
1 2
2 3
3 4
4 5
5 6
*
operatorOverloaded *
operator for string*number
and number*string
:
Dice> "hello" * 3
"hello" * 3 → "hellohellohello"
Dice> 3 * "hello"
3 * "hello" → "hellohellohello"
Overloaded *
operator: pmf1*pmf2
returns the convolution of pmfs. Here is the pmf for an astragalus:
Dice> pmf <: {"1": 0.1, "3": 0.4, "4": 0.4, "6": 0.1}
pmf ↜ {"1":0.1,"3":0.4,"4":0.4,"6":0.1} → {"1":0.1,"3":0.4,"4":0.4,"6":0.1}
Her is the pmf for two astragali:
Dice> pmf * pmf
pmf * pmf → {"2":0.010000000000000002,"4":0.08000000000000002,"5":0.08000000000000002,"6":0.16000000000000003,"7":0.3400000000000001,"8":0.16000000000000003,"9":0.08000000000000002,"10":0.08000000000000002,"12":0.010000000000000002}
Inverse of a hashmap. Note this will only work if the hashmap is one-to-one:
Dice> inv {"red":1,"blue":2,"green":3}
inv {"red":1,"blue":2,"green":3} → {"1":"red","2":"blue","3":"green"}
Inverse of a string is the reverse of the string:
Dice> inv "hello world!"
inv "hello world!" → "!dlrow olleh"
Inverse of a number is the additive inverse of the number, same as unary minus:
Dice> inv 12
inv 12 → -12
N.B. We need parens here, since unary minus is not a primary expression!
Dice> inv (-12)
inv (-1 * 12) → 12
Inverse of a boolean is logical negation of the boolean, same as '~' operator.
Dice> inv true
inv true → false
Sorting a string converts it to an array and then sorts it alphabetically:
Dice> sort "hello"
sort "hello" → ["e", "h", "l", "l", "o"]
This may be useful for tallying the occurrences letters in the string:
Dice> tally(sort "hello")
tally (sort "hello") → {"e":1,"h":1,"l":2,"o":1}
Sorting a numeric array:
Dice> a <: 10#d%
a ↜ 10 # d% → [56, 10, 10, 9, 68, 66, 39, 16, 87, 74]
Dice> sort a
sort a → [9, 10, 10, 16, 39, 56, 66, 68, 74, 87]
Sorting a number (or a boolean) just converts it to a one-element array:
Dice> sort 3
sort 3 → [3]
Dice> sort true
sort true → [true]
Shuffle is the "inverse" of sort. It "sorts" an array in a random order:
Dice> shuffle [1..10]
shuffle [1..10] → [3, 8, 7, 1, 5, 2, 6, 9, 4, 10]
Here is a more elaborate example that makes a deck of 52 cards, shuffles them, and then deals out the first 5:
Dice> ranks <- [2..10] union ["J","Q","K","A"]
ranks ← [2..10] ∪ ["J", "Q", "K", "A"]
Dice> suits <- ["♠", "♡", "♢", "♣"]
suits ← ["♠", "♡", "♢", "♣"]
Dice> concat<- ranks+s
concat ← ranks + s
cards ← flatten (map concat, s, suits)
Dice> cards
cards → ["2♠", "3♠", "4♠", "5♠", "6♠", "7♠", "8♠", "9♠", "10♠", "J♠", "Q♠", "K♠", "A♠", "2♡", "3♡", "4♡", "5♡", "6♡", "7♡", "8♡", "9♡", "10♡", "J♡", "Q♡", "K♡", "A♡", "2♢", "3♢", "4♢", "5♢", "6♢", "7♢", "8♢", "9♢", "10♢", "J♢", "Q♢", "K♢", "A♢", "2♣", "3♣", "4♣", "5♣", "6♣", "7♣", "8♣", "9♣", "10♣", "J♣", "Q♣", "K♣", "A♣"]
Dice> take (shuffle cards), 5
take (shuffle cards), 5 → ["8♠", "4♡", "6♠", "7♡", "2♣"]
Last update: Fri Sep 23 2016