RdnCdn
RdnCdn

Reputation: 183

Return element from list of structs based upon criteria

I have a function that is a list of structs called Sandbox. See below:

defmodule Sandbox do

  defstruct description: nil, chance: nil

  def all, do: [
    %Sandbox{
      description: "Description 1",
      chance: [1..40]
    },
    %Sandbox{
      description: "Description 2",
      chance: [41..60]
    },
    %Sandbox{
      description: "Description 3",
      chance: [61..100]
    },
  ]

I want to select a Sandbox at random with a specific probability for each Sandbox. My plan is to assign a range of numbers to an attribute called "chance" based upon the desired probability for each item, as seen above. I will then generate a number at random from 1 to 100 and match it with each Sandboxes list to find the winning Sandbox. The function needs to return the winning Sandbox.

Questions:

  1. Can you write out a function to perform the match and return the proper Sandbox? Have been trying various combinations for a while and can't seem to figure it out.

  2. Can anyone think of an easier way to do this? I feel I've overcomplicated this problem.

Really appreciate the help.

Upvotes: 1

Views: 311

Answers (2)

Mike Buhot
Mike Buhot

Reputation: 4885

You may be running into issues because the chance is a range inside a list.

This should do what you're looking for:

defmodule Sandbox do

  defstruct description: nil, chance: nil

  def all, do: [
    %Sandbox{
      description: "Description 1",
      chance: 1..40
    },
    %Sandbox{
      description: "Description 2",
      chance: 41..60
    },
    %Sandbox{
      description: "Description 3",
      chance: 61..100
    },
  ]

  @doc "Select a sandbox given a random value between 1 and 100"
  def find(r) do
    all() |> Enum.find(fn s -> r in s.chance end)
  end

  @doc "Select a sandbox at random"
  def random() do
    :rand.uniform(100) |> find()
  end
end

Upvotes: 2

Dogbert
Dogbert

Reputation: 222198

First of all you shouldn't wrap the range in a list. Just write chance: 1..40, chance: 41..60, etc. Here's how you can generate a random integer and then select the correct sandbox:

defmodule Sandbox do
  defstruct description: nil, chance: nil

  def all, do: [
    %Sandbox{
      description: "Description 1",
      chance: 1..40
    },
    %Sandbox{
      description: "Description 2",
      chance: 41..60
    },
    %Sandbox{
      description: "Description 3",
      chance: 61..100
    },
  ]

  def random do
    rand = :rand.uniform(100)
    Enum.find(all(), fn %{chance: chance} -> rand in chance end)
  end
end

Can anyone think of an easier way to do this? I feel I've overcomplicated this problem.

This approach looks good to me. If performance is a concern it should be faster to store the list in a tree-like data structure where we can jump to the right chance faster than iterating through each element.

Upvotes: 3

Related Questions