Skip to content

Sup2point0/weighted-list

Repository files navigation

weighted-list

A list implementation for weighted randomisation, implemented (eventually) in every programming language I’ve learnt.

Python
greetings = WeightedList((20, "sup"), (2, "salutations"))

print(greetings.select())
# => sup
C#
WeightedList<string, int> greetings = new((20, "sup"), (2, "salutations"));

Console.WriteLine(greetings.GetRandomValue());
// => salutations
TypeScript (under development)
let greetings = new WeightedList([20, "sup"], [2, "salutations"]);

console.log(greetings.select_value());
// => sup
Haskell (under development)
greetings :: WeightedList String Int
greetings = newWeightedList [(20, "sup"), (2, "salutations")]

main :: IO ()
main = print (selectValue greetings)
-- => salutations
Rust (under development)
let descriptors = wlist![
    (10, String::from("cool")),
    (5,  String::from("awesome")),
    (2,  String::from("elegant")),
    (1,  String::from("beautiful")),
];

let words = descriptors.select_random_values()
    .rng(&mut rand::rng())
    .count(2)
    .unique(true)
    .call();

if let Some(first) = words[0] && let Some(second) = words[1] {
    println!("Rust is {} and {}", first, second);
    // => Rust is awesome and elegant
}
Ruby (awaiting development)
# Ruby (working on it!)

An immutable optimised variant FrozenWeightedList is also implemented, which provides $O(\log{n})$ item access.


Features

  • Weighted randomised selection with a variety of constraints
  • Utility methods to manipulate values and weights
  • In-place and pure variants of methods for flexibility
  • Conversions to and from a wide range of other data types

Future

  • Slice indexing1

Purposes

Tip

For the full rationale behind this project, see rationale.

I made this class for weighted randomisation, where each element in a collection has a different chance of being selected – the greater an item’s weight, the higher the chance it is selected. This is super common in games for reward systems, displaying messages, etc.


Usage

Tip

Walkthroughs and specimens for each language can be found in their respective folders.

Honestly, I don’t trust my code enough to publish it :P

The project is not available as a package.2 Instead, just download the relevant files, or copy and paste the code directly.

Python

All you need is the weightedlist.py file, which contains the WeightedList class with all the functionality. Simply import it, and you’re ready to go!

from weightedlist import WeightedList

See walkthrough for a tutorial, or examples for examples.

C#

All the code is contained within the WeightedList.cs file. You might also need the weighted-list.csproj file. If you want the entire solution, you can download the repo and extract the c-sharp/ folder.

For a tutorial, see walkthrough.


Compatibility

Language Version Status Dependencies Notes
Python >= 3.11 Awaiting rewrite
C# 12.0 Awaiting maintenance None Supports LINQ querying
TypeScript Under development None
Haskell GHC2021 Under development None
Rust 2024 Under development rand, num_traits, bon
Ruby Awaiting development

Questions

Why did you create this?

Back when I was picking up the ropes of Python, I was working on a project which featured randomisation, and, like any game developer, I thought it’d be cool to give each outcomes different probabilities of occurring. At first, I achieved this behaviour by duplicating items, but I quickly realised the numerous issues with this.

And so, I set out to write my own class, which I’d never really needed to do up until that point. I thought it’d be a great exercise in learning Python – and it very much was, teaching me tons about object-oriented programming, dunder methods, generators, etc. It was also my first experience of conscientiously writing code that wasn’t exclusively for myself, which helped me understand the importance of consistency and clarity, and above all, documentation.

A couple years later, I’ve come back to do the same in C#, this time also adding several features I always intended to add but never did – especially non-integer weights, which allows the class to truly embrace its usage as representing probabilities. Trying to translate Python into C# was an interesting experience,3 and helped highlight some important differences between the languages that I would otherwise not have found out.

A few more years later, I’m back to do the same in Haskell and Rust (and also finish off the TypeScript and Ruby implementations that I started but never finished). Damn I love this project. Seriously, it never fails to raise so many questions about a language’s mechanics and quirks that I would never encounter otherwise.

Is this even useful?

I mean yeah, a whole several-hundred-lines class to handle one thing is probably overkill... it’s more an exercise and proof-of-concept.

Regardless, I’ve used my own code4 in at least 2 major projects (PENGUIN and Algorhythm), so I can definitely say it’s been useful to me!

Why are the source files several hundred lines long?

  1. documentation
  2. line breaks
  3. utility

Particularly documentation. That stuff just eats the line count. Also, implementing something as complex as an enumerable container requires a lot of methods, operators, interfaces and delegation. And in C# you've even got overloading to account for as well.

How fast is it?

In all honesty, I don’t know. I’m slowly adding benchmarks to test different approaches.

Why is your Python code not compliant to PEP 8?

I have my own particular preferences when it comes to coding in Python, which I explain fully here.

Why do you start {} on a new line?

I’m a C# programmer, what can I say :P

Why do you use snake_case in TypeScript?

I’m a Python programmer, what can I say :P

Why are you okay with camelCase in Haskell then?

I used snake_case before, and ngl, in Haskell you kinda need the camelCase to keep things readable without parentheses...


Contribute

Any feedback, suggestions or improvements are definitely welcome!


Footnotes

  1. Really quite difficult with non-integer weights.

  2. I don’t think it’s a large enough project to warrant an entire package, when you could just copy and paste the code directly.

  3. This was not exactly the way I created the project in C#, but the Python implementation certainly laid out a general framework and was influential in some design decisions.

  4. To my own surprise, somewhat.