So, it’s quite possible for someone to just give you an answer here, but I think if someone does so they are doing you a disservice. The code you have here indicates that you haven’t quite internalized that Elixir is a language with immutable data.
Start with something simple. How would you count a simple list?
Given items = [1, 2, 3, 4, 5] how do you count the number of things inside items without using the built in length? Your choices are manual recursion, or Enum.reduce. For the purposes of figuring out your more nested list situation, Enum.reduce/3 is the function you’ll want to look at.
Can you explain more what the algorithm is intended to do? The code is quite hard to reason about (for me).
I think the idea is to generate a list of maps where each map has an :id and for each :id there are 0…n maps where n is :rand.uniform(max), For each map there may be an :item_id of some value that comes from items (but Im not sure how you decide which item.
in Elixir if you’re using Enum.at/2 and indexes into lists its a pretty good chance you’re not in the Elixir or functional groove.
this code is for testing version,
so i modified code for easy read
yes,
the final target is generate a records list from config,
and each record have chance get item from list items
list items like a item pool, when take one, remove one ( so i using pop_at 0 )
my first question is i can’t access items list from nested for-loop
First thing, try to stop thinking about for-loop. There isn’t a thing like a loop in Eixir. A loop requires to mutate the state for each iteration, Elixir is immutable.
Second thing, dont try to reproduce solution from other languages. Start with the problem, or “what is the final intent of your code”, and from that find the resources the language provides you to achieve that.
The pattern match can even occur right on top of an argument in a function parameter like in Kilo.add_item_r/2 below:
# file: kilo.exs
defmodule Kilo do
def put_item(record, item),
do: Map.put record, :item, item
# -- function clause with pattern matching
def add_item_r(record, {[item|other_items], result}), # the tuple, the list and it's head and tail
do: {other_items, [(put_item record, item)|result]} # same list syntax is used to build a new list
def add_item_m({item, record}), # pattern matching a tuple
do: put_item record, item
# -- multi-clause function
def add_item_c({:none, record}), # each clause processing a separate case
do: record # identified by pattern matching
def add_item_c({item, record}), # pattern matching is a conditional construct
do: put_item record, item
end
ids = Enum.to_list 0..9 # Using a range to create a list
IO.puts "ids #{inspect ids}"
items = for id <- ids, do: 10 - id # for comprehension
IO.puts "items #{inspect items}"
items_again = Enum.map ids, &(10 - &1) # same thing - this time with Enum.map
IO.puts "item_again #{inspect items_again}" # with a captured partial function application
recs = Enum.map ids, &(Map.new [{:id, &1}]) # creating Map with a key-value tuple list
IO.puts "recs #{inspect recs}"
{_,recsr} = Enum.reduce recs, {items,[]}, &Kilo.add_item_r/2 # Adding items using reduce
IO.puts "recsr #{inspect recsr}" # with a captured function
# note the order of the result
IO.puts "Enum.reverse recsr #{inspect (Enum.reverse recsr)}"
recsz =
items # again same thing
|> Enum.zip(recs) # this time with
|> Enum.map(&Kilo.add_item_m/1) # zip-mapping with the pipe operator
IO.puts "recsz #{inspect recsz}" # with a captured function
scatter_items = [:none,1,:none,:none,2,:none,:none,:none,3,:none]
recsc =
scatter_items # conditional adding
|> Enum.zip(recs) # using zip-mapping and
|> Enum.map(&Kilo.add_item_c/1) # the multi-clause function
IO.puts "recsc #{inspect recsc}" # `Kilo.add_item_c/1`
I don’t think this is exactly your algorithm, but its close and its more idiomatic Elixir (likely far from perfect however):
defmodule GenResult do
def generate do
items = [1, 2, 3, 4, 5]
definitions = [
%{ id: 0, count: %{ min: 1, max: 2 } },
%{ id: 1, count: %{ min: 1, max: 5 } }
]
{records, _items} = Enum.reduce definitions, {[], items}, fn definition, {results, items} ->
{records, items} = define_items(definition, items, :rand.uniform())
{records ++ results, items}
end
records
end
# Empty list result
def define_items(_definition, items, probability) when probability < 0.5 do
{[], items}
end
# No more items to be consumed
def define_items(_definition, [], _probability) do
{[], []}
end
def define_items(%{id: id, count: %{max: max}}, items, _probability) do
gen_for_define(id, :rand.uniform(max), :rand.uniform(), items, 0, [])
end
# No more items left
def gen_for_define(_id, _max, _add_item, [] = items, _count, records) do
{records, items}
end
# Maximum number of records generated for this definition
def gen_for_define(_id, max, _add_item, items, count, records) when count > max do
{records, items}
end
# Generate a record with a child
def gen_for_define(id, max, add_item, [item | rest], count, records) when add_item < 0.8 do
new_records = [%{id: id, item_id: item} | records]
gen_for_define(id, max, :rand.uniform(), rest, count + 1, new_records)
end
# Generate a bare record
def gen_for_define(id, max, _add_item, items, count, records) do
new_records = [%{id: id} | records]
gen_for_define(id, max, :rand.uniform(), items, count + 1, new_records)
end
end