DimWell

DimWell

Hi,
I am relatively new to Elixir and need a support from more advanced users.
I try to do a simple thing → Flatten Map
But Elixir and functional programming in general is a bit new to me.

I found online a solution, but it looks overcomplicated.
I wonder if maybe there is a solution out of the box provided by the language.
I tried to find something like Map.flatten(%{a: 1, %{b: 2}}) but with no success.

It would be great if you could advice that is the best practice ?

Meanwhile, I found the solution online, please see the code below.

defmodule Json do
  def flatten(%{} = json) do
    json
    |> Map.to_list()
    |> to_flat_map(%{})
  end
  def flatten(%{} = json) when json == %{}, do: %{}

  defp to_flat_map([{_k, %{} = v} | t], acc), do: to_flat_map(Map.to_list(v), to_flat_map(t, acc))
  defp to_flat_map([{k, v} | t], acc), do: to_flat_map(t, Map.put_new(acc, k, v))
  defp to_flat_map([], acc), do: acc
end

%{id: "1", foo: %{bar: %{qux: "hello world"}, baz: 123}}
|> Json.flatten()
|> IO.inspect()

# %{baz: 123, id: "1", qux: "hello world"}

Currently, due to a lack of knowledge I can’t evaluate this solution.

I would highly appreciate if someone could comment on the above code and mention good and bad points ?

Showing Posts 1 to 9

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Can you speak a bit about your use case? What do you want to do if sub-maps include the same key as a parent map ie: %{foo: %{bar: 1}, bar: 2}

DimWell

DimWell OP

Wow, thanks for super fast reply !
In my scenario it is impossible that that sub maps would include same key/s as parent map.
All keys are always unique.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Are there any datastructures other than maps, like lists?

DimWell

DimWell OP

Hm… I don’t think so.
I see more background information is needed.

Here is explanation that I am doing.

  1. I use HTTPoision to fetch Json data
  2. I convert response to map using Poision.parse
  3. To my understanding I receive list with maps in other words (enumerable)
  4. I Enum.each(item, fn map → some modification end)

Here I receive a map, that contains other maps - sub maps.
Before, I can proceed further I need to flatten the map.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Here is a slightly simpler implementation that you may be able to evaluate more easily:

defmodule Foo do

  def flatten_map(map) when is_map(map) do
    map
    |> Map.to_list
    |> do_flatten([])
    |> Map.new
  end

  defp do_flatten([], acc), do: acc

  defp do_flatten([{_k, v} | rest], acc) when is_map(v) do
    v = Map.to_list(v)
    flattened_subtree = do_flatten(v, acc)
    do_flatten(flattened_subtree ++ rest, acc)
  end

  defp do_flatten([kv | rest], acc) do
    do_flatten(rest, [kv | acc])
  end
end
DimWell

DimWell OP

Thank your for the example, I really appreciate!
Yes, your example has more clear process flow.
But, one step is still not very obvious to me.

First of all, I checked what data type I receive from Poison, and it is a list.
So, some changes needs to be applied:

def flatten_map(list) when is_list(list) do
list
|> do_flatten()
|> Map.new
end

So, the process flow is the following:

  1. I call flatten_map(list) and submit list as an argument.
  2. Function “flatten_map” checks if argument is a list , and if yes, it proceeds.
  3. The function “do_flatten()” is called with a list and accumulator [ ] arguments are passed.
  4. If list is empty, then due to pattern matching the following function is called and empty list is returned:

defp do_flatten(, acc), do: acc

please correct me if I am wrong.

  1. Else if list is not empty the following function will be pattern matched:

defp do_flatten([{_k, v} | rest], acc) when is_map(v) do
v = Map.to_list(v)
flattened_subtree = do_flatten(v, acc)
do_flatten(flattened_subtree ++ rest, acc)
end

This function receives 2 arguments, list and accumulator.
List is split into: Head | Tail , this way we take first item of the list.
At the same time we pattern Head with {_k, v} and then check if v is a map.

It is not clear how the pattern matching works here ?
At this point map %{key, value} is pattern matched to tuple {_k, v } ?
Or is it a typo and should be a %{ _k, v }

Then we know that “v” is a map, so we convert it to list.
Then ** do_flatten** for subbtree → this goes recursevly until reaches the point that it maches: [], acc and then acc is returned.
Then:

do_flatten(flattened_subtree ++ rest, acc)

Here we take list of subtree and join with Tail (rest) and repeat the process passing, list with accumulator. So far seems clear to me…

The only question left, when last function is called ?
In which scenario the pattern match will be matched ?

defp do_flatten([kv | rest], acc) do
do_flatten(rest, [kv | acc])
end

While I was re-reading my reply understood, that if v is not a map, then the last function is executed.

Then we call do_flatten and pass Tail (rest) as a first argument, and a list by using kv as Head and acc as Tail ?

I would highly appreciate if you could correct me if my understanding is wrong.

DimWell

DimWell OP

I spend few more minutes, and finally understood, that we converted the map to list, and therefore we don’t need to pattern match to map %{k, v}.

Furthermore, no changes were needed.
As according to steps I described, I enumerate the list and take the Map as each item, therefore your code is exactly that I need.

Thank you very much , one more time!

idi527

idi527

You don’t pattern match maps with %{ _k, v }, you do it with %{^k => v}.

Note the pin.

DimWell

DimWell OP

In this scenario, I realised that I don’t need to pattern match to map.
But in other case scenario, I would definitely forgot about the pin.
Definitely need to make a note about the pin :slight_smile
Thank you for the advise!

— All posts loaded —

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
jaybe78
Hello, I’m developing a online persistent chat system (what’s app) like using elixir/dynamodb/aws for a mobile app(flutter). The diffic...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New

Other Trending Topics Top

garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New
webofbits
Aludel - LLM Evaluation Workbench Aludel is an embeddable Phoenix LiveView dashboard for evaluating and comparing LLM prompts across mult...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews