I’m new to functional programming and I’d like to know the idiomatic way to convert a flat list of maps into a nested list.
For some background, I’m experimenting with Ecto. I have a table with a recursive relationship to itself. That is to say that there is a parent_id field which has a foreign key pointing to the id field of the same table. I’m trying to get a list of just the rows where the parent_id is null at the top level with all of its children in a nested list.
flat = [
    %{id: 1, name: "region 1", parent_region_id: nil},
    %{id: 2, name: "subregion 1", parent_region_id: 1},
    %{id: 3, name: "subregion 2", parent_region_id: 1},
    %{id: 4, name: "region 2", parent_region_id: nil},
    %{id: 5, name: "subregion 3", parent_region_id: 4},
    %{id: 6, name: "subregion 4", parent_region_id: 4}
  ]
nested = [
    %{
      id: 1,
      name: "region 1",
      parent_region_id: nil,
      subregions: [%{id: 2, name: "subregion 1", parent_id: 1}, %{id: 3, name: "subregion 2", parent_region_id: 1}]
    },
    %{
      id: 4,
      name: "region 2",
      parent_region_id: nil,
      subregions: [%{id: 5, subregion: "subregion 3", parent_region_id: 4}, %{id: 6, name: "subregion 4", parent_region_id: 4}]
    }
  ]
I can achieve this by using Repo.preload(:subregions), but that would add an unnecessary join. Any tips on an elegant way to convert flat into nested?
Here is the migration script.
defmodule Bazaar.Repo.Migrations.CreateRegions do
  use Ecto.Migration
  def change do
    create table(:regions) do
      add :name, :string
      add :parent_region_id, references(:regions), null: true
      timestamps()
    end
  end
end
And this is the schema.
defmodule Bazaar.Geoscheme.Region do
  use Ecto.Schema
  import Ecto.Changeset
  schema "regions" do
    field(:name, :string)
    belongs_to(:parent_region, Bazaar.Geoscheme.Region)
    has_many(:subregions, Bazaar.Geoscheme.Region, foreign_key: :parent_region_id)
    timestamps()
  end
  @doc false
  def changeset(region, attrs) do
    region
    |> cast(attrs, [:name, :parent_region_id])
    |> validate_required([:name])
  end
end




















