Last active
February 5, 2019 22:04
-
-
Save kybernetikos/0bb24c183fa0a438a7d4e23ba29819ef to your computer and use it in GitHub Desktop.
Generate some random tickets from quantum numbers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const fetch = require('node-fetch'); | |
const lotteries = { | |
lotto: { | |
balls: [ | |
{number: 6, from:1, to: 59} | |
] | |
}, | |
euromillions: { | |
balls: [ | |
{number: 5, from:1, to: 50}, | |
{number: 2, from:1, to: 12} | |
] | |
}, | |
megamillions: { | |
balls: [ | |
{number: 5, from: 1, to: 70}, | |
{number: 1, from: 1, to: 25} | |
] | |
} | |
} | |
async function asyncReduce(collection, fn, initial) { | |
if (initial == undefined) { | |
initial = collection.shift() | |
} | |
let result = initial | |
for (let item of collection) { | |
result = await fn(result, item) | |
} | |
return result | |
} | |
const base = 'https://qrng.anu.edu.au/API/jsonI.php?type=uint8&length=' | |
function requestRandomHexOctets(num) { | |
return fetch(base + String(num)).then((response) => response.json()).then((body) => body.data.map((a) => parseInt(a, 16))) | |
} | |
function makeRandomSource() { | |
const len = 100; | |
let buffer = [] | |
const drawByte = async () => { | |
if (buffer.length == 0) { | |
buffer = await requestRandomHexOctets(len) | |
} | |
return buffer.pop() | |
} | |
return async (from, to) => { | |
const width = to - from + 1 // inclusive | |
if (width > 256) { | |
throw new Error("We're just using bytes here, one at a time. Maximum width of range is 256.") | |
} | |
const max = Math.floor(256/width) * width | |
let value = max | |
while (value >= max) { | |
value = await drawByte() | |
} | |
return (value % width) + from | |
} | |
} | |
function totalBalls(lottery) { | |
return lottery.balls.map((d) => d.number).reduce((acc, x) => acc + x, 0) | |
} | |
// select n integers from the range [from, to] (inclusive at both sides), | |
// taking random values from the randomSource as needed | |
async function randomNumbersWithoutReplacement(n, from, to, randomSource) { | |
const possibilities = Array.from({length: to - from + 1}, (_, i) => i + from); | |
const result = [] | |
for (let i = 0; i < n; ++i) { | |
const index = await randomSource(0, possibilities.length) | |
result.push(...possibilities.splice(index, 1)) | |
} | |
// sorted for beauty | |
result.sort((a, b) => a - b) | |
return result | |
} | |
async function randomResult(lottery, randomSource) { | |
return asyncReduce(lottery.balls, async (result, {number, from, to}) => [...result, ...(await randomNumbersWithoutReplacement(number, from, to, randomSource))], []) | |
} | |
function tickets(lottery, number, randomSource) { | |
return Promise.all(Array.from({length:number}, () => randomResult(lottery, randomSource))) | |
} | |
function playLotteryQuantum(lottery, number) { | |
return tickets(lottery, number, makeRandomSource()); | |
} | |
const inquirer = require('inquirer'); | |
const questions = [ | |
{ | |
type: 'list', | |
name: 'lottery', | |
message: 'Which lottery do you want to get tickets for?', | |
choices: Object.keys(lotteries) | |
}, | |
{ | |
type: 'input', | |
name: 'number', | |
message: 'How many do you want to get?', | |
validate: (value) => !isNaN(parseInt(value, 10)) || "Please enter a number.", | |
filter: Number | |
} | |
] | |
inquirer.prompt(questions).then(({lottery, number}) => { | |
playLotteryQuantum(lotteries[lottery], number) | |
.then((result) => console.log(result)); | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The random numbers generated by
Math.floor(randomSource() * possibilities.length)
have biases when randomSource is a byte/256. Really I should draw repeatededly from randomSource until I get one that satisfies the requirement, but that means that the number of bytes needed from randomSource is unbounded :-(