Skip to content

Instantly share code, notes, and snippets.

@kybernetikos
Last active February 5, 2019 22:04
Show Gist options
  • Save kybernetikos/0bb24c183fa0a438a7d4e23ba29819ef to your computer and use it in GitHub Desktop.
Save kybernetikos/0bb24c183fa0a438a7d4e23ba29819ef to your computer and use it in GitHub Desktop.
Generate some random tickets from quantum numbers.
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));
})
@kybernetikos
Copy link
Author

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 :-(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment