Need help transforming / zipping a table into given map structure

Please I have a list of fields and a corresponding data-table.

I have been trying (unsuccessfully) to convert / zip them into the required map format below:

Any help would be appreciated please.

flds = ["field-1", "field-2", "field-3"]

data_table =
        [
         ["10100", "aaa", 3.0, 2.0], ["10100", "bbb", 5.0, 5.0], ["10100", "ccc", 2.0, 1.0],
         ["20100", "ddd", 3.0, 2.0],
         ["30100", "rrr", 3.0, 2.0], ["30100", "www", 2.0, 1.0]
        ]

output_map = 
    %{
     "10100" =>
      %{{1, "field-1"} => "aaa", {1, "field-2"} => 3.00, {1, "field-3"} => 2.00,
        {2, "field-1"} => "bbb", {2, "field-2"} => 5.00, {2, "field-3"} => 5.00,
        {3, "field-1"} => "ccc", {3, "field-2"} => 2.00, {3, "field-3"} => 1.00},
     "20100" =>
      %{{1, "field-1"} => "ddd", {1, "field-2"} => 3.00, {1, "field-3"} => 2.00},
     "30100" =>
      %{{1, "field-1"} => "rrr", {1, "field-2"} => 3.00, {1, "field-3"} => 2.00,
        {2, "field-1"} => "www", {2, "field-2"} => 2.00, {2, "field-3"} => 1.00}
    }

I am able to handle the simpler case where the fields match the data-table directly:

flds = ["field-1", "field-2", "field-3"]

input_data2 = 
[
 ["aaa", 3.00, 2.00], ["bbb", 5.00, 5.00], ["ccc", 2.00, 1.00]
]

expected_map = 
%{  {1, "field-1"} => "aaa", {1, "field-2"} => 3.0, {1, "field-3"} => 2.0, 
    {2, "field-1"} => "bbb", {2, "field-2"} => 5.0, {2, "field-3"} => 5.0,
    {3, "field-1"} => "ccc", {3, "field-2"} => 2.0, {3, "field-3"} => 1.0}

with this code:

defp combine(arr), do: (for {l, r} <- arr, {f, v} <- l, into: %{}, do: {{r,f}, v})

input_data2
        |> Stream.map(&Stream.zip(flds, &1))
        |> Stream.with_index(1)
        |> combine()
3 Likes

I was finally able to get it done … thanks to Enum.group_by

using the given data-table and fields:

flds = ["field-1", "field-2", "field-3"]

data_table =
        [
         ["10100", "aaa", 3.0, 2.0], ["10100", "bbb", 5.0, 5.0], ["10100", "ccc", 2.0, 1.0],
         ["20100", "ddd", 3.0, 2.0],
         ["30100", "rrr", 3.0, 2.0], ["30100", "www", 2.0, 1.0]
        ]

output_map = combine_group(data, flds)

gives:

output_map = 
    %{
     "10100" =>
      %{{1, "field-1"} => "aaa", {1, "field-2"} => 3.00, {1, "field-3"} => 2.00,
        {2, "field-1"} => "bbb", {2, "field-2"} => 5.00, {2, "field-3"} => 5.00,
        {3, "field-1"} => "ccc", {3, "field-2"} => 2.00, {3, "field-3"} => 1.00},
     "20100" =>
      %{{1, "field-1"} => "ddd", {1, "field-2"} => 3.00, {1, "field-3"} => 2.00},
     "30100" =>
      %{{1, "field-1"} => "rrr", {1, "field-2"} => 3.00, {1, "field-3"} => 2.00,
        {2, "field-1"} => "www", {2, "field-2"} => 2.00, {2, "field-3"} => 1.00}
    }

not pretty, but it works

defp combine(data), do: (for {l, r} <- data, {f, v} <- l, into: %{}, do: {{r,f}, v})
defp combine(data, flds) do
  data
  |> Stream.map(&Stream.zip(flds, &1))
  |> Stream.with_index(1)
  |> combine()
end

defp combine_fields(data, flds), do: (for {k, m} <- data, into: %{}, do: {k, combine(m, flds)})
defp combine_group(data, flds) do
  data
  |> Enum.group_by(&Enum.at(&1, 0), &Enum.drop(&1, 1))
  |> combine_fields(flds)
end
defp combine_fields2(data), do: (for {k, m} <- data, into: %{}, do: {k, combine_group(m, flds)})
defp combine_group2(data, flds) do
  data
  |> Enum.group_by(&Enum.at(&1, 0), &Enum.drop(&1, 1))
  |> combine_fields2(flds)
end
2 Likes

Another way of doing it:

defmodule Test do

  defp extractData(rec, seq_no, fields) do
    for {n,v} <- (Enum.zip fields, (tl rec)), do: {{seq_no, n}, v} 
  end

  defp addData(rec, fields, {last_no, rest}) do
    seq_no = last_no + 1

    {seq_no, [extractData(rec, seq_no, fields) | rest]}
  end

  defp structureData({key, {_, data_list}}) do
    { key, (for n <- (List.flatten data_list), into: %{}, do: n) }
  end
  
  def process(data, fields) do

    addRecord = fn rec, rec_map ->
      Map.update(
        rec_map,
        hd(rec),
        {1, [extractData(rec, 1, fields)]},
        &(addData(rec, fields, &1))
      )
    end

    for n <- (Enum.reduce data, %{}, addRecord), into: %{}, do: (structureData n) 
  end
end

flds = ["field-1", "field-2", "field-3"]
data_table =
  [
    ["10100", "aaa", 3.0, 2.0],
    ["10100", "bbb", 5.0, 5.0],
    ["10100", "ccc", 2.0, 1.0],
    ["20100", "ddd", 3.0, 2.0],
    ["30100", "rrr", 3.0, 2.0],
    ["30100", "www", 2.0, 1.0]
  ]
  
IO.inspect (Test.process data_table, flds)
2 Likes