{"id":15415884,"url":"https://github.com/davidje13/raffle","last_synced_at":"2025-06-25T02:37:56.241Z","repository":{"id":151511219,"uuid":"126736371","full_name":"davidje13/Raffle","owner":"davidje13","description":"Calculates odds of winning prizes in a raffle","archived":false,"fork":false,"pushed_at":"2021-10-13T04:21:50.000Z","size":242,"stargazers_count":1,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-20T13:21:10.560Z","etag":null,"topics":["finance","statistics"],"latest_commit_sha":null,"homepage":"https://davidje13.github.io/Raffle/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/davidje13.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-03-25T20:17:34.000Z","updated_at":"2023-02-09T11:37:23.000Z","dependencies_parsed_at":"2023-05-18T18:30:52.713Z","dependency_job_id":null,"html_url":"https://github.com/davidje13/Raffle","commit_stats":{"total_commits":68,"total_committers":1,"mean_commits":68.0,"dds":0.0,"last_synced_commit":"1e244a1ff4d3d988d5ced9d7f376a83ff7db8f18"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/davidje13/Raffle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidje13%2FRaffle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidje13%2FRaffle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidje13%2FRaffle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidje13%2FRaffle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davidje13","download_url":"https://codeload.github.com/davidje13/Raffle/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidje13%2FRaffle/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261792353,"owners_count":23210301,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["finance","statistics"],"created_at":"2024-10-01T17:10:05.074Z","updated_at":"2025-06-25T02:37:56.222Z","avatar_url":"https://github.com/davidje13.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Raffle\n\nCalculates odds of winning prizes in a raffle. This was written to\ncalculate the expected return from the UK\n[NS\u0026I premium bonds](https://www.nsandi.com/premium-bonds) scheme, but\ncan also be applied to any raffle where the prizes are primarily\ndefined by their monetary value.\n\n[See it in action!](https://davidje13.github.io/Raffle/)\n\nThis project includes a web page for visualising results, but the core\nlogic is available as a component which can be included in other\nprojects under the LGPL license.\n\n## Browser Support\n\nDue to the use of Web Workers and modern Javascript syntax, this will\nonly work in modern browsers (Chrome / Safari / FireFox). It may or may\nnot work in Edge and almost certainly will not work in Internet\nExplorer.\n\n## Local Development\n\nTo run this project locally, you will need to start a localhost server.\nIf you have nodejs installed, this can be done with a simple command:\n\n```sh\nnpm start\n```\n\n(a localhost server is required because Web Workers cannot be served\nfrom the filesystem in all browsers)\n\n### WebAssembly\n\nThis project uses C for its core logic (compiled to WebAssembly). To change the\nC code, you will need to install emscripten:\n\n```sh\nbrew install emscripten binaryen\n```\n\nYou can also install helpers (e.g. `wasm2wat`) with:\n\n```sh\nbrew install wabt\n# configure git diff for .wasm files:\nprintf '[diff \"wasm\"]\\n\\ttextconv = wasm2wat\\n' \u003e\u003e ~/.gitconfig\n```\n\nTo rebuild the WebAssembly files, run:\n\n```sh\nnpm run build\n```\n\n### Testing\n\nThere is also a suite of Jasmine tests (`spec/**/*.spec.js`) and a\nlinter which can be run with:\n\n```sh\nnpm run test            # run Jasmine \u0026 C tests\nnpm run lint            # run linter\nnpm run check           # run linter and tests\n```\n\n## Using the Library\n\n```javascript\n// First, build a raffle with prizes and a total audience size:\n\nconst raffle = new Raffle({\n  audience: 100,\n  prizes: [\n    {count:  2, value: 1000},\n    {count: 10, value:  100},\n    {count: 20, value:   10},\n  ],\n  pCutoff: 1e-10, // Optimisation (defaults to 0)\n});\n\n// Now enter the raffle with a number of tickets:\n\nraffle.enter(5).then((results) =\u003e {\n  console.log('Tickets: ' + results.tickets());\n  console.log('Audience: ' + raffle.audience());\n  console.log(\n    'Prize range: £' + results.min().toFixed(2) +\n    ' - £' + results.max().toFixed(2)\n  );\n  console.log('Median winnings: £' + results.median());\n\n  for(let i = results.min(); i \u003c= results.max(); ++ i) {\n    const pE = results.exact_probability(i);\n    if(pE \u003c 0.00000001) {\n      continue;\n    }\n    const pR = results.range_probability(i, Number.POSITIVE_INFINITY);\n    console.log(\n      '= £' + i.toFixed(2) + ': ' +\n      (pE * 100).toFixed(4) + '%' +\n      ' \u003e= £' + i.toFixed(2) + ': ' +\n      (pR * 100).toFixed(4) + '%'\n    );\n  }\n});\n\n// We can enter the same raffle any number of times.\n// All results will be independent:\n\nraffle.enter(2).then((results) =\u003e {\n  // ...\n});\n```\n\n## Explanation\n\n### Theory\n\nTo calculate the probabilities, all distributions of prizes are\nsimulated (with a configurable cutoff for incredibly unlikely outcomes\nfor performance reasons). The process is:\n\n1. Clean up the prize list (e.g. by combining prizes of the same value)\n\n2. Add 0-value prizes to the prize list so that every entry will win\n   something (even if its worth is 0)\n\n3. Sort the prize list from the rarest to the most common. This is not\n   strictly required but provides a large performance boost in the\n   later stages by keeping the matrices sparse.\n\n4. Construct a nested array structure. This represents a sparse matrix\n   where the primary (\"vertical\") dimension represents the number of\n   spent tickets so far, and the secondary (\"horizontal\") dimension is\n   the total value won so far. The elements of the matrix store the\n   probability of this situation. Initially, only the element [0,0] has\n   a value (1).\n\n   _note: a nested array structure is used so that the next stages can\n   run more efficiently._\n\n5. For each prize (from rarest to most common), iterate down the matrix\n   (all tickets spent to no tickets spent). At each of these rows,\n   calculate the probabilities of winning different quantities of the\n   current prize (e.g. if we are at row 8 of 10, we have 2 tickets\n   remaining, so we can calculate how likely we are to win 0, 1 or 2 of\n   the current prize). Once these probabilities are known, use them to\n   add entries to the rows above the current row (i.e. 1 more ticket\n   spent =\u003e value of 1 prize, with current probability * odds of\n   winning, then same for 2, 3, etc.).\n\n   _note: in this implementation, a shortcut is taken for the final\n   (most common) prize, since once that stage has completed we will\n   only care about a small part of the matrix._\n\n6. Read out the probabilities for each monetary value from the top row\n   of the matrix (corresponding to all tickets spent).\n\n7. Apply post-processing (store probabilities as cumulative\n   probabilities and reduce rounding errors by normalising to [0 1])\n\nTo calculate the probability of winning an individual prize (step 5),\nthe equation used is:\n\n```\n(targets C n) * ((total - targets) C (samples - n))\n---------------------------------------------------\n                 total C samples\n```\n\nWhere `n C k` represents the binomial coefficient:\n\n```\n             n!\nn C k = -------------\n        k! * (n - k)!\n```\n\nSince these factorials lead to very large numbers, these calculations\nare performed in log space, using Stirling's log-gamma approximation.\n\n### Code\n\nSteps 1-3 above are performed when creating a `Raffle` object, and the\nresult is stored. When `enter` is called, steps 4-7 are performed in\na worker thread (code for this is in raffle_worker.js). The result is\nstored in a `Result` object which is returned. This `Result` object\nhas various convenience methods for reading the probabilities.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidje13%2Fraffle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidje13%2Fraffle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidje13%2Fraffle/lists"}