Move stuff

This commit is contained in:
Richard Feldman
2018-08-05 04:13:33 -04:00
parent bf20622319
commit 7793c69762
3419 changed files with 6 additions and 7 deletions

View File

@@ -0,0 +1 @@
elm-stuff

View File

@@ -0,0 +1,30 @@
Copyright (c) 2014, Max Goldstein
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Max Goldstein nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,62 @@
# Format String for Elm
by Max Goldstein
Create format strings for dates in the Elm programming language.
## Documentation
The module `Date.Format` exports `format : String -> Date.Date -> String`.
The `Date` refers to Elm's standard [Date library](http://package.elm-lang.org/packages/elm-lang/core/latest/Date).
The input `String` may contain any of the following directives, which will be expanded to parts of the date.
A directive consists of a percent (%) character, zero or more flags and a conversion specifier as follows.
```
%<flags><conversion>
```
Flags:
* `-` - don't pad a numerical output
* `_` - use spaces for padding
* `0` - use zeros for padding
Format directives:
* `%Y` - 4 digit year
* `%y` - 2 digit year
* `%m` - Zero-padded month of year, e.g. `"07"` for July
* `%B` - Full month name, e.g. `"July"`
* `%b` - Abbreviated month name, e.g. `"Jul"`
* `%d` - Zero-padded day of month, e.g `"02"`
* `%e` - Space-padded day of month, e.g `" 2"`
* `%a` - Day of week, abbreviated to three letters, e.g. `"Wed"`
* `%A` - Day of week in full, e.g. `"Wednesday"`
* `%H` - Hour of the day, 24-hour clock, zero-padded
* `%k` - Hour of the day, 24-hour clock, space-padded
* `%I` - Hour of the day, 12-hour clock, zero-padded
* `%l` - (lower ell) Hour of the day, 12-hour clock, space-padded
* `%p` - AM or PM
* `%P` - am or pm
* `%M` - Minute of the hour, zero-padded
* `%S` - Second of the minute, zero-padded
* `%L` - Millisecond of the second, zero-padded
* `%%` - literal `%`
## Localization
`Date.Format` also exports `localFormat : Date.Local.Local -> String -> Date.Date -> String`.
This function allows to add a localization record as specified in `Date.Local`.
It can be used to display local terms for week days, months, and AM or PM.
## Contributing
Pull requests are welcome! Note that in addition to adding a new letter to the
massive case statement, you'll also need to add it to the regex. Languages like
[Haskell](http://www.haskell.org/ghc/docs/6.12.3/html/libraries/time-1.1.4/Data-Time-Format.html),
[Python](https://docs.python.org/2/library/datetime.html#strftime-strptime-behavior),
and [Ruby](http://apidock.com/ruby/DateTime/strftime) have very comprehensive
format strings. (Luckily, they seem to agree on the encoding, which you should
follow.) I've tried to add the most common formats, but if you want one added,
send a PR (and add a passing test). To run the tests, run `elm test` (which you
can install from the [elm-test](https://github.com/elm-community/elm-test) package).

View File

@@ -0,0 +1,18 @@
{
"version": "1.4.2",
"summary": "Format dates with ease",
"repository": "https://github.com/mgold/elm-date-format.git",
"license": "BSD3",
"source-directories": [
"src"
],
"exposed-modules": [
"Date.Format",
"Date.Local",
"Time.Format"
],
"dependencies": {
"elm-lang/core": "5.0.0 <= v < 6.0.0"
},
"elm-version": "0.18.0 <= v < 0.19.0"
}

View File

@@ -0,0 +1,274 @@
module Date.Format exposing (format, formatISO8601, localFormat)
{-| Format strings for dates.
@docs format, localFormat, formatISO8601
-}
import Date
import Date.Local exposing (Local, international)
import List exposing (head, tail)
import Maybe exposing (andThen, withDefault)
import Regex
import String exposing (padLeft, right, toUpper)
re : Regex.Regex
re =
Regex.regex "%(_|-|0)?(%|Y|y|m|B|b|d|e|a|A|H|k|I|l|L|p|P|M|S)"
type Padding
= NoPadding
| Space
| Zero
| ZeroThreeDigits
{-| Use a format string to format a date. See the
[README](https://github.com/mgold/elm-date-format/blob/master/README.md) for a
list of accepted formatters.
-}
format : String -> Date.Date -> String
format s d =
localFormat international s d
{-| Use a localization record and a format string to format a date. See the
[README](https://github.com/mgold/elm-date-format/blob/master/README.md) for a
list of accepted formatters.
-}
localFormat : Local -> String -> Date.Date -> String
localFormat loc s d =
Regex.replace Regex.All re (formatToken loc d) s
{-| Formats a UTC date acording to
[ISO-8601](https://en.wikipedia.org/wiki/ISO_8601). This is commonly used to
send dates to a server. For example: `2016-01-06T09:22:00Z`.
-}
formatISO8601 : Date.Date -> String
formatISO8601 =
format "%Y-%m-%dT%H:%M:%SZ"
formatToken : Local -> Date.Date -> Regex.Match -> String
formatToken loc d m =
let
( padding, symbol ) =
case m.submatches of
[ Just "-", Just x ] ->
( Just NoPadding, x )
[ Just "_", Just x ] ->
( Just Space, x )
[ Just "0", Just x ] ->
( Just Zero, x )
[ Nothing, Just x ] ->
( Nothing, x )
_ ->
( Nothing, " " )
in
case symbol of
"%" ->
"%"
"Y" ->
d |> Date.year |> toString
"y" ->
d |> Date.year |> toString |> right 2
"m" ->
d |> Date.month |> monthToInt |> padWith (withDefault Zero padding)
"B" ->
d |> Date.month |> monthToWord loc.date.months
"b" ->
d |> Date.month |> monthToWord loc.date.monthsAbbrev
"d" ->
d |> Date.day |> padWith (withDefault Zero padding)
"e" ->
d |> Date.day |> padWith (withDefault Space padding)
"a" ->
d |> Date.dayOfWeek |> dayOfWeekToWord loc.date.wdaysAbbrev
"A" ->
d |> Date.dayOfWeek |> dayOfWeekToWord loc.date.wdays
"H" ->
d |> Date.hour |> padWith (withDefault Zero padding)
"k" ->
d |> Date.hour |> padWith (withDefault Space padding)
"I" ->
d |> Date.hour |> mod12 |> zero2twelve |> padWith (withDefault Zero padding)
"l" ->
d |> Date.hour |> mod12 |> zero2twelve |> padWith (withDefault Space padding)
"p" ->
if Date.hour d < 12 then
toUpper loc.time.am
else
toUpper loc.time.pm
"P" ->
if Date.hour d < 12 then
loc.time.am
else
loc.time.pm
"M" ->
d |> Date.minute |> padWith (withDefault Zero padding)
"S" ->
d |> Date.second |> padWith (withDefault Zero padding)
"L" ->
d |> Date.millisecond |> padWith (withDefault ZeroThreeDigits padding)
_ ->
""
monthToInt m =
case m of
Date.Jan ->
1
Date.Feb ->
2
Date.Mar ->
3
Date.Apr ->
4
Date.May ->
5
Date.Jun ->
6
Date.Jul ->
7
Date.Aug ->
8
Date.Sep ->
9
Date.Oct ->
10
Date.Nov ->
11
Date.Dec ->
12
monthToWord loc m =
case m of
Date.Jan ->
loc.jan
Date.Feb ->
loc.feb
Date.Mar ->
loc.mar
Date.Apr ->
loc.apr
Date.May ->
loc.may
Date.Jun ->
loc.jun
Date.Jul ->
loc.jul
Date.Aug ->
loc.aug
Date.Sep ->
loc.sep
Date.Oct ->
loc.oct
Date.Nov ->
loc.nov
Date.Dec ->
loc.dec
dayOfWeekToWord loc dow =
case dow of
Date.Mon ->
loc.mon
Date.Tue ->
loc.tue
Date.Wed ->
loc.wed
Date.Thu ->
loc.thu
Date.Fri ->
loc.fri
Date.Sat ->
loc.sat
Date.Sun ->
loc.sun
mod12 h =
h % 12
zero2twelve n =
if n == 0 then
12
else
n
padWith : Padding -> a -> String
padWith padding =
let
padder =
case padding of
NoPadding ->
identity
Zero ->
padLeft 2 '0'
ZeroThreeDigits ->
padLeft 3 '0'
Space ->
padLeft 2 ' '
in
padder << toString

View File

@@ -0,0 +1,207 @@
module Date.Local exposing (Local, Months, WeekDays, TimeZones, international, french)
{-| A record type to store localized time formatting information.
@docs international, french
@docs Local, Months, WeekDays, TimeZones
-}
import Dict exposing (Dict)
{-| A collection of strings and formats for localizing formats.
Time zones and default formats are not implemented,
but included to avoid possible version conflicts in the future.
-}
type alias Local =
{ date :
{ months : Months
, monthsAbbrev : Months
, wdays : WeekDays
, wdaysAbbrev : WeekDays
, defaultFormat :
Maybe String
-- for %x
}
, time :
{ am : String
, pm : String
, defaultFormat :
Maybe String
-- for %X
}
, timeZones :
Maybe TimeZones
-- for %Z
, defaultFormat :
Maybe String
-- for %c
}
{-| A record of names for the months of the year.
-}
type alias Months =
{ jan : String
, feb : String
, mar : String
, apr : String
, may : String
, jun : String
, jul : String
, aug : String
, sep : String
, oct : String
, nov : String
, dec : String
}
{-| A record of names for the days of the week.
-}
type alias WeekDays =
{ mon : String
, tue : String
, wed : String
, thu : String
, fri : String
, sat : String
, sun : String
}
{-| Maps from %z type string (+hhmm or -hhmm) to timezone name or abbreviation.
Not currently implemented.
-}
type alias TimeZones =
Dict String String
{-| A default set of localizations.
-}
international : Local
international =
{ date =
{ months =
{ jan = "January"
, feb = "February"
, mar = "March"
, apr = "April"
, may = "May"
, jun = "June"
, jul = "July"
, aug = "August"
, sep = "September"
, oct = "October"
, nov = "November"
, dec = "December"
}
, monthsAbbrev =
{ jan = "Jan"
, feb = "Feb"
, mar = "Mar"
, apr = "Apr"
, may = "May"
, jun = "Jun"
, jul = "Jul"
, aug = "Aug"
, sep = "Sep"
, oct = "Oct"
, nov = "Nov"
, dec = "Dec"
}
, wdays =
{ mon = "Monday"
, tue = "Tuesday"
, wed = "Wednesday"
, thu = "Thursday"
, fri = "Friday"
, sat = "Saturday"
, sun = "Sunday"
}
, wdaysAbbrev =
{ mon = "Mon"
, tue = "Tue"
, wed = "Wed"
, thu = "Thu"
, fri = "Fri"
, sat = "Sat"
, sun = "Sun"
}
, defaultFormat = Nothing
}
, time =
{ am = "am"
, pm = "pm"
, defaultFormat = Nothing
}
, timeZones = Nothing
, defaultFormat = Nothing
}
{-| French set of localizations.
-}
french : Local
french =
{ date =
{ months =
{ jan = "Janvier"
, feb = "Février"
, mar = "Mars"
, apr = "Avril"
, may = "Mai"
, jun = "Juin"
, jul = "Juillet"
, aug = "Août"
, sep = "Septembre"
, oct = "Octobre"
, nov = "Novembre"
, dec = "Décembre"
}
, monthsAbbrev =
{ jan = "Jan"
, feb = "Fév"
, mar = "Mar"
, apr = "Avr"
, may = "Mai"
, jun = "Jui"
, jul = "Jul"
, aug = "Aoû"
, sep = "Sep"
, oct = "Oct"
, nov = "Nov"
, dec = "Déc"
}
, wdays =
{ mon = "Lundi"
, tue = "Mardi"
, wed = "Mercredi"
, thu = "Jeudi"
, fri = "Vendredi"
, sat = "Samedi"
, sun = "Dimanche"
}
, wdaysAbbrev =
{ mon = "Lun"
, tue = "Mar"
, wed = "Mer"
, thu = "Jeu"
, fri = "Ven"
, sat = "Sam"
, sun = "Dim"
}
, defaultFormat = Nothing
}
, time =
{ am = "am"
, pm = "pm"
, defaultFormat = Nothing
}
, timeZones = Nothing
, defaultFormat = Nothing
}

View File

@@ -0,0 +1,19 @@
module Time.Format exposing (format)
{-| Format strings for times.
@docs format
-}
import Time
import Date.Format
import Date
{-| Use a format string to format a time. See the
[README](https://github.com/mgold/elm-date-format/blob/master/README.md) for a
list of accepted formatters.
-}
format : String -> Time.Time -> String
format s t =
Date.Format.format s (Date.fromTime t)

View File

@@ -0,0 +1,146 @@
module Tests exposing (all)
{- A simple test an example of the library.
Does not test every option, you can submit PRs for that.
-}
import Date
import Date.Format
import Expect
import String exposing (join, padLeft)
import Test exposing (..)
import Time
import Time.Format
-- test name, expected value, format string
all : Test
all =
describe "Format tests" <|
[ describe "Date Format tests" <|
List.map (makeTest << formatDateTest) dateTestData
, describe "Time Format tests" <|
List.map (makeTest << formatTimeTest) timeTestData
]
type alias TestTriple =
( String, String, String )
dateTestData : List TestTriple
dateTestData =
[ ( "numeric date", "12/08/2014", "%d/%m/%Y" )
, ( "spelled out date", "Tuesday, August 12, 2014", "%A, %B %d, %Y" )
, ( "time", expectedTime, "%I:%M:%S %p" )
, ( "time no spaces", expectedTimeNoSpace, "%H%M%S" )
, ( "literal %", expectedTimeWithLiteral, "%H%%%M" )
, ( "padding modifiers", "08|8| 8|08", "%m|%-m|%_m|%0m" )
]
timeTestData : List TestTriple
timeTestData =
[ ( "time no spaces", expectedTimeNoSpace, "%H%M%S" )
, ( "literal %", expectedTimeWithLiteral, "%H%%%M" )
, ( "time colons", expectedTimeColons, "%H:%M" )
, ( "time full colons", expectedFullTimeColons, "%H:%M:%S" )
, ( "time with milliseconds", expectedTimeWithMilliSeconds, "%H:%M:%S:%L" )
]
expectedTimeWithLiteral =
join "%" [ sampleHour, sampleMinute ]
expectedTimeNoSpace =
join "" [ sampleHour, sampleMinute, sampleMinute ]
expectedTimeColons =
join ":" [ sampleHour, sampleMinute ]
expectedFullTimeColons =
join ":" [ sampleHour, sampleMinute, sampleSecond ]
expectedTimeWithMilliSeconds =
join ":" [ sampleHour, sampleMinute, sampleSecond, sampleMilliSecond ]
expectedTime =
join ":" [ sampleHour, sampleMinute, sampleMinute ]
++ (case Date.hour sampleDate < 12 of
True ->
" AM"
False ->
" PM"
)
sampleDate : Date.Date
sampleDate =
Date.fromTime 1407819233012
sampleTime : Time.Time
sampleTime =
Date.toTime sampleDate
pad : Int -> Int -> String
pad n =
toString >> padLeft n '0'
sampleHour : String
sampleHour =
Date.hour sampleDate
|> pad 2
sampleMinute : String
sampleMinute =
Date.minute sampleDate
|> pad 2
sampleSecond : String
sampleSecond =
Date.second sampleDate
|> pad 2
sampleMilliSecond : String
sampleMilliSecond =
Date.millisecond sampleDate
|> pad 3
formatSampleDate : String -> String
formatSampleDate fstring =
Date.Format.format fstring sampleDate
formatSampleTime : String -> String
formatSampleTime fstring =
Time.Format.format fstring sampleTime
formatDateTest : TestTriple -> TestTriple
formatDateTest ( a, b, format ) =
( a, b, formatSampleDate format )
formatTimeTest : TestTriple -> TestTriple
formatTimeTest ( a, b, format ) =
( a, b, formatSampleTime format )
makeTest : TestTriple -> Test
makeTest ( described, expected, actual ) =
test described <| \() -> Expect.equal actual expected

View File

@@ -0,0 +1,16 @@
{
"version": "1.0.0",
"summary": "Tests for formatting dates with ease",
"repository": "https://github.com/mgold/elm-date-format.git",
"license": "BSD3",
"source-directories": [
"../src",
"."
],
"exposed-modules": [],
"dependencies": {
"elm-lang/core": "5.0.0 <= v < 6.0.0",
"elm-community/elm-test": "4.2.0 <= v < 5.0.0"
},
"elm-version": "0.18.0 <= v < 0.19.0"
}

View File

@@ -0,0 +1,8 @@
elm-stuff
elm.js
index.html
documentation.json
test/dieharder/elm*.txt
test/dieharder/generate_files.js
test/dieharder/raw_out.js

View File

@@ -0,0 +1,30 @@
Copyright (c) 2015-2016, Max Goldstein
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Max Goldstein nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,50 @@
# Random.Pcg for Elm
> "The generation of random numbers is too important to be left to chance." Robert R. Coveyou
An alternate random number generator built around four principles:
* **Statistical Quality.** If you use any seed less than 53,668 and generate one bool, it will be `True` if you're
using core's `Random` module. More sophisticated statistical tests spot patterns in the "random" numbers almost
immediately. Would you want to trust the accuracy of your [fuzz
tests](http://package.elm-lang.org/packages/elm-community/elm-test/latest/) to such a flawed algorithm? This library
produces far less predictable and biased output, especially if you use thousands of random numbers. See
`test/dieharder` for more details.
* **Useful features.** This library exports `constant` and `andMap`, which are conspicuously absent from core, along
with other helpful functions for composing generators. Particularly interesting is `independentSeed`, which allows for
lazy lists and isolated components to generate as much randomness as they need, when they need it.
* **Performance.** This library will generate floats about 3.5 times faster than core, and ints do not regress. These
figures stand to improve pending some optimizations to the compiler. You can see the [full
benchmark results](https://github.com/mgold/elm-random-pcg/issues/5#issuecomment-236398261).
* **Compatibility.** This library is a drop-in replacement for core's Random module. Specifically, you
can replace `import Random` with `import Random.Pcg as Random` and everything will continue to work. (The one exception is third party
libraries like [elm-random-extra](http://package.elm-lang.org/packages/NoRedInk/elm-random-extra/latest/Random-Extra).)
This is an implementation of [PCG](http://www.pcg-random.org/) by M. E. O'Neil. The generator is **not cryptographically
secure**.
Please report bugs, feature requests, and other issues [on GitHub](https://github.com/mgold/elm-random-pcg/issues/new).
## Changelog (major versions only)
### 5.0.0
* Argument order of `andMap` flipped.
### 4.0.0
* Upgraded for 0.18.
* Argument order of `andThen` flipped.
### 3.0.0
* Change implementation to use the RXS-M-SH variant of PCG. Now much faster and not much worse statistically.
* Remove `initialSeed2`, since there are now only 32 bits of state.
* `Random.Pcg.Interop.fission` has been changed to a (core) generator of (PCG) seeds.
* Add `generate` to match core 4.x API. Implemented by Richard Feldman.
### 2.0.0
* Upgraded for 0.17.
* `generate` renamed `step` to match core 4.x API.
* Module renamed `Random.Pcg` from `Random.PCG`.
* `split` has been removed; use `independentSeed`.
* `minInt` and `maxInt` values changed to match core.

View File

@@ -0,0 +1,17 @@
{
"version": "5.0.2",
"summary": "A faster, more featureful, statistically better, & compatible random generator",
"repository": "https://github.com/mgold/elm-random-pcg.git",
"license": "BSD3",
"source-directories": [
"src"
],
"exposed-modules": [
"Random.Pcg",
"Random.Pcg.Interop"
],
"dependencies": {
"elm-lang/core": "5.0.0 <= v < 6.0.0"
},
"elm-version": "0.18.0 <= v < 0.19.0"
}

View File

@@ -0,0 +1,848 @@
module Random.Pcg exposing (Generator, Seed, bool, int, float, oneIn, sample, pair, list, maybe, choice, choices, frequency, map, map2, map3, map4, map5, andMap, filter, constant, andThen, minInt, maxInt, step, generate, initialSeed, independentSeed, fastForward, toJson, fromJson)
{-| Generate psuedo-random numbers and values, by constructing
[generators](#Generator) for them. There are a bunch of basic generators like
[`bool`](#bool) and [`int`](#int) that you can build up into fancier generators
with functions like [`list`](#list) and [`map`](#map).
You run a `Generator` by calling the [`step`](#step) function, which
also takes a random [`Seed`](#Seed), and passes back a new seed. You should
never use the same seed twice because you will get the same result! If you need
random values over time, you should store the most recent seed in your model.
Alternatively, use [`generate`](#generate) to obtain random values from the Elm
runtime.
This is an implementation of [PCG](http://www.pcg-random.org/) by M. E. O'Neil,
and is not cryptographically secure.
# Getting Started
@docs initialSeed, step, generate
# Basic Generators
@docs Generator, bool, int, float, oneIn, sample
# Combining Generators
@docs pair, list, maybe, choice, choices, frequency
# Custom Generators
@docs constant, map, map2, map3, map4, map5, andMap, andThen, filter
# Working With Seeds
@docs Seed, independentSeed, fastForward, toJson, fromJson
# Constants
@docs minInt, maxInt
-}
import Bitwise
import Json.Encode
import Json.Decode
import Task
import Tuple
import Time
{-| A `Generator` is like a recipe for generating certain random values. So a
`Generator Int` describes how to generate integers and a `Generator String`
describes how to generate strings.
-}
type Generator a
= Generator (Seed -> ( a, Seed ))
{-| Generate a random value as specified by a given `Generator`, using a `Seed`
and returning a new one.
In the following example, we are trying to generate numbers between 0 and 100
with the `int 0 100` generator. Each time we call `generate` we need to provide
a seed. This will produce a random number and a *new* seed to use if we want to
run other generators later.
(x, seed1) = step (int 0 100) seed0
(y, seed2) = step (int 0 100) seed1
(z, seed3) = step (int 0 100) seed2
[x, y, z] -- [85, 0, 38]
Notice that we use different seeds on each line. This is important! If you reuse
the same seed, you get the same results.
(x, _) = step (int 0 100) seed0
(y, _) = step (int 0 100) seed0
(z, _) = step (int 0 100) seed0
[x,y,z] -- [85, 85, 85]
As you can see, threading seeds through many calls to `step` is tedious and
error-prone. That's why this library includes many functions to build more
complicated generators, allowing you to call `step` only a small number of
times.
Our example is best written as:
(xs, newSeed) = step (list 3 <| int 0 100) seed0
xs -- [85, 0, 38]
-}
step : Generator a -> Seed -> ( a, Seed )
step (Generator generator) seed =
generator seed
{-| Create a Command that will generate random values according to the supplied
`Generator`.
Think of this function as an alternative to `step`, since they both provide a
way to actually get the random values that you want. This function frees you
from worrying about seeds entirely, but as a tradeoff, you get your random
values asynchronously, in their own Message. Additionally, due to constraints on
third-party packages, it's possible that multiple commands sent at the same
moment will return the same values.
You can also think of this function as an alternative to `independentSeed`,
since they both allow you to use randomness in deeply nested components. In the
case of this function, it's through sending Commands up the chain that you have
to set up anyway.
-}
generate : (a -> msg) -> Generator a -> Cmd msg
generate toMsg generator =
Time.now
|> Task.map (round >> initialSeed >> step generator >> Tuple.first)
|> Task.perform toMsg
{-| A `Seed` is the source of randomness in the whole system. It hides the
current state of the random number generator.
Generators, not seeds, are the primary data structure for generating random
values. Generators are much easier to chain and combine than functions that take
and return seeds. Creating and managing seeds should happen "high up" in your
program.
-}
type Seed
= Seed Int Int
{-| Initialize the state of the random number generator. The input should be
a randomly chosen 32-bit integer. You can generate and copy random integers to
create a reproducible psuedo-random generator.
$ node
> Math.floor(Math.random()*0xFFFFFFFF)
227852860
-- Elm
seed0 : Seed
seed0 = initialSeed 227852860
Alternatively, you can generate the random integers on page load and pass them
through a port. The program will be different every time.
-- Elm
port randomSeed : Int
seed0 : Seed
seed0 = initialSeed randomSeed
-- JS
Elm.ModuleName.fullscreen(
{ randomSeed: Math.floor(Math.random()*0xFFFFFFFF) })
Either way, you should initialize a random seed only once. After that, whenever
you use a seed, you'll get another one back.
-}
initialSeed : Int -> Seed
initialSeed x =
let
(Seed state1 incr) =
-- The magic constant is from Numerical Recipes and is inlined for perf.
next (Seed 0 1013904223)
state2 =
state1 + x |> Bitwise.shiftRightZfBy 0
in
next (Seed state2 incr)
next : Seed -> Seed
next (Seed state0 incr) =
-- The magic constant is from Numerical Recipes and is inlined for perf.
Seed ((state0 * 1664525) + incr |> Bitwise.shiftRightZfBy 0) incr
-- obtain a psuedorandom 32-bit integer
peel : Seed -> Int
peel (Seed state _) =
-- This is the RXS-M-SH version of PCG, see section 6.3.4 of the paper
-- and line 184 of pcg_variants.h in the 0.94 C implementation
let
word =
((state |> Bitwise.shiftRightZfBy ((state |> Bitwise.shiftRightZfBy 28) + 4)) |> Bitwise.xor state) * 277803737
in
Bitwise.xor (word |> Bitwise.shiftRightZfBy 22) word
|> Bitwise.shiftRightZfBy 0
{-| Generate 32-bit integers in a given range, inclusive.
int 0 10 -- an integer between zero and ten
int -5 5 -- an integer between -5 and 5
int minInt maxInt -- an integer in the widest range feasible
This function *can* produce values outside of the range [[`minInt`](#minInt),
[`maxInt`](#maxInt)] but sufficient randomness is not guaranteed.
*Performance note:* This function will be ~1.5x faster if the range (i.e. `max - min + 1`) is a power of two. The
effect will only be noticable if you are generating tens of thousands of random integers.
-}
int : Int -> Int -> Generator Int
int a b =
Generator <|
\seed0 ->
let
( lo, hi ) =
if a < b then
( a, b )
else
( b, a )
range =
hi - lo + 1
in
-- fast path for power of 2
if (range |> Bitwise.and (range - 1)) == 0 then
( (peel seed0 |> Bitwise.and (range - 1) |> Bitwise.shiftRightZfBy 0) + lo, next seed0 )
else
let
threshhold =
-- essentially: period % max
rem (-range |> Bitwise.shiftRightZfBy 0) range |> Bitwise.shiftRightZfBy 0
accountForBias : Seed -> ( Int, Seed )
accountForBias seed =
let
x =
peel seed
seedN =
next seed
in
if x < threshhold then
-- in practice this recurses almost never
accountForBias seedN
else
( rem x range + lo, seedN )
in
accountForBias seed0
bit53 =
9007199254740992.0
bit27 =
134217728.0
{-| Generate floats in a given range. The following example is a generator
that produces numbers between 0 and 1.
probability : Generator Float
probability =
float 0 1
-}
float : Float -> Float -> Generator Float
float min max =
Generator <|
\seed0 ->
let
-- Get 64 bits of randomness
seed1 =
next seed0
n0 =
peel seed0
n1 =
peel seed1
-- Get a uniformly distributed IEEE-754 double between 0.0 and 1.0
hi =
toFloat (n0 |> Bitwise.and 0x03FFFFFF) * 1.0
lo =
toFloat (n1 |> Bitwise.and 0x07FFFFFF) * 1.0
val =
((hi * bit27) + lo) / bit53
-- Scale it into our range
range =
abs (max - min)
scaled =
val * range + min
in
( scaled, next seed1 )
{-| Create a generator that produces boolean values with equal probability. This
example simulates flipping three coins and checking if they're all heads.
threeHeads : Generator Bool
threeHeads =
map3 (\a b c -> a && b && c) bool bool bool
-}
bool : Generator Bool
bool =
map ((==) 1) (int 0 1)
{-| The maximum value for randomly generated 32-bit ints.
-}
maxInt : Int
maxInt =
2147483647
{-| The minimum value for randomly generated 32-bit ints.
-}
minInt : Int
minInt =
-2147483648
{-| Create a pair of random values. A common use of this might be to generate
a point in a certain 2D space. Imagine we have a collage that is 400 pixels
wide and 200 pixels tall.
randomPoint : Generator (Int,Int)
randomPoint =
pair (int -200 200) (int -100 100)
-}
pair : Generator a -> Generator b -> Generator ( a, b )
pair genA genB =
map2 (,) genA genB
{-| Create a list of random values of a given length.
floatList : Generator (List Float)
floatList =
list 10 (float 0 1)
intList : Generator (List Int)
intList =
list 5 (int 0 100)
intPairs : Generator (List (Int, Int))
intPairs =
list 10 <| pair (int 0 100) (int 0 100)
-}
list : Int -> Generator a -> Generator (List a)
list n (Generator generate) =
Generator <|
\seed ->
listHelp [] n generate seed
listHelp : List a -> Int -> (Seed -> ( a, Seed )) -> Seed -> ( List a, Seed )
listHelp list n generate seed =
if n < 1 then
( list, seed )
else
let
( value, newSeed ) =
generate seed
in
listHelp (value :: list) (n - 1) generate newSeed
{-| Create a generator that always produces the value provided. This is useful
when creating complicated chained generators and you need to handle a simple
case. It's also useful for the base case of recursive generators.
-}
constant : a -> Generator a
constant value =
Generator (\seed -> ( value, seed ))
{-| Transform the values produced by a generator using a stateless function as a
callback.
These examples show how to generate letters based on a basic integer generator.
lowercaseLetter : Generator Char
lowercaseLetter =
map (\n -> Char.fromCode (n + 97)) (int 0 25)
uppercaseLetter : Generator Char
uppercaseLetter =
map (\n -> Char.fromCode (n + 65)) (int 0 25)
-}
map : (a -> b) -> Generator a -> Generator b
map func (Generator genA) =
Generator <|
\seed0 ->
let
( a, seed1 ) =
genA seed0
in
( func a, seed1 )
{-| Combine two generators. This is useful when you have a function with two
arguments that both need to be given random inputs.
pointInCircle : Float -> Generator (Float, Float)
pointInCircle radius =
let
r = float 0 radius
theta = map degrees (float 0 360)
in
map2 (curry fromPolar) r theta
-}
map2 : (a -> b -> c) -> Generator a -> Generator b -> Generator c
map2 func (Generator genA) (Generator genB) =
Generator <|
\seed0 ->
let
( a, seed1 ) =
genA seed0
( b, seed2 ) =
genB seed1
in
( func a b, seed2 )
{-| Combine three generators. This could be used to produce random colors.
rgb : Generator Color.Color
rgb =
map3 Color.rgb (int 0 255) (int 0 255) (int 0 255)
hsl : Generator Color.Color
hsl =
map3 Color.hsl (map degrees (float 0 360)) (float 0 1) (float 0 1)
-}
map3 : (a -> b -> c -> d) -> Generator a -> Generator b -> Generator c -> Generator d
map3 func (Generator genA) (Generator genB) (Generator genC) =
Generator <|
\seed0 ->
let
( a, seed1 ) =
genA seed0
( b, seed2 ) =
genB seed1
( c, seed3 ) =
genC seed2
in
( func a b c, seed3 )
{-| Combine four generators. This could be used to produce random transparent
colors.
rgba : Generator Color.Color
rgba =
map4 Color.rgba (int 0 255) (int 0 255) (int 0 255) (float 0 1)
-}
map4 : (a -> b -> c -> d -> e) -> Generator a -> Generator b -> Generator c -> Generator d -> Generator e
map4 func (Generator genA) (Generator genB) (Generator genC) (Generator genD) =
Generator <|
\seed0 ->
let
( a, seed1 ) =
genA seed0
( b, seed2 ) =
genB seed1
( c, seed3 ) =
genC seed2
( d, seed4 ) =
genD seed3
in
( func a b c d, seed4 )
{-| Combine five generators.
-}
map5 : (a -> b -> c -> d -> e -> f) -> Generator a -> Generator b -> Generator c -> Generator d -> Generator e -> Generator f
map5 func (Generator genA) (Generator genB) (Generator genC) (Generator genD) (Generator genE) =
Generator <|
\seed0 ->
let
( a, seed1 ) =
genA seed0
( b, seed2 ) =
genB seed1
( c, seed3 ) =
genC seed2
( d, seed4 ) =
genD seed3
( e, seed5 ) =
genE seed4
in
( func a b c d e, seed5 )
{-| Map over any number of generators.
randomPerson : Generator Person
randomPerson =
map person genFirstName
|> andMap genLastName
|> andMap genBirthday
|> andMap genPhoneNumber
|> andMap genAddress
|> andMap genEmail
-}
andMap : Generator a -> Generator (a -> b) -> Generator b
andMap =
map2 (|>)
{-| Chain random operations by providing a callback that accepts a
randomly-generated value. The random value can be used to drive more randomness.
This example shows how we can use `andThen` to generate a list of random values
*and* random length. Then we use `map` to apply a stateless function to that
list. Assume we already have `genName : Generator String` defined.
authors : Generator String
authors =
int 1 5 -- number of authors
|> andThen (\i -> list i genName)
|> map (\ns ->
case ns of
[n] ->
"Author: " ++ n
n::ns ->
"Authors: " ++ String.join ", " ns ++ " and " ++ n
[] ->
"This can't happen"
)
If you find yourself calling `constant` in every branch of the callback, you can
probably use `map` instead.
-}
andThen : (a -> Generator b) -> Generator a -> Generator b
andThen callback (Generator generateA) =
Generator <|
\seed ->
let
( result, newSeed ) =
generateA seed
(Generator generateB) =
callback result
in
generateB newSeed
{-| Filter a generator so that all generated values satisfy the given predicate.
evens : Generator Int
evens =
filter (\i -> i % 2 == 0) (int minInt maxInt)
**Warning:** If the predicate is unsatisfiable, the generator will not terminate, your
application will hang with an infinite loop, and you will be sad. You should
also avoid predicates that are merely very difficult to satisfy.
badCrashingGenerator =
filter (\_ -> False) anotherGenerator
verySlowGenerator =
filter (\i -> i % 2000 == 0) (int minInt maxInt)
-}
filter : (a -> Bool) -> Generator a -> Generator a
filter predicate generator =
Generator (retry generator predicate)
retry : Generator a -> (a -> Bool) -> Seed -> ( a, Seed )
retry generator predicate seed =
let
( candidate, newSeed ) =
step generator seed
in
if predicate candidate then
( candidate, newSeed )
else
retry generator predicate newSeed
{-| Produce `True` one-in-n times on average.
Do not pass a value less then one to this function.
flippedHeads = oneIn 2
rolled6 = oneIn 6
criticalHit = oneIn 20
-}
oneIn : Int -> Generator Bool
oneIn n =
map ((==) 1) (int 1 n)
{-| Given a list, choose an element uniformly at random. `Nothing` is only
produced if the list is empty.
type Direction = North | South | East | West
direction : Generator Direction
direction =
sample [North, South, East, West]
|> map (Maybe.withDefault North)
-}
sample : List a -> Generator (Maybe a)
sample =
let
find k ys =
case ys of
[] ->
Nothing
z :: zs ->
if k == 0 then
Just z
else
find (k - 1) zs
in
\xs -> map (\i -> find i xs) (int 0 (List.length xs - 1))
{-| Choose between two values with equal probability.
type Flip = Heads | Tails
coinFlip : Generator Flip
coinFlip =
choice Heads Tails
-}
choice : a -> a -> Generator a
choice x y =
map
(\b ->
if b then
x
else
y
)
bool
{-| Create a generator that chooses a generator from a list of generators
with equal probability.
**Warning:** Do not pass an empty list or your program will crash! In practice
this is usually not a problem since you pass a list literal.
-}
choices : List (Generator a) -> Generator a
choices gens =
frequency <| List.map (\g -> ( 1, g )) gens
{-| Create a generator that chooses a generator from a list of generators
based on the provided weight. The likelihood of a given generator being
chosen is its weight divided by the total weight (which doesn't have to equal 1).
**Warning:** Do not pass an empty list or your program will crash! In practice
this is usually not a problem since you pass a list literal.
-}
frequency : List ( Float, Generator a ) -> Generator a
frequency pairs =
let
total =
List.sum <| List.map (Tuple.first >> abs) pairs
pick choices n =
case choices of
( k, g ) :: rest ->
if n <= k then
g
else
pick rest (n - k)
_ ->
Debug.crash "Empty list passed to Random.Pcg.frequency!"
in
float 0 total |> andThen (pick pairs)
{-| Produce `Just` a value on `True`, and `Nothing` on `False`.
You can use `bool` or `oneIn n` for the first argument.
-}
maybe : Generator Bool -> Generator a -> Generator (Maybe a)
maybe genBool genA =
genBool
|> andThen
(\b ->
if b then
map Just genA
else
constant Nothing
)
{-| A generator that produces a seed that is independent of any other seed in
the program. These seeds will generate their own unique sequences of random
values. They are useful when you need an unknown amount of randomness *later*
but can request only a fixed amount of randomness *now*.
Let's say you write a component that uses some randomness to initialize itself
and then never needs randomness again. You can easily write a `Generator
Component` by mapping over the generators it needs. But if component requires
randomness after initialization, it should keep its own independent seed, which
it can get by mapping over *this* generator.
type alias Component = { seed : Seed }
genComponent : Generator Component
genComponent = map Component independentSeed
If you have a lot of components, you can initialize them like so:
genComponents : List (Seed -> a) -> Generator (List a)
genComponents constructors =
list (List.length constructors) independentSeed
|> map (List.map2 (<|) constructors)
The independent seeds are extremely likely to be distinct for all practical
purposes. However, it is not proven that there are no pathological cases.
-}
independentSeed : Generator Seed
independentSeed =
Generator <|
\seed0 ->
let
gen =
int 0 0xFFFFFFFF
( ( state, b, c ), seed1 ) =
step (map3 (,,) gen gen gen) seed0
{--
Although it probably doesn't hold water theoretically, xor two
random numbers to make an increment less likely to be
pathological. Then make sure that it's odd, which is required.
Finally step it once before use.
--}
incr =
(Bitwise.xor b c) |> Bitwise.or 1 |> Bitwise.shiftRightZfBy 0
in
( seed1, next <| Seed state incr )
mul32 : Int -> Int -> Int
mul32 a b =
-- multiply 32-bit integers without overflow
let
ah =
(a |> Bitwise.shiftRightZfBy 16) |> Bitwise.and 0xFFFF
al =
Bitwise.and a 0xFFFF
bh =
(b |> Bitwise.shiftRightZfBy 16) |> Bitwise.and 0xFFFF
bl =
Bitwise.and b 0xFFFF
in
-- The Bitwise.or could probably be replaced with shiftRightZfBy but I'm not positive?
(al * bl) + (((ah * bl + al * bh) |> Bitwise.shiftLeftBy 16) |> Bitwise.shiftRightZfBy 0) |> Bitwise.or 0
{-| Fast forward a seed the given number of steps, which may be negative (the
seed will be "rewound"). This allows a single seed to serve as a random-access
lookup table of random numbers. (To be sure no one else uses the seed, use
`step independentSeed` to split off your own.)
diceRollTable : Int -> Int
diceRollTable i =
fastForward i mySeed |> step (int 1 6) |> Tuple.first
-}
fastForward : Int -> Seed -> Seed
fastForward delta0 (Seed state0 incr) =
let
helper : Int -> Int -> Int -> Int -> Int -> Bool -> ( Int, Int )
helper accMult accPlus curMult curPlus delta repeat =
let
( accMult_, accPlus_ ) =
if Bitwise.and delta 1 == 1 then
( mul32 accMult curMult
, mul32 accPlus curMult + curPlus |> Bitwise.shiftRightZfBy 0
)
else
( accMult, accPlus )
curPlus_ =
mul32 (curMult + 1) curPlus
curMult_ =
mul32 curMult curMult
newDelta =
-- divide by 2
delta |> Bitwise.shiftRightZfBy 1
in
if newDelta == 0 then
if delta0 < 0 && repeat then
-- if passed a negative number, negate everything once
helper accMult_ accPlus_ curMult_ curPlus_ -1 False
else
( accMult_, accPlus_ )
else
helper accMult_ accPlus_ curMult_ curPlus_ newDelta repeat
( accMultFinal, accPlusFinal ) =
-- magic constant same as in next
helper 1 0 1664525 incr delta0 True
in
Seed (mul32 accMultFinal state0 + accPlusFinal |> Bitwise.shiftRightZfBy 0) incr
{-| Serialize a seed as a [JSON
value](http://package.elm-lang.org/packages/elm-lang/core/latest/Json-Encode#Value)
to be sent out a port, stored in local storage, and so on. The seed can be
recovered using `fromJson`.
Do not inspect or change the resulting JSON value.
-}
toJson : Seed -> Json.Encode.Value
toJson (Seed state incr) =
Json.Encode.list [ Json.Encode.int state, Json.Encode.int incr ]
{-| A JSON decoder that can recover seeds encoded using `toJson`. Alternatively,
pass an integer to create a seed using `initialSeed`.
Json.Decode.decodeValue fromJson (toJson mySeed) == Ok mySeed
-}
fromJson : Json.Decode.Decoder Seed
fromJson =
Json.Decode.oneOf
[ Json.Decode.map2 Seed
(Json.Decode.index 0 Json.Decode.int)
(Json.Decode.index 1 Json.Decode.int)
, Json.Decode.map initialSeed Json.Decode.int
]

View File

@@ -0,0 +1,29 @@
module Random.Pcg.Interop exposing (fission)
{-| Provides a function to create a PCG seed from a seed in the core library.
This is useful for library writers who need a splittable or most robust PRNG but
don't want to require client code to use the PCG implementation.
```elm
import Random
import Random.Pcg
import Random.Pcg.Interop as Random.Pcg
```
@docs fission
-}
import Random
import Random.Pcg
{-| Use the core library's random seed to produce a PCG random seed.
It seems that the package website doesn't show modules in type annotations, so here it is in full:
fission : Random.Generator (Random.Pcg.Seed)
-}
fission : Random.Generator Random.Pcg.Seed
fission =
Random.int 0 0xFFFFFFFF |> Random.map Random.Pcg.initialSeed

View File

@@ -0,0 +1,4 @@
# Random.Pcg Tests
The tests in this directory are one-off simple programs to confirm that certain functions aren't obviously wrong.
Heavyweight statistical tests are under `dieharder`. Benchmarks (using a 0.16 benchmarking lib) are in `benchmark`.

View File

@@ -0,0 +1,26 @@
module Bench.Native exposing (..)
import Native.Benchmark
type Benchmark
= Benchmark
type BenchmarkSuite
= BenchmarkSuite
bench : String -> (() -> a) -> Benchmark
bench =
Native.Benchmark.bench
suite : String -> List (Benchmark) -> BenchmarkSuite
suite =
Native.Benchmark.suite
run : List (BenchmarkSuite) -> b -> b
run =
Native.Benchmark.run

View File

@@ -0,0 +1,45 @@
module Main exposing (main)
import Html
import Html.App
import Bench.Native as Benchmark
import Bench.Native exposing (Benchmark, BenchmarkSuite, bench, suite)
import Random.Pcg as Ran
main : Program Never
main =
Html.App.beginnerProgram
{ model = ()
, update = \_ _ -> ()
, view = \() -> Html.text "Done!"
}
|> Benchmark.run [ mySuite ]
seed : Ran.Seed
seed =
Ran.initialSeed 141053960
n =
1000
mySuite : BenchmarkSuite
mySuite =
suite
"Random number suite"
[ bench "flip a coin" (\_ -> Ran.step Ran.bool seed)
, bench ("flip " ++ toString n ++ " coins") (\_ -> Ran.step (Ran.list n Ran.bool) seed)
, bench "generate an integer 0-4094" (\_ -> Ran.step (Ran.int 0 4094) seed)
, bench "generate an integer 0-4095" (\_ -> Ran.step (Ran.int 0 4095) seed)
, bench "generate an integer 0-4096" (\_ -> Ran.step (Ran.int 0 4096) seed)
, bench "generate a massive integer" (\_ -> Ran.step (Ran.int 0 4294967295) seed)
, bench "generate a percentage" (\_ -> Ran.step (Ran.float 0 1) seed)
, bench ("generate " ++ toString n ++ " percentages") (\_ -> Ran.step (Ran.list n (Ran.float 0 1)) seed)
, bench "generate a float 0-4094" (\_ -> Ran.step (Ran.float 0 4094) seed)
, bench "generate a float 0-4095" (\_ -> Ran.step (Ran.float 0 4095) seed)
, bench "generate a float 0-4096" (\_ -> Ran.step (Ran.float 0 4096) seed)
, bench "generate a massive float" (\_ -> Ran.step (Ran.float 0 4294967295) seed)
]

View File

@@ -0,0 +1,44 @@
var _user$project$Native_Benchmark = (function () {
function bench(name, fn) {
return {
name: name,
fn: fn
}
}
function suite(name, fnList) {
var fns = _elm_lang$core$Native_List.toArray(fnList),
suite = new Benchmark.Suite(name),
i, curr;
for (i = 0; i < fns.length; i++) {
curr = fns[i];
suite = suite.add(curr.name, curr.fn);
}
return suite;
}
function run(suiteList, program) {
var suites = _elm_lang$core$Native_List.toArray(suiteList),
i;
for (i = 0; i < suites.length; i++) {
suites[i].on('start', function () {
console.log('Starting ' + this.name + ' suite.');
}).on('cycle', function (event) {
console.log(String(event.target));
}).on('complete', function () {
console.log('Done with ' + this.name + ' suite.');
}).run();
}
return program;
}
return {
bench: F2(bench),
suite: F2(suite),
run: F2(run)
};
})()

View File

@@ -0,0 +1,14 @@
# Benchmarks for Random.Pcg
These benchmarks are possible thanks to Robin Heggelund Hansen's work benchmarking [collections-ng](https://github.com/Skinney/collections-ng).
To run them yourself, first download the vendor files (you only need to do this once):
```
mkdir vendor
cd vendor
wget https://cdn.jsdelivr.net/lodash/4.13.1/lodash.min.js
wget https://cdn.jsdelivr.net/benchmarkjs/2.1.0/benchmark.js
```
Then adjust the import of `Random` in `Bencher.elm` to control which library is being benchmarked. Finally run `sh prep-bench.sh` then
`elm-reactor` in this directory and open `run-benchmarks.html`.

View File

@@ -0,0 +1,17 @@
{
"version": "0.0.1",
"summary": "Benchmarking suite for Random.Pcg",
"repository": "https://github.com/user/project.git",
"license": "BSD3",
"source-directories": [
".",
"../../src"
],
"exposed-modules": [],
"native-modules": true,
"dependencies": {
"elm-lang/core": "4.0.0 <= v < 5.0.0",
"elm-lang/html": "1.1.0 <= v < 2.0.0"
},
"elm-version": "0.17.0 <= v < 0.18.0"
}

View File

@@ -0,0 +1,5 @@
#!/bin/sh
set -e
elm-make --yes --output bench.js Bencher.elm

View File

@@ -0,0 +1,7 @@
<!DOCTYPE HTML>
<html><head><meta charset="UTF-8"><title>Main</title><style>html,head,body { padding:0; margin:0; }
body { font-family: calibri, helvetica, arial, sans-serif; }</style>
<script type="text/javascript" src="vendor/lodash.min.js"></script>
<script type="text/javascript" src="vendor/benchmark.js"></script>
<script type="text/javascript" src="bench.js"></script>
</head><body>Check console for output... <script type="text/javascript">Elm.Main.fullscreen()</script></body></html>

View File

@@ -0,0 +1,40 @@
module Bounds exposing (..)
{-| Demonstrate that, after creating ten million floats between 0 and 1, all
indeed fall inside that range. If you try this test with core (4.x) Random, it
will produce at least one value less than zero! This could have catastrophic
effects if you take the log or square root of this value, although more likely
it will just propogate NaN through your program.
-}
import Random.Pcg as Random
import Html
import Html.App
n =
10000000
seed0 : Random.Seed
seed0 =
Random.initialSeed 628318530
samples : List Float
samples =
Random.step (Random.list n (Random.float 0 1)) seed0 |> fst
range : ( Maybe Float, Maybe Float )
range =
( List.minimum samples, List.maximum samples )
main : Program Never
main =
Html.App.beginnerProgram
{ model = ()
, update = \_ _ -> ()
, view = \() -> Html.text <| toString <| range
}

View File

@@ -0,0 +1,58 @@
import String
import Task exposing (Task)
import Random.Pcg as Random
import Console exposing (IO)
name = "elm-random-pcg"
n = 2e6 |> round
seed0 = 42
bound = {lo = 0, hi = 0xFFFFFFFF}
header : IO ()
header =
Console.putStr <| "# " ++ name ++ "\n# seed: " ++ toString seed0 ++ "\ntype: d\ncount: " ++
toString (12*n) ++ "\nnumbit: 32"
body : List Int -> IO ()
body ints =
Console.putStr "\n"
`Console.seq`
Console.putStr (List.map (toString >> String.padLeft 10 ' ') ints |> String.join "\n")
core : Random.Seed -> (List Int, Random.Seed)
core seed =
let gen = Random.list n (Random.int bound.lo bound.hi)
in Random.step gen seed
run1 : Random.Seed -> IO Random.Seed
run1 seed =
let (ints, seed2) = core seed
in body ints |> Console.map (\_ -> seed2)
job : IO ()
job =
header
`Console.seq`
run1 (Random.initialSeed seed0)
`Console.andThen` run1
`Console.andThen` run1
`Console.andThen` run1
`Console.andThen` run1
`Console.andThen` run1
`Console.andThen` run1
`Console.andThen` run1
`Console.andThen` run1
`Console.andThen` run1
`Console.andThen` run1
`Console.andThen` run1
`Console.seq`
Console.exit 0
port io : Signal (Task x ())
port io = Console.run job

View File

@@ -0,0 +1,27 @@
# Dieharder tests for Random.Pcg
**These tests rely on a library that has not been update for 0.17.**
The purpose of testing the random number generator is twofold: one, show the deficiencies in the core implementation;
two, show the correctness of the Pcg implementation *in Elm*. Because we are testing the Elm implementation, not the Pcg
algorithm, we must feed dieharder a file of already-generated random numbers. I've seen sources recommending 10 million
random numbers; these tests use 24 million, but even so the files are "rewound", as many as 2500 times on later tests.
For the original 19 diehard tests, the core fails 9 tests while Pcg fails none. (One test resulted in "weak" but further
investigation resulted in passing; we expect this on one test in 100). On the entire battery, core passes 29, fails 75,
and is weak in 10. Pcg passes 82, fails 24, and is weak in 8. Some of Pcg's strength may be attributed to its 64 bits of
state, compared to core's 32, but this does not excuse failing the less-comprehensive diehard tests. Conversely, many of
the failures can be attributed to reusing pre-generated random numbers.
The source Elm is `Dieharder.elm`. Because the tests require more random numbers than can be stored in memory, they
output chunks of 2 million at a time. In order to reuse the code, `compile.sh` rewrites the file to change the Random
implementation. It also writes out the `.txt` data files, and then runs dieharder, logging the results.
The result log files have been committed to version control; I would have liked to commit the data files but they're
huge, and only take a few minutes to generate. You are free to alter the random seed in the Elm code, and rerun the
tests with `sh compile` (which takes several hours to complete). That said, I encourage you to run it long enough to see
core fail the birthday test, after only five or six seconds of scrutiny. (The `dieharder` tool is available through most
package managers. Once the data files are generated, try `time dieharder -g 202 -f elm-core-random.txt -d 0`.)
The Pcg paper uses the TestU01 (e.g. BigCrush) suite; I'm using dieharder since it was easier to get to work
reading from a file.

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -e
if [ ! -f elm-core-random.txt ]; then
sed -i.bak 's/import Random.Pcg as Random/import Random/g' Dieharder.elm
sed -i.bak 's/elm-random-pcg/elm-core-random/g' Dieharder.elm
elm make Dieharder.elm --output=raw_out.js
sh elm-io.sh raw_out.js generate_files.js
echo "Generating elm-core-random.txt..."
node generate_files.js > elm-core-random.txt
dieharder -g 202 -f elm-core-random.txt -a | tee dieharder-core.log
fi
if [ ! -f elm-random-pcg.txt ]; then
sed -i.bak 's/import Random$/import Random.Pcg as Random/g' Dieharder.elm
sed -i.bak 's/elm-core-random/elm-random-pcg/g' Dieharder.elm
elm make Dieharder.elm --output=raw_out.js
sh elm-io.sh raw_out.js generate_files.js
echo "Generating elm-random-pcg.txt..."
node generate_files.js > elm-random-pcg.txt
dieharder -g 202 -f elm-random-pcg.txt -a | tee dieharder-pcg.log
fi

View File

@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Script for use with the Console library to allow Elm to run on Node.
if [ "$#" -ne 2 ]; then
echo "Usage: $0 <generated-js-file> <output-file>"
exit 1
fi
read -d '' handler <<- EOF
(function(){
if (typeof Elm === "undefined") { throw "elm-io config error: Elm is not defined. Make sure you call elm-io with a real Elm output file"}
if (typeof Elm.Main === "undefined" ) { throw "Elm.Main is not defined, make sure your module is named Main." };
var worker = Elm.worker(Elm.Main);
})();
EOF
cat $1 > $2
echo "$handler" >> $2

View File

@@ -0,0 +1,15 @@
{
"version": "0.0.1",
"summary": "Dieharder tests for elm-random-pcg",
"repository": "https://github.com/user/project.git",
"license": "BSD3",
"source-directories": [
".", "../../src"
],
"exposed-modules": [],
"dependencies": {
"elm-lang/core": "3.0.0 <= v < 4.0.0",
"laszlopandy/elm-console": "1.0.3 <= v < 2.0.0"
},
"elm-version": "0.16.0 <= v < 0.17.0"
}

View File

@@ -0,0 +1,15 @@
{
"version": "0.0.1",
"summary": "Miscellaneous tests for elm-pcg-random",
"repository": "https://github.com/user/project.git",
"license": "BSD3",
"source-directories": [
".", "../src"
],
"exposed-modules": [],
"dependencies": {
"elm-lang/core": "4.0.3 <= v < 5.0.0",
"elm-lang/html": "1.1.0 <= v < 2.0.0"
},
"elm-version": "0.17.1 <= v < 0.18.0"
}

View File

@@ -0,0 +1,61 @@
module FastForwardTest exposing (..)
{-| Compares the outputs of seeds created using the ordinary sequential stepping method,
and those created using the `fastForward` function. Note that we compare output, not the
seeds themselves, because somestimes the states get confused between signed and unsigned--
this has no effect on the psuedo-random output. (Edit July 2016: not sure if that's true
anymore...)
-}
import Random.Pcg as Random
import Html
import Html.App
n =
100000
seed0 : Random.Seed
seed0 =
Random.initialSeed 628318530
stepped : List Random.Seed
stepped =
List.scanl
(\_ oldSeed -> Random.step Random.bool oldSeed |> snd)
seed0
[1..n]
fastForwarded : List Random.Seed
fastForwarded =
List.map
(\i -> Random.fastForward i seed0)
[0..n]
gen =
Random.int 1 10000
generate seed =
Random.step gen seed |> fst
bools =
List.map2
(\seed1 seed2 -> generate seed1 == generate seed2)
stepped
fastForwarded
|> List.all identity
main : Program Never
main =
Html.App.beginnerProgram
{ model = ()
, update = \_ _ -> ()
, view = \() -> Html.text <| toString <| bools
}

View File

@@ -0,0 +1,35 @@
module FilterTest exposing (..)
import Html exposing (Html)
import Random.Pcg as Random
{-| This test checks that very difficult-to-satisfy filters do not result in a stack
overflow (just run slowly). The test will take several seconds to run!
-}
main : Html Never
main =
let
-- Try 'predicate i = False' to verify that the test hangs instead of
-- crashing with a stack overflow (indicating that tail recursion has
-- been properly optimized into a loop, which in this case happens to
-- be infinite).
predicate i =
i % 1000000 == 0
divisibleNumberGenerator =
Random.filter predicate (Random.int Random.minInt Random.maxInt)
listGenerator =
Random.list 10 divisibleNumberGenerator
initialSeed =
Random.initialSeed 1234
generatedList =
fst (Random.step listGenerator initialSeed)
in
-- You can verify that everything is working by observing that the generated
-- sum is in fact divisible by the chosen divisor (e.g. for a divisor of
-- 1000000 the sum should always have six trailing zeros)
Html.text (toString (List.sum generatedList))

View File

@@ -0,0 +1,40 @@
module IndependentSeedTest exposing (..)
import Html exposing (Html)
import Random.Pcg as Random exposing (Generator)
import Debug
{-| This test ensures that the 'independentSeed' generator actually produces
valid random seeds that can be stepped properly to produce distinct values. If
there is an error in 'independentSeed', at some point a seed such as 'Seed 0
0' will be produced which when stepped will yield itself, resulting in an
infinite loop within 'filter' (since it will internally just keep generating
the same value over and over again).
-}
main : Html Never
main =
let
initialSeed =
Random.initialSeed 1234
seedListGenerator =
Random.list 1000 Random.independentSeed
randomSeeds =
fst (Random.step seedListGenerator initialSeed)
isDivisible i =
let
_ =
Debug.log "i" i
in
i /= 0 && i % 10 == 0
divisibleNumberGenerator =
Random.filter isDivisible (Random.int Random.minInt Random.maxInt)
divisibleNumbers =
List.map (Random.step divisibleNumberGenerator >> fst) randomSeeds
in
Html.text (toString (List.sum divisibleNumbers))

View File

@@ -0,0 +1,60 @@
module ListTest exposing (..)
{-| The central limit theorem states that if you sample any distribution and take the mean, and do that many times,
those means will be on a standard distribution.
That's exactly what this test does, using an independent seed. Judging how "good" the distribution is versus how good it
"should" be is beyond the scope of this example. This test is really to show that independent seeds aren't *trivially*
wrong.
-}
import Dict exposing (Dict)
import Random.Pcg as Random
import Html
import Html.App
seed0 : Random.Seed
seed0 =
Random.initialSeed 628318530 |> Random.step Random.independentSeed |> snd
gen =
Random.int 1 6
|> Random.list 15
|> Random.map mean
|> Random.map (\x -> round (10 * x))
|> Random.list 800
|> Random.map toMultiSet
mean : List Int -> Float
mean xs =
toFloat (List.sum xs) / toFloat (List.length xs)
toMultiSet : List Int -> Dict Int Int
toMultiSet list =
let
helper xs d =
case xs of
[] ->
d
x :: xs ->
helper xs <| Dict.insert x (Dict.get x d |> Maybe.withDefault 0 |> (+) 1) d
in
helper list Dict.empty
generated =
Random.step gen seed0 |> fst
main : Program Never
main =
Html.App.beginnerProgram
{ model = ()
, update = \_ _ -> ()
, view = \() -> Html.text <| toString <| generated
}

View File

@@ -0,0 +1,7 @@
# Port test
```
elm make Test.elm --output=elm.js
```
This shows how to hook up ports to the random number generator to produce different values each time.

View File

@@ -0,0 +1,13 @@
module Test where
import Graphics.Element exposing (show)
import Random.Pcg as Random
port randomSeed : (Int, Int)
seed0 : Random.Seed
seed0 = (uncurry Random.initialSeed2) randomSeed
gen = Random.list 32 (Random.int 1 6)
main = show <| fst <| Random.generate gen seed0

View File

@@ -0,0 +1,14 @@
{
"version": "1.0.0",
"summary": "helpful summary of your project, less than 80 characters",
"repository": "https://github.com/user/project.git",
"license": "BSD3",
"source-directories": [
".", "../../src/"
],
"exposed-modules": [],
"dependencies": {
"elm-lang/core": "3.0.0 <= v < 4.0.0"
},
"elm-version": "0.16.0 <= v < 0.17.0"
}

View File

@@ -0,0 +1,34 @@
module RewindTest exposing (..)
{-| Demonstrates that `fastForward` may also be used to rewind. We create a seed, fastForward it, then rewind it to get
back the original seed.
-}
import Random.Pcg as Random
import Html
import Html.App
n =
31
seed0 =
Random.initialSeed 628318
seed1 =
Random.fastForward n seed0
seed2 =
Random.fastForward -n seed1
main : Program Never
main =
Html.App.beginnerProgram
{ model = ()
, update = \_ _ -> ()
, view = \() -> Html.text <| toString [ seed0, seed1, seed2 ]
}