mxst

mxst

Can I have a devstruct with computed values?

Hey everyone.
I was wondering about how I should initialize a struct where I have some values and some computed values.

For now I came up with:

defmodule Stats do
  defstruct annotations: 0, true_positive: 0,
    false_positive: 0, false_negative: 0,
    precision: 0, recall: 0

  def set_precision(stats \\ %AnnotationStats{}) do
    Map.put(stats, :precision, (stats.true_positive / (stats.true_positive + stats.false_positive)))
  end

  def set_recall(stats \\ %AnnotationStats{}) do
    Map.put(stats, :recall, (stats.true_positive / (stats.true_positive + stats.false_negative)))
  end
end

What I the initialize with

my_stats = %Stats{annotations: 3, true_positive: 1, false_positive: 2, false_negative: 1}
|> set_precision()
|> set_recall()

But is there a cleaner way ? Or the way to do it?

Thank you all :slight_smile:

Marked As Solved

Eiji

Eiji

Here is my proposition:

defmodule Stats do
  required_keys = [:false_negative, :false_positive, :true_positive]
  @enforce_keys required_keys
  defstruct required_keys ++ [annotations: 0, precision: 0, recall: 0]

  @type t :: %__MODULE__{
          annotations: integer,
          false_negative: integer,
          false_positive: integer,
          precision: float,
          recall: float,
          true_positive: integer
        }

  @spec init(t) :: t
  def init(%__MODULE__{true_positive: true_positive} = struct) do
    computed = %{
      precision: init_computed(true_positive, struct.false_positive),
      recall: init_computed(true_positive, struct.false_negative)
    }

    Map.merge(struct, computed)
  end

  defp init_computed(true_positive, value), do: true_positive / (true_positive + value)
end

Stats.init(%Stats{annotations: 3, false_negative: 1, false_positive: 2, true_positive: 1})

It would fail if you did not pass 3 keys (specified in @enforce_keys) which are required to compute.

Alternatively you could write something like:

defmodule Stats do
  required_keys = [:false_negative, :false_positive, :true_positive]
  @enforce_keys required_keys
  defstruct required_keys ++ [annotations: 0, precision: 0, recall: 0]

  @type t :: %__MODULE__{
          annotations: integer,
          false_negative: integer,
          false_positive: integer,
          precision: float,
          recall: float,
          true_positive: integer
        }

  @spec init(map, integer, integer, integer) :: t
  def init(map, false_negative, false_positive, true_positive) do
    full_map =
      map
      |> Map.put(:false_negative, false_negative)
      |> Map.put(:false_positive, false_positive)
      |> Map.put(:precision, init_computed(true_positive, false_positive))
      |> Map.put(:recall, init_computed(true_positive, false_negative))
      |> Map.put(:true_positive, true_positive)

    struct(__MODULE__, full_map)
  end

  defp init_computed(true_positive, value), do: true_positive / (true_positive + value)
end

This way allows to pass map (don’t require struct), but forces to pass required keys separately.

Finally you can also do it in more pattern-matching like way:

defmodule Stats do
  required_keys = [:false_negative, :false_positive, :true_positive]
  @enforce_keys required_keys
  defstruct required_keys ++ [annotations: 0, precision: 0, recall: 0]

  @type init_map :: %{
          optional(:annotations) => integer,
          optional(:precision) => float,
          optional(:recall) => float,
          required(:false_negative) => integer,
          required(:false_positive) => integer,
          required(:true_positive) => integer
        }

  @type t :: %__MODULE__{
          annotations: integer,
          false_negative: integer,
          false_positive: integer,
          precision: float,
          recall: float,
          true_positive: integer
        }

  @spec init(init_map) :: t
  def init(
        %{
          false_negative: false_negative,
          false_positive: false_positive,
          true_positive: true_positive
        } = map
      ) do
    computed = %{
      precision: init_computed(true_positive, false_positive),
      recall: init_computed(true_positive, false_negative)
    }

    __MODULE__ |> struct(map) |> Map.merge(computed)
  end

  defp init_computed(true_positive, value), do: true_positive / (true_positive + value)
end

Which allows to pass required keys in map.

Last Post!

Eiji

Eiji

Yup, everything is correct. I saw that annotiations is not used for computing, but you pass it, so it’s more like map with optional data for me. If you want to use 2nd solution feel free to make this map optional by adding default argument like map \\ %{}.

Where Next?

Popular in Questions Top

rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a > b) do {:ok, "a"} end if (a < b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

Other popular topics Top

nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 55125 245
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New

We're in Beta

About us Mission Statement