Coder Social home page Coder Social logo

ndsvw / fluent-random-picker Goto Github PK

View Code? Open in Web Editor NEW
41.0 3.0 3.0 340 KB

Fluent Random Picker is a nice, performant, fluent way to pick random values. Probabilities can be specified, values can be weighted.

Home Page: https://www.nuget.org/packages/FluentRandomPicker

License: MIT License

C# 100.00%
random fluent picker choose pick probability outof csharp hacktoberfest hacktoberfest2023

fluent-random-picker's Introduction

# Fluent Random Picker

![Fluent Random Picker](https://raw.githubusercontent.com/ndsvw/Fluent-Random-Picker/main/FluentRandomPicker/icon48x48.png "Fluent Random Picker")

Fluent Random Picker is a user-friendly, but also performant .NET library that simplifies random value selection / picking. It allows you to specify probabilities and weights for each value, making it easy to handle complex randomization tasks.

[![License MIT](https://img.shields.io/github/license/ndsvw/Fluent-Random-Picker)](https://github.com/ndsvw/Fluent-Random-Picker/blob/main/LICENSE)
[![Build status](https://github.com/ndsvw/Fluent-Random-Picker/actions/workflows/dotnet.yml/badge.svg)](https://github.com/ndsvw/Fluent-Random-Picker)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ndsvw_Fluent-Random-Picker&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=ndsvw_Fluent-Random-Picker)
[![Code coverage](https://img.shields.io/codecov/c/github/ndsvw/Fluent-Random-Picker)](https://app.codecov.io/gh/ndsvw/Fluent-Random-Picker)
[![Nuget version](https://img.shields.io/nuget/v/FluentRandomPicker)](https://www.nuget.org/packages/FluentRandomPicker)
[![Release notes](https://img.shields.io/badge/release%20notes-%F0%9F%97%92-green
)](RELEASENOTES.md)
[![NuGet downloads](https://img.shields.io/nuget/dt/FluentRandomPicker.svg)](https://www.nuget.org/packages/FluentRandomPicker)

<!-- TOC -->

- [Fluent Random Picker](#fluent-random-picker)
    - [Compatibility](#compatibility)
    - [Getting started](#getting-started)
    - [Examples](#examples)
        - [The easiest example](#the-easiest-example)
        - [Specifying percentages](#specifying-percentages)
        - [Specifying weights](#specifying-weights)
        - [Picking multiple values](#picking-multiple-values)
        - [Picking distinct values](#picking-distinct-values)
        - [Using external types with weight/percentage information](#using-external-types-with-weightpercentage-information)
        - [Omitting percentages or weights](#omitting-percentages-or-weights)
        - [Specifying the returned type explicitly](#specifying-the-returned-type-explicitly)
    - [Advanced](#advanced)
    - [Migration to version 3](#migration-to-version-3)

<!-- /TOC -->

## Compatibility

Fluent Random Picker targets .Net Standard 2.0 and is therefore compatible with the following target frameworks:

- ✔️ .Net 5, 6, 7, 8
- ✔️ .Net Core 2.X, 3.X
- ✔️ .Net Standard 2.0, 2.1
- ✔️ .Net Framework 4.7.2, 4.8

## Getting started

Install the nuget package (https://www.nuget.org/packages/FluentRandomPicker/)

Add the using directive:
```c#
using FluentRandomPicker;
```

To get started, use the `Out.Of()` syntax as shown in the examples below:

## Examples

### The easiest example
```c#
var randomNumber = Out.Of().Value(5).AndValue(6).PickOne();
// randomNumber is 5 or 6 with equal probability.
```

### Specifying percentages
```c#
var randomChar = Out.Of()
                  .Value('a').WithPercentage(70)
                  .AndValue('b').WithPercentage(30)
                  .PickOne();
// randomChar is 'a' with a probability of 70 % and 'b' with a probability of 30 %.
```

### Specifying weights
```c#
var randomString = Out.Of()
                  .Value("hello").WithWeight(2)
                  .AndValue("world").WithWeight(3)
                  .PickOne();
// randomString is "hello" or "world", but the probability for "world" is 1.5 times as high.
```

### Specifying multiple values
```c#
var randomChar = Out.Of().Values(new List<char> { 'a', 'b' })
                  .WithPercentages(new List<int> { 70, 30 })
                  .PickOne();
// randomChar is 'a' with a probability of 70 % and 'b' with a probability of 30 %.

var randomChar = Out.Of().Values(new HashSet<string> { "hello", "world" })
                  .WithWeights(new List<int> { 2, 3 })
                  .PickOne();
// randomString is "hello" or "world", but the probability for "world" is 1.5 times as high.
```

### Picking multiple values
```c#
var randomInts = Out.Of()
                  .Value(1).WithPercentage(70)
                  .AndValue(10).WithPercentage(15)
                  .AndValue(100).WithPercentage(10)
                  .AndValue(1000).WithPercentage(5)
                  .Pick(5);
// randomInts can be [1, 1, 1, 1, 1] with a higher probability or [1, 1, 100, 10, 1]
// or even [1000, 1000, 1000, 1000, 1000] with a very small probability.
```

### Picking distinct values
```c#
var randomInts = Out.Of()
                  .Values(new List<int> { 1, 10, 100, 1000 })
                  .WithPercentages(70, 15, 10, 5)
                  .PickDistinct(2);
// randomInts can be [1, 10], [1, 100], ..., [1000, 100], but not [1, 1], [10, 10], ...
```

### Using external types with weight/percentage information
```c#
class Item {
    public int Rarity { get; set; }
    public string Name { get; set; }
}

var items = new Item[]
{
    new Item { Name = "Stone", Rarity = 5 }, // common
    new Item { Name = "Silver helmet", Rarity = 2 }, // uncommon
    new Item { Name = "Gold sword", Rarity = 1 }, // rare
};

var itemName = Out.Of()
                  .PrioritizedElements(items)
                  .WithValueSelector(x => x.Name)
                  .AndWeightSelector(x => x.Rarity)
                  .PickOne();

// itemName is "Stone" in 5/8 of the cases, "Silver helmet" in 2/8 of the cases and "Gold sword" in 1/8 of the cases.
// If no value selector is specified, the whole item object will be returned instead of only its name.
```

### Omitting percentages or weights
```c#
var randomChar = Out.Of()
                  .Value('a').WithPercentage(80)
                  .AndValue('b') // no percentage
                  .AndValue('c') // no percentage
                  .PickOne();
// The missing percentages to reach 100% are equally distributed on the values without specified percentages.
// Attention! The missing percentages to reach 100% must be divisible without remainder through the number of values without percentages.
// randomChar is 'a' with a probability of 80% or 'b' or 'c' with a probability of each 10%.
```

```c#
var randomString = Out.Of()
                  .Value("hello").WithWeight(4)
                  .AndValue("world") // no weight
                  .PickOne();
// The default weight is 1.
// randomString is "hello" with a probability of 80% (4 of 5) and "world" with a probability of 20% (1 of 5).
```

### Specifying the returned type explicitly
```c#
var operation = Out.Of<Func<long, long>>()
                .Value(i => i + 2)
                .AndValue(i => i * 2)
                .AndValue(i => (long)Math.Pow(i, 2))
                .AndValue(i => (long)Math.Pow(i, i))
                .PickOne();

var result = operation(10);
// result equals 10 + 2 or 10 * 2 or 10^2 or 10^10. 
```

## Advanced

[//]: # (comment: links are absolute, so that they work on nuget.org, too)

Please see [README-Advanced.md](https://github.com/ndsvw/Fluent-Random-Picker/blob/main/README-Advanced.md) for more advanced topics like:

- [specifying a seed](https://github.com/ndsvw/Fluent-Random-Picker/blob/main/README-Advanced.md#specifying-a-seed)
- [using a secure RNG](https://github.com/ndsvw/Fluent-Random-Picker/blob/main/README-Advanced.md#using-a-secure-rng)
- [using a custom RNG](https://github.com/ndsvw/Fluent-Random-Picker/blob/main/README-Advanced.md#using-a-custom-rng)

## Migration to version 3

The namespace was changed to match coding conventions.
Please replace:

```c#
using Fluent_Random_Picker;
```
with
```c#
using FluentRandomPicker;
```

Some method parameter identifiers do also have changed to match [the coding conventions of Microsoft](https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions).

fluent-random-picker's People

Contributors

dependabot[bot] avatar ndsvw avatar sapiensanatis avatar shoter avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

fluent-random-picker's Issues

NotEnoughValuesToPickException when only one value is passed

Having another issue with my drops code 😔

If my enemy list (which is data driven) happens to only contain one element, it throws NotEnoughValuesToPickException when initializing the picker.

I am initializing an IPick<DropEntity> as follows

IPick<AtgenEnemy> randomBuilder = Out.Of()
    .PrioritizedElements(enemyList)
    .WithWeightSelector(x =>x.Weight);

and the stack trace is

FluentRandomPicker.Exceptions.NotEnoughValuesToPickException: Exception of type 'FluentRandomPicker.Exceptions.NotEnoughValuesToPickException' was thrown.
   at FluentRandomPicker.SelectorBasedRandomPicker`1.PrioritizedElements(IEnumerable`1 elements)
   at FluentRandomPicker.NonGenericOf.PrioritizedElements[T](IEnumerable`1 ts)
   at DragaliaAPI.Features.Dungeon.QuestEnemyService.GetEnemyPicker(AtgenEnemy[] enemyList)

It would be helpful for me if this was fixed, but I can see why it would be intended. I recognize that initializing a random picker with one element is a bit weird, but there's nothing technically stopping it from always returning the same value. Let me know your thoughts 🙂

Get rid of multiple enumerations of IEnumerables

There are multiple enumerations of IEnumerables in the code.

Count() is probably also calculated more often than necessary.

FluentRandomPicker should work with Collections/Lists internally. The IEnumerable that the user passes to FluentRandomPicker needs to be fully enumerated, anyway:

  • To pick 1 or more non-prioritized element(s), the number of all elements needs to be known.
  • To pick 1 or more prioritized element(s), all of them need to be enumerated, too.

OverflowException in SecureRandomNumberGenerator.NextInt()

Failed NextInt_NoArgument_ReturnsEquallyDistributedValues [613 ms]
Error Message:
Test method FluentRandomPicker.Tests.Random.SecureRandomNumberGeneratorTests.NextInt_NoArgument_ReturnsEquallyDistributedValues threw exception:
System.OverflowException: Negating the minimum value of a twos complement number is invalid.
Stack Trace:
at FluentRandomPicker.Random.SecureRandomNumberGenerator.NextInt() in /home/alex/SynologyDrive/Projects/Fluent-Random-Picker/FluentRandomPicker/Random/SecureRandomNumberGenerator.cs:line 31
at FluentRandomPicker.Tests.Random.SecureRandomNumberGeneratorTests.NextInt_NoArgument_ReturnsEquallyDistributedValues() in /home/alex/SynologyDrive/Projects/Fluent-Random-Picker/FluentRandomPicker.Tests/Random/SecureRandomNumberGeneratorTests.cs:line 59
at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)

PrioritizedElements.Pick(n) returns the same value n times

Version: 3.5.0

Reproduction example, slightly modified from the PrioritizedElements example in the docs:

using FluentRandomPicker;

var items = new Item[]
{
    new Item { Name = "Stone", Rarity = 1 }, // common
    new Item { Name = "Silver helmet", Rarity = 1 }, // uncommon
    new Item { Name = "Gold sword", Rarity = 1 }, // rare
};

IEnumerable<string> pickedItems = Out.Of()
    .PrioritizedElements(items)
    .WithValueSelector(x => x.Name)
    .AndWeightSelector(x => x.Rarity)
    .Pick(10);

foreach (string name in pickedItems)
{
    Console.WriteLine(name);
}
    
class Item {
    public int Rarity { get; set; }
    public string Name { get; set; }
}

When I run this the same name is always printed 10 times. I know this is theoretically possible but I am assuming that if it has happened 5+ times in a row for me then there is probably a bug 😉

This seems to also affect pickers using percentages instead of weightings.

Percentages less than 1

At the moment the lowest you can have is 1% but if I want a value to be returned 0.0001% of the time I cannot do it.

Weights using long

Hey,

Is it possible to extend weights to support long, lookg like at the moment they only support int?

❤️

Allow chaining Values(...).AndValues(...) ?

What's possible in this case:

  • Out.Of().Values(...).AndValues(...).AndValues(...)...
  • Out.Of().Values(...).WithWeigts(...).AndValues(...).WithWeigts(...)...

What's not possible in this case:

  • Out.Of().Values(...).WithWeigts(...).AndValues(...).Pick(...);
  • Out.Of().Values(...).AndValues(...).WithWeigts(...).Pick(...);

What about percentages?

Do all of them sum of to 100 or each of the .Values(...).WithPercentages(...) blocks?

Consider extension methods

  • How much effort is it?
  • is it worth it?
  • does the project name "Fluent-Random-Picker" still make sense?
    • Can extension methods for all this be built without fluent syntax?
    • Does the extension method syntax still look like the code that already exists?

Extension methods could exist for IEnumerable:

new List<int>{ 1, 2, 3}.PickOne(); // but where can you set the RNG?
new List<int>{ 1, 2, 3}.PickOne().WithSettings(rng).Evaluate(); // but then, the PickOne() logic is different to the PickOne() from the normal syntax

How can I give a small pool of reference types and pick a large number of items?

Version: 3.4.0

I am trying to use the following code to randomly generate some drops for a game:

private static IEnumerable<DropEntity> GenerateDrops(QuestDropInfo questDropInfo)
{
    int totalQuantity = (int)Math.Round(questDropInfo.Drops.Sum(x => x.Quantity));

    IPick<DropEntity> randomBuilder = Out.Of()
        .PrioritizedElements(questDropInfo.Drops)
        .WithWeightSelector(x => x.Weight);

    return randomBuilder.Pick(totalQuantity);
}

questDropInfo is a drop table with the following example JSON representation. The Weight property is the rounded value of Quantity * 100.

[
   {
      "_Id": 201009001,
      "_Quantity": 50
   },
   {
      "_Id": 40050045,
      "_Quantity": 0.1
   },
   {
      "_Id": 103001003,
      "_Quantity": 10
   }
]

I'm using totalQuantity as I want the items to be weighted in such a way that increasing the average quantity for one item does not make the other items less common.

When I run this code, FluentRandomPicker throws NotEnoughValuesToPickException presumably because I am trying to get 60 drops from a list with 3 values. I wanted to evenly distribute the drops based on the above weighting and generate many more items than I pass in.

The docs show an example where 5 values are picked from a picker with 4 values, but is this case different because I'm working with reference types which can't easily be cloned?

Weight doesn't influence random values correctly.

Running this code 100.000.000 times returns almost improbably often 'a' and 'b', but 'c' is almost improbably rare.

var value = Out.Of()
    .Value('a').WithWeight(7)
    .AndValue('b').WithWeight(2)
    .AndValue('c').WithWeight(1)
    .AndValue('d').WithWeight(0)
    .PickOne();

a: a lot > than 700.000.000
b: a lot > than 200.000.000
c: ~ 9.000.000 instead of 10.000.000

Allow specifying values + priorities in one step

(Based on issue 10 - 3rd comment and issue 10 - 4th comment by phillip-haydon)

Idea: Something like this:

var options = Out.Of().WeightedValues(lootable.Select(x => new WeightedValue(x, x.Weight));

var drop = options.PickOne();

Advantage:

  • No mistakes with order (order of values and percentages/weights) need to match when using Out.Of().Values(...).AndWeights(...) syntax
  • Easy way if the values and percentages/weights are already combined in an object.

Priorities don't matter in rare cases

SortingBasedWeightedLeftShuffle can't handle a combination of large priorities and large random doubles:

Math.Pow(0.9999, 1/(double)int.MaxValue) // = 0.99999999999995348165526820594095624983310699462891
Math.Pow(0.9999, 1/(double)long.MaxValue) // = 1.00000000000000000000000000000000000000000000000000
Math.Pow(0.9999, 1/((double)long.MaxValue/10)) // = 1.00000000000000000000000000000000000000000000000000
Math.Pow(0.9999, 1/((double)long.MaxValue/1_000_000)) // = 1.00000000000000000000000000000000000000000000000000
Math.Pow(0.9999, 1/((double)long.MaxValue/10_000_000)); // = 0.99999999999999988897769753748434595763683319091797

So, maybe, there needs to be a fallback like Math.Pow(0.9999, 10_000_000/(double)n); for larger numbers.
Maybe, a fallback should already be there for ints, because for ~0,000001% of the weights (higher if we would allow long weights), it does not even make a difference, whether they are int.MaxValue or int.MaxValue / 10 (edge case):

Math.Pow(0.99999999, 1/(double)int.MaxValue) // = 1.00000000000000000000000000000000000000000000000000
Math.Pow(0.99999999, 1/((double)int.MaxValue / 10)) // = 1.00000000000000000000000000000000000000000000000000
Math.Pow(0.99999999, 1/((double)int.MaxValue / 100))  // = 0.9999999999999996

This gets worse when allowing to specify priorities as longs (see #14)


Furthermore: Random.NextDouble being exactly 0 could also be problematic as the result is always 0...


(from #14)

Try improving implementation of StochasticAcceptanceBasedWeightedLeftShuffle

Question: How to pick values with different priorities prioritized?

The current implementation works with stochastic acceptance and runs in linear time on average (see StochasticAcceptanceBasedWeightedLeftShuffle.cs)

Problem: The max probability value is calculated once in the beginning. If there is one high probability (e.g. weight 10.000) and all the other values are low (e.g. weight 1), the while(true) loop for the stochastic acceptance is iterated very often for no reason.

But: Updating the max potentially n times makes the algorithm run in O(n^2).

Sorting the values in the beginning to have "all max values" for every step makes it O(n*log(n)), what's maybe not the best solution.

Possibly, there is a different approach that runs in O(n) and does not require max-tracking. Or maybe there is a good approach to realize max-tracking without too much additional complexity.

Create implementation of `IRandomNumberGenerator` that is cryptographically secure

Currently FRP is using only Random() which is cryptographically unsecure. It is going to be flagged as major flaw by a lot of static code analyzers (i.e veracode). Adding new interface to handle random number generation in more secure way would be great.
Also there should be a way to exchange currently used random number generator.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.