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




















