Add items to dict in elixir - dictionary

I have a nested dict in elixir, from which I want to save the latest items in a new dict.
sorted_slides = [
%{
id: 1,
visual_events: [
%{entity_id: 1, payload: "abc"},
%{entity_id: 2, payload: "def"}
]
},
%{
id: 2,
visual_events: [
%{entity_id: 2, payload: "yui"},
%{entity_id: 3, payload: "def"},
%{entity_id: 4, payload: "ghi"},
]
},
%{
id: 3,
visual_events: [
%{entity_id: 2, payload: "ert"},
%{entity_id: 4, payload: "poi"},
]
}
]
dict = %{}
Enum.each(sorted_slides, fn slide ->
Enum.each(slide.visual_events, fn ve ->
eid = ve.entity_id
dict = Map.put(dict, eid, ve)
IO.inspect(dict)
end)
end)
IO.inspect(dict)
My original data structure contains items that may be overwritten by newer items. I want the new dict to be:
dict = %{
1 => %{entity_id: 1, payload: "abc"},
2 => %{entity_id: 2, payload: "ert"},
3 => %{entity_id: 3, payload: "def"},
4 => %{entity_id: 4, payload: "poi"}
}
I want the dict to save the changes made to it by each iteration, but I guess that scoping works different from some other languages here.
How would I achieve this in Elixir?

You can use Enum.flat_map/2 to extract the inner elements, and Map.new/2 to construct a new map from those elements. Map.new/2 will ensure the latest element prevails when there are duplicate keys.
sorted_slides
|> Enum.flat_map(fn %{visual_events: items} -> items end)
|> Map.new(fn %{entity_id: id} = map -> {id, map} end)
Result:
%{
1 => %{entity_id: 1, payload: "abc"},
2 => %{entity_id: 2, payload: "ert"},
3 => %{entity_id: 3, payload: "def"},
4 => %{entity_id: 4, payload: "poi"}
}

In the case you want to build a structure within a "loop", you can most of the times reach for Enum.reduce/3:
Enum.reduce(sorted_slides, %{}, fn slide, acc ->
Enum.into(slide.visual_events, acc, fn event ->
{event.entity_id, event}
end)
end)
The inner loop could be implemented with Enum.reduce/3 as well, but Enum.into/3 makes it slightly more compact.
Enum.each/2 is only meant to perform side-effects (like printing) but doesn't return any actual result, it just always returns :ok.
I guess that scoping works different from some other languages here.
Exactly, in Elixir you don't mutate existing structures, you create new structures and need to pass them around. This is typically the case of our acc accumulator above.
Side note: in Elixir, these are not called dicts but maps. There have been deprecated dicts structure in the past.

Related

How to store a map using :dets in Elixir?

I want to be able to store a map using :dets
Currently, that is the solution I am trying to implement:
# a list of strings
topics = GenServer.call(MessageBroker.TopicsProvider, {:get_topics})
# a map with each element of the list as key and an empty list as value
topics_map =
topics
|> Enum.chunk_every(1)
|> Map.new(fn [k] -> {k, []} end)
{:ok, table} = :dets.open_file(:messages, type: :set)
# trying to store the map
:dets.insert(table, [topics_map])
:dets.close(table)
However, I get
** (EXIT) an exception was raised:
** (ArgumentError) argument error
(stdlib 3.12) dets.erl:1259: :dets.insert(:messages, [%{"tweet" => [], "user" => []}])
How is it possible to accomplish this?
I have tested by erlang. You should convert the map to list first.
Following from dets:insert_new() doc
insert_new(Name, Objects) -> boolean() | {error, Reason}
Types
Name = tab_name()
Objects = object() | [object()]
Reason = term()
Inserts one or more objects into table Name. If there already exists some object with a key matching the key of any of the specified objects, the table is not updated and false is returned. Otherwise the objects are inserted and true returned.
test code
dets:open_file(dets_a,[{file,"/tmp/aab"}]).
Map = #{a => 2, b => 3, c=> 4, "a" => 1, "b" => 2, "c" => 4}.
List_a = maps:to_list(Map). %% <----- this line
dets:insert(dets_a,List_a).
Chen Yu's solution is good, but before getting it I already found another solution.
Basically, you can just add the map to a tuple
:dets.insert(table, {:map, topics_map})
Then, you can get this map by using
:dets.lookup(table, :map)
As I understood your intent, you want to store users and tweets under separate keys. For that, you need to construct a keyword list, not a map, in the first place.
topics = for topic <- topics, do: {topic, []}
# or topics = Enum.map(topics, &{&1, []})
# or topics = Enum.map(topics, fn topic -> {topic, []} end)
then you might use this keyword list to create dets.
{:ok, table} = :dets.open_file(:messages, type: :set)
:dets.insert(table, topics)
:dets.close(table)

Please how do i achieve the following using ramda

I have a random array of numbers 1 to five occurring in ramdom sometimes [1,1,1,1,2,2] etc. I am tasked with finding the value with highest occurrence all the the time regardless. I achieved that in javascript like below using a library called ramda here . After reading the documentation, i went with a solution like below.
// filter out duplication in array that way you can get the uniq represented numbers
const uniqueItems = R.uniq(params);
// use the unique numbers as keys and create a new array of object
const mappedItemsWithRepresentations = map((a) => ({ color: a, rep: params.filter(b => b === a).length }), uniqueItems);
// and then finally, select the item with highest rep and return it key
const maxRepItem = mappedItemsWithRepresentations.reduce((acc, curr) => acc.rep > curr.rep ? acc : curr, []);
return maxRepItem.key; // gives me the correct value i need
However, reading through more in the documentation and going through the example here, i realised there is a way i can combine the logic above and simply with ramda. I tried numerous attempt possible and the closest i could get are below.
const getMaxRep = curry(pipe(uniq, map((a) => ({ color: a, rep: filter(b => b === a).length })), pipe(max(pathEq("rep")), tap(console.log))));
console.log("Max Rep here", getMaxRep(params));
I also tried utilising the reduced feature here, all to no avail. Please how do i arrange achieve that ? Any help will be appreciated.
Ramda has R.countBy to get the number of occurrences. You can convert the resulting object of country to pairs [value, count], and then reduce it to find the pair with the highest count:
const { pipe, countBy, identity, toPairs, reduce, maxBy, last, head } = R
const fn = pipe(
countBy(identity), // count the occurrences
toPairs, // convert to pairs of [value, count]
reduce(maxBy(last), [0, 0]), // reduce to find the maximum occurrence
head, // get the actual value
Number, // convert back to an number
)
const arr = [1,1,1,1,2,2]
const result = fn(arr)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
A slight variation on this idea that collects values with the same count to an array. This will handle cases in which the frequency of several items is identical:
const { pipe, countBy, identity, toPairs, invert, reduce, maxBy, last, head, map } = R
const fn = pipe(
countBy(identity), // count the occurrences
invert, // combine all values with the same count
toPairs, // convert to pairs of [value, count]
reduce(maxBy(head), [0, 0]), // reduce to find the maximum occurrence
last, // get the actual values
map(Number), // convert back to numbers
)
const arr = [1,1,1,1,2,2,3,3,3,3]
const result = fn(arr)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
nice use case, try this:
const maxReduce = reduce(maxBy(last), [0,0])
const getMaxRep = pipe(countBy(identity), toPairs, maxReduce, head)
console.log(getMaxRep([1,1,1,1,2,2]))
countBy is a really nice start, sadly Ramda don't support reduce for object but we can convert to an array of arrays using toPairs function and finish the work.
It's not entirely clear to me what it is you're asking for.
But it might be something like this:
const maxRep = pipe (
countBy (identity),
toPairs,
map (zipObj(['color', 'rep'])),
reduce (maxBy (prop ('rep')), {rep: -Infinity}),
)
const params = [1, 2, 3, 4, 2, 3, 5, 2, 3, 2, 1, 1, 4, 5, 5, 3, 2, 5, 1, 5, 2]
console .log (
maxRep (params)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
<script> const {pipe, countBy, identity, toPairs, map, zipObj, reduce, maxBy, prop} = R </script>
We start with a list of values drawn from {1, 2, 3, 4, 5}, occuring in some random, multiply-occuring order.
With countBy(identity) we change the original list into something like
{"1": 4, "2": 6, "3": 4, "4": 2, "5": 5}
with the counts associated with each entry.
toPairs formats that as an array like
[["1", 4], ["2", 6], ["3", 4], ["4", 2], ["5", 5]]
(You could also use Object.entries here.)
Then by calling map (zipObj (['color', 'rep'])), we turn this into
[{"color": "1", "rep": 4}, {"color": "2", "rep": 6}, ...]
Finally, we reduce the result, using maxBy (prop ('rep')), which chooses the one with the maximum rep value. For the initial value to the max call, we create a dummy object, {rep: -Infinity} that will compare less than any in your list.
If you wanted to also keep that final intermediate structure, you could rename that function to makeReps, dropping off the last function in the pipeline, and then making a new maxRep out of it.
Then you could call
const reps = makeResps (params)
const maxVal = maxRep (reps)
and use both.
But all this presupposes that the value with color and rep properties is what you need. If you just need the count then the other solutions already here handle that fine.

Which approach to find multiple totals more closely follows the functional programming paradigm?

Say you have an array of objects with the structure like {id: 1, type: 'A', value: 10} want to find the total of type A, type B, and type C.
It would be more efficient to initialize the total variables and then loop through the array once, adding the the total variables based on type, than to use a reduce function for each total, in effect looping over the array 3 times.
However, from what I understand from the functional programming paradigm, functions should not manipulate anything outside of it internal scope and functions should have just one purpose, so the latter approach would be preferred.
Approach 1: initialize a variable for each of the three types, loop once and add to each total based on type
Approach 2: use reduce function for each total type.
Which one is preferred?
You can use a single fold/reduce if you use a record containing the three values as the state e.g. in clojure:
(defn sum-inputs [inputs]
(reduce (fn [acc {:keys [type value]}]
(update acc (keyword type) + value))
{:A 0 :B 0 :C 0}
inputs))
then
(sum-inputs [{:id 1 :type "A" :value 10}
{:id 2 :type "B" :value 12}
{:id 3 :type "B" :value 7}
{:id 4 :type "C" :value 40}])
in Javascript it looks like you can use Array.reduce:
const input = [{id: 1, type: "A", value: 4}, {id: 2, type: "B", value: 3}, {id: 3, type: "B", value: 9}, {id: 4, type: "C", value: 2}]
input.reduce(function(acc, i) { acc[i.type] += i.value; return acc; }, {A: 0, B: 0, C: 0})
note this mutates the accumulator record in place.

Pattern match against the other element of a map

I just want to know if there's a clean way to get the "other" element from a map in Elixir. By "other" I mean a second key-value pair, whose key I don't know.
Example: %{success: boolean, other => value}
This is the best I could come up with:
case map do
%{success: true} ->
other = map |> Map.delete(:success) |> Map.values |> List.first
# Do something with other
%{success: false} ->
error = map |> Map.delete(:success) |> Map.values |> List.first
# Do something with error
end
There's Map.pop/3 function, which accepts map and a key and returns a tuple with the value and a map without the key:
Map.pop %{ a: 1, b: 2 }, :a
# => {1, %{b: 2}}
and will refactor your code into something like:
case Map.pop(map, :success) do
{true, other_map} ->
other = other_map |> Map.values |> List.first
# Do something with other
{false, other_map} ->
error = other_map |> Map.values |> List.first
# Do something with error
end
I would go with old good Enum.reduce/3:
Enum.reduce %{success: true, foo: 42}, %{state: nil, map: %{}}, fn
{:success, value}, acc -> %{acc | state: value}
{key, value}, acc -> %{acc | map: Map.put(acc.map, key, value)}
end
#⇒ %{map: %{foo: 42}, state: true}
Now you might do whatever is needed without code duplication. Actually, the tuple is fine for collecting the result:
{success, map} =
Enum.reduce %{success: true, foo: 42}, {nil, %{}}, fn
{:success, value}, {_, acc} -> {value, acc}
{key, value}, {state, acc} -> {state, Map.put(acc, key, value)}
end
#⇒ {true, %{foo: 42}}
iex(9)> map = %{:success => true, {1,2,3} => 10}
%{:success => true, {1, 2, 3} => 10}
iex(10)> List.first(for {key, val} <- map, key != :success, do: val)
10

Converting Array of Arrays to Backbone Collection of Models

new to Backbone and underscore js here.
I have an array of arrays that I want to convert to a collection of models.
So it's like
{ {1, 2, 3, 4}, {5, 6, 7, 8}}
The second level of arrays is what's going into a backbone model. Right now, I have
collection.reset(_.map(results, (indvidualResults) -> new model(individualResults))
Which doesn't work as when I do a console.log(collection.pop) I get a function printed out. I think this is because I'm working with an array of arrays (but I could be wrong). How do I convert the second array into a model and then put that into a collection?
Reshape your raw data to look more like:
[{ first: 1, second: 2, third: 3, fourth: 4 }, { first: 5, second: 6, third: 7, fourth: 8}]
Assuming you have a model and collection defined something like:
var Model = Backbone.Model.extend({});
var Collection = Backbone.Collection.extend({
model: Model
});
Then just pass the array of attribute hashes into the reset method:
var results = [{ first: 1, second: 2, third: 3, fourth: 4 }, { first: 5, second: 6, third: 7, fourth: 8}];
var collection = new Collection();
collection.reset(results);
var model = collection.pop();
console.log(JSON.stringify(model.toJSON());

Resources