Transform nested clojure maps - vector

I am having trouble transforming a clojure map. The map has a vector as element and the vectors in turn have maps as elements.
The original map looks like this:
{"values" [{"sub" false, "name" "Adhoc"} {"acm" true, "list" true, "deval" true, "name" "Buyer"}]}
The maps within the vector always have the key "name" but the other keys may vary.
The name element should act as a key within the map.
As end result I need the original map to be transformed into this:
{"values" {"Adhoc" {"sub" false}, "Buyer" {"deval" true, "acm" true, "list" true}}
The problem is that the maps within the vector can have any amount of elements and I don't really know how to solve that with looping.
Any suggestions would be highly appreciated.

This would process the vector of maps for you:
(defn merge-by
[maps k]
(->> maps
(map (juxt #(get % k) #(dissoc % k)))
(into {})))
(merge-by [{"sub" false, "name" "Adhoc"}
{"acm" true, "list" true, "deval" true, "name" "Buyer"}]
"name")
;; => {"Adhoc" {"sub" false}, "Buyer" {"deval" true, "acm" true, "list" true}}
And this would process the outer map (if stored in my-map):
(update-in my-map ["values"] merge-by "name")

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)

How to remove leafs from a binary tree represented with list of lists?

I have a binary tree represented with nested lists:
[[[[], []], [[], []]], [[], [], []]]
And I want to write a function which removes the leafs (the empty lists) from it with recursion.
I tried with this but it doesn't even run. Could someone help me how to start?
def removeLeaf(tree):
for i in tree:
if type(tree[i]) is list:
return removeLeaf(tree[i])
elif tree[i] == []:
tree.pop(i)
return removeLeaf(tree)
input:
[[[[], []], [[], []]], [[], [], []]]
output:
[[[], []], []]
You shouldn't call resursion in the first part of if, because you are removing the empty list that is a leaf. Also the for sentence must be changed.
Here's the code:
def removeLeafs(tree):
for i in range(0,len(tree)-1):
if len(tree[i]) == 0:
tree.pop(i)
else
removeLeafs(tree[i])

Clojure: search/replace values in a dynamic nested map/seq

I have a dynamically created map data structure which will later on be parsed into JSON. Therefore the nesting levels, etc. are unknown and depend on the data.
If a key has multiple values, they are represented as maps inside a sequence.
Here is an example:
{:key "value"
:anotherKey "anotherValue"
:foo "test"
:others [ {:foo "test2"} {:foo "test3"} ]
:deeper {:nesting {:foo "test4"} }
}
I now want to search for the key :foo and append "/bar" to the value.
The result should return the modified map:
{:key "value"
:anotherKey "anotherValue"
:foo "test/bar"
:others [ {:foo "test2/bar"} {:foo "test3/bar"} ]
:deeper {:nesting {:foo "test4/bar"} }
}
What would be a clean and simple way to achieve that?
I tried a recursive approach but beside the memory problem of large data structures I'm struggling with returning my appended values.
There might be something simpler than this:
(clojure.walk/prewalk
(fn [m]
(if (and (map? m) (:foo m))
(update-in m [:foo] #(str % "/bar"))
m))
{:key "value"
:anotherKey "anotherValue"
:foo "test"
:others [{:foo "test2"} {:foo "test3"}]
:deeper {:nesting {:foo "test4"}}})
=>
{:anotherKey "anotherValue",
:key "value",
:deeper {:nesting {:foo "test4/bar"}},
:foo "test/bar",
:others [{:foo "test2/bar"} {:foo "test3/bar"}]}

Filtering elements of one list by looking at boolean values from the second list

I have two lists of equal length. I want to filter the elements of the first list by looking, if the element, with the same index in the second list, has a true boolean value.
Example:
[1,2,3,4,5]:int list
[true,false,false,true,false]:bool list
Expected result: [1,4]
I know two ways I could achieve this:
1) Write a function that takes two lists. For every element in the first list, that I want to append, check if the current(head) element of the second list is true.
2) Zip the two lists and filter it according to the boolean value.
There should be an easier to go about this, right?
Not really. The cleanest way to do this is probably
List.map (fn (x,y) => x) (List.filter (fn (x,y) => y) (ListPair.zip (L1,L2)))
or
List.map Option.valOf (List.filter Option.isSome (ListPair.map(fn (x,y) => if y then SOME x else NONE) (L1,L2)))
The recursive function isn't too bad, either:
fun foo ([],[]) = []
| foo ([],L) = raise Fail "Different lengths"
| foo (L,[]) = raise Fail "Different lengths"
| foo (x::xs, b::bs) = if b then x::foo(xs,bs) else foo(xs,bs)
Those are pretty much the two options you have; either recurse two lists at once, or combine them into one list of tuples and recurse that. There are several combinators you could use to achieve the latter.
val foo = [1,2,3,4,5];
val bar = [true,false,true,true,false];
val pairs = ListPair.zip (foo, bar)
Once zipped, here are two other ways you can do it:
val result = List.foldr (fn ((n,b), res) => if b then n::res else res) [] pairs
val result = List.mapPartial (fn (n,b) => if b then SOME n else NONE) pairs
The simplest is probably
ListPair.foldr (fn (x,y,z) => if y then x :: z else z) [] (L1, L2)
Don't know if ML has list comprehension, but if your language has it:
[ x | (x, True) <- zip xs ys ]

Creating Clojure vector(s) using values pulled from map(s) via keywords

I have a Clojure vector of maps used as a lookup table:
(def data
[{map1},{map2},...,{nth-map}])
Each map in the vector contains two key/value pairs:
{:key1 "Value1", :key2 "Value2"}
So the entire structure looks like this:
(def data
[{:key1 "Value1-1", :key2 "Value2-1"}, ; map1
{:key1 "Value1-2", :key2 "Value2-2"}, ; map2
|
|
V
{:key1 "Value1-n", :key2 "Value2-n"}]) ; nth-map
I'd like to migrate this into a database table. I already have a function that inserts values into a table row. It takes a single vector with each item in the vector representing a column in the row:
(defn insert-row [column1-value column2-value]
(sql/with-connection (System/getenv "DATABASE_URL")
(sql/insert-values
:table ; table name
[:column1 :column2] ; table column names
[column1-value column2-value]))) ; values to be inserted into row
What I need is a function that goes through the entire vector of maps and for each map it creates a vector of just that map's values:
[Value1 Value2]
I think I can use my existing database function insert-row as a parameter to map:
(map insert-row values-from-map)
where values-from-map represents an outer vector containing inner vectors (each containing its respective values) for each map in the original vector:
[[Value1-1 Value2-1]
[Value1-2 Value2-2]
[Value1-n Value2-n]]
This would take each vector created and pass it to the insert-values function.
I can create a single vector containing all the values from one keyword:
user=> (vec (map :key1 data))
["Value1" "Value2" ... "nth=Value"]
How would I create an outer vector containing all the inner vectors?
[[Value1-1 Value2-1][Value1-2 Value2-2]...[Value1-n Value2-n]]
user=> (vec (for [m data] ((juxt :key1 :key2) m)))
[["Value1-1" "Value2-1"] ["Value1-2" "Value2-2"]]
Assuming you still defined your vector of maps as "data":
(into [] (map #(into [] (vals %)) data))

Resources