├── .tool-versions ├── test ├── test_helper.exs ├── generators.ex ├── zipper_test.exs └── rose_tree_test.exs ├── img └── rose_tree_diagram.png ├── .pre-commit-config.yaml ├── .gitignore ├── circle.yml ├── mix.lock ├── mix.exs ├── config └── config.exs ├── README.org └── lib ├── rose_tree.ex └── zipper └── zipper.ex /.tool-versions: -------------------------------------------------------------------------------- 1 | erlang 19.2 2 | elixir 1.4.5 3 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | Code.require_file("test/generators.ex") 3 | -------------------------------------------------------------------------------- /img/rose_tree_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smpoulsen/rose_tree/HEAD/img/rose_tree_diagram.png -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | - repo: local 2 | hooks: 3 | - id: mix-test 4 | name: 'elixir: mix test' 5 | entry: mix test 6 | language: system 7 | files: \.exs$ 8 | - id: mix-dialyzer 9 | name: 'elixir: mix dialyzer' 10 | entry: mix dialyzer 11 | language: system 12 | files: \.ex$ 13 | - id: mix-credo 14 | name: 'elixir: mix credo' 15 | entry: mix credo 16 | language: system 17 | files: \.ex$ 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | #general: 2 | machine: 3 | environment: 4 | PATH: "$HOME/.asdf/bin:$HOME/.asdf/shims:$PATH" 5 | dependencies: 6 | cache_directories: 7 | - ~/.asdf 8 | pre: 9 | - if ! asdf | grep version; then git clone https://github.com/HashNuke/asdf.git ~/.asdf; fi 10 | - if ! asdf plugin-list | grep erlang; then asdf plugin-add erlang https://github.com/HashNuke/asdf-erlang.git; fi 11 | - if ! asdf plugin-list | grep elixir; then asdf plugin-add elixir https://github.com/HashNuke/asdf-elixir.git; fi 12 | - erlang_version=$(awk '/erlang/ { print $2 }' .tool-versions) && asdf install erlang ${erlang_version} 13 | - elixir_version=$(awk '/elixir/ { print $2 }' .tool-versions) && asdf install elixir ${elixir_version} 14 | - yes | mix deps.get 15 | - yes | mix local.rebar 16 | test: 17 | override: 18 | - mix test 19 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, 2 | "credo": {:hex, :credo, "0.8.2", "aa410c8f27c5fab00cab24f8477782bde9100b73d31debdce46625f097cc0e32", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, 3 | "dialyxir": {:hex, :dialyxir, "0.5.0", "5bc543f9c28ecd51b99cc1a685a3c2a1a93216990347f259406a910cf048d1d7", [:mix], [], "hexpm"}, 4 | "earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], [], "hexpm"}, 5 | "ex_doc": {:hex, :ex_doc, "0.16.2", "3b3e210ebcd85a7c76b4e73f85c5640c011d2a0b2f06dcdf5acdb2ae904e5084", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, 6 | "excheck": {:hex, :excheck, "0.5.3", "7326a29cc5fdb6900e66dac205a6a70cc994e2fe037d39136817d7dab13cdabf", [:mix], [], "hexpm"}, 7 | "triq": {:git, "https://github.com/triqng/triq.git", "0591543a91bd42cd8466e441765f5b49dbef3443", []}} 8 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule RoseTree.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :rose_tree, 6 | version: "0.2.0", 7 | elixir: "~> 1.4", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | description: description(), 11 | package: package(), 12 | deps: deps()] 13 | end 14 | 15 | def application do 16 | [extra_applications: [:logger]] 17 | end 18 | 19 | def description() do 20 | """ 21 | A rose tree is a recursive n-ary tree. rose_tree implements the data structure 22 | and provides raw and zipper-based traversal and manipulation. 23 | """ 24 | end 25 | 26 | defp deps do 27 | [ 28 | {:ex_doc, ">= 0.0.0", only: :dev}, 29 | {:credo, "~> 0.8.2", only: [:dev, :test]}, 30 | {:dialyxir, "~> 0.5.0", only: [:dev, :test]}, 31 | {:excheck, "~> 0.5.3", only: :test}, 32 | {:triq, github: "triqng/triq", only: :test}, 33 | ] 34 | end 35 | 36 | defp package do 37 | [ 38 | name: :rose_tree, 39 | licenses: ["BSD2"], 40 | maintainers: ["Sylvie Poulsen"], 41 | links: %{ 42 | "GitHub" => "https://github.com/smpoulsen/rose_tree", 43 | } 44 | ] 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :rose_tree, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:rose_tree, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /test/generators.ex: -------------------------------------------------------------------------------- 1 | defmodule Generators do 2 | use ExUnit.CaseTemplate 3 | 4 | using do 5 | quote do 6 | use ExCheck 7 | 8 | @node_values [:a, :b, :c, 0, 1, 2, 3, "hello", "world"] 9 | 10 | # Generator for RoseTrees 11 | def rose_tree(n) do 12 | domain(:rose_tree, 13 | fn(self, size) -> 14 | {_, node} = :triq_dom.pick(elements(@node_values), size) 15 | {_, child_count} = :triq_dom.pick(elements([0,1, 2]), size) 16 | children = if child_count > 0 do 17 | gen_child_trees(size, n) 18 | else 19 | [] 20 | end 21 | tree = %RoseTree{node: node, children: children} 22 | {self, tree} 23 | end, fn 24 | (self, %RoseTree{node: node, children: children}) -> 25 | new_node = :x 26 | new_children = if Enum.empty?(children), do: [], else: tl(children) 27 | tree = %RoseTree{node: new_node, children: new_children} 28 | {self, tree} 29 | end) 30 | end 31 | 32 | defp gen_child_trees(size, count) do 33 | for n <- Range.new(0, count) do 34 | {_, child} = :triq_dom.pick(rose_tree(n), size) 35 | child 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/zipper_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ZipperTest do 2 | use ExUnit.Case 3 | use Generators 4 | alias RoseTree.Zipper 5 | doctest Zipper 6 | 7 | property "nth_child/2 followed by ascend/1 results in no change" do 8 | for_all {tree} in such_that({t} in {rose_tree(2)} when length(t.children) > 0) do 9 | tree 10 | |> Zipper.from_tree() 11 | |> Zipper.nth_child(0) 12 | |> Zipper.lift(&Zipper.ascend/1) 13 | |> Zipper.lift(&Zipper.to_tree/1) == tree 14 | end 15 | end 16 | 17 | @tag iterations: 500 18 | property "to_root/1 always returns to the tree's root" do 19 | for_all {tree} in {rose_tree(5)} do 20 | zipper = Zipper.from_tree(tree) 21 | implies (Zipper.lift(Zipper.nth_child(zipper, 1), &Zipper.nth_child(&1, 1)) != {:error, {:rose_tree, :no_children}}) do 22 | zipper 23 | |> Zipper.nth_child(1) 24 | |> Zipper.lift(&Zipper.nth_child(&1, 1)) 25 | |> Zipper.lift(&Zipper.to_root/1) 26 | |> Zipper.to_tree() == tree 27 | end 28 | end 29 | end 30 | 31 | property "result of to_root/1 is root?/1 and !has_parent/1" do 32 | for_all {tree} in {rose_tree(0)} do 33 | zipper = Zipper.from_tree(tree) 34 | implies Zipper.has_children?(zipper) == true do 35 | root = zipper 36 | |> Zipper.first_child() 37 | |> Zipper.lift(&Zipper.to_root/1) 38 | Zipper.root?(root) == true && Zipper.has_parent?(root) == false 39 | end 40 | end 41 | end 42 | 43 | property "result of to_leaf/1 has no children" do 44 | for_all {tree} in {rose_tree(1)} do 45 | tree 46 | |> Zipper.from_tree() 47 | |> Zipper.to_leaf() 48 | |> Zipper.has_children?() == false 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /test/rose_tree_test.exs: -------------------------------------------------------------------------------- 1 | defmodule RoseTreeTest do 2 | use ExUnit.Case 3 | use Generators 4 | doctest RoseTree 5 | 6 | property "add_child/2 adds a child to a tree" do 7 | for_all {t1, t2} in {rose_tree(0), rose_tree(1)} do 8 | added_child = RoseTree.add_child(t1, t2) 9 | RoseTree.is_child?(added_child, t2) == true 10 | end 11 | end 12 | 13 | property "child_values/1 returns a list of child values" do 14 | for_all tree in rose_tree(3) do 15 | child_values = for child <- tree.children, do: child.node 16 | RoseTree.child_values(tree) == child_values 17 | end 18 | end 19 | 20 | property "merge_nodes/2 merges nodes and their children" do 21 | for_all {t1, t2} in such_that({tt1, tt2} in {rose_tree(1), rose_tree(1)} when tt1.node == tt2.node) do 22 | merged = RoseTree.merge_nodes(t1, t2) 23 | all_children = t1 24 | |> RoseTree.child_values 25 | |> MapSet.new() 26 | |> MapSet.union(MapSet.new(RoseTree.child_values(t2))) 27 | 28 | Enum.all?(RoseTree.child_values(merged), &MapSet.member?(all_children, &1)) 29 | end 30 | end 31 | 32 | property "is_child?/2 is a predicate" do 33 | for_all {t1, t2} in {rose_tree(2), rose_tree(2)} do 34 | RoseTree.is_child?(t1, t2) == Enum.member?(RoseTree.child_values(t1), t2.node) 35 | end 36 | end 37 | 38 | property "pop_child/1 returns the first of a node's children" do 39 | for_all {tree} in {rose_tree(3)} do 40 | res = RoseTree.pop_child(tree) 41 | case tree do 42 | %RoseTree{children: [h | t]} -> 43 | res == {h, %{tree | children: t}} 44 | _ -> 45 | res == {nil, tree} 46 | end 47 | end 48 | end 49 | 50 | property "pop_child_at/2 returns the node's child at an index" do 51 | for_all {tree, idx} in such_that({t, i} in {rose_tree(3), int(0, 5)} 52 | when i < Enum.count(t.children)) do 53 | {child, new_tree} = RoseTree.pop_child_at(tree, idx) 54 | case tree do 55 | %RoseTree{children: []} -> 56 | child == nil && new_tree == tree 57 | _ -> 58 | child == Enum.at(tree.children, idx) && Enum.count(new_tree.children) < Enum.count(tree.children) 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * RoseTree 2 | [[https://circleci.com/gh/tpoulsen/rose_tree][https://circleci.com/gh/tpoulsen/rose_tree.svg?style=svg]] 3 | [[https://img.shields.io/hexpm/v/rose_tree.svg]] 4 | 5 | /An n-ary tree by any other name.../ 6 | 7 | A [[https://en.wikipedia.org/wiki/Rose_tree][rose tree]] is an n-ary tree in which each node has zero or more children, all 8 | of which are themselves rose trees. 9 | 10 | Ex: 11 | [[img/rose_tree_diagram.png]] 12 | 13 | Up-to-date documentation can be found on [[https://hexdocs.pm/rose_tree/api-reference.html][hex]]. 14 | 15 | ** Functions 16 | *** RoseTree 17 | **** Tree creation 18 | + =RoseTree.new/0= 19 | + =RoseTree.new/1= 20 | + =RoseTree.new/2= 21 | + =RoseTree.from_map/1= 22 | **** Node insertion/removal/validation 23 | + =RoseTree.add_child/2= 24 | + =RoseTree.add_child/2= 25 | + =RoseTree.pop_child/1= 26 | + =RoseTree.pop_child_at/2= 27 | + =RoseTree.elem_at/2= 28 | **** Node manipulation 29 | + =RoseTree.map/2= 30 | + =RoseTree.update_node/3= 31 | + =RoseTree.update_children/3= 32 | + =RoseTree.merge_nodes/2= 33 | **** Traversal 34 | + =RoseTree.to_list/1= 35 | + =RoseTree.paths/1= 36 | + =RoseTree.child_values/1= 37 | **** Predicates 38 | + =RoseTree.is_child?/2= 39 | *** RoseTree.Zipper 40 | **** Creation/Extraction 41 | + =RoseTree.Zipper.to_tree/1= 42 | + =RoseTree.Zipper.from_tree/1= 43 | **** Traversal 44 | + =RoseTree.Zipper.nth_child/2= 45 | + =RoseTree.Zipper.ascend/1= 46 | + =RoseTree.Zipper.to_root/1= 47 | + =RoseTree.Zipper.next_sibling/1= 48 | + =RoseTree.Zipper.previous_sibling/1= 49 | + =RoseTree.Zipper.last_child/1= 50 | + =RoseTree.Zipper.first_child/1= 51 | + =RoseTree.Zipper.find_child/2= 52 | *** Modification 53 | + =RoseTree.Zipper.prune/1= 54 | + =RoseTree.Zipper.modify/2= 55 | + =RoseTree.Zipper.lift/2= 56 | + =RoseTree.Zipper.insert_left/2= 57 | + =RoseTree.Zipper.insert_right/2= 58 | + =RoseTree.Zipper.insert_first_child/2= 59 | + =RoseTree.Zipper.insert_last_child/2= 60 | + =RoseTree.Zipper.insert_nth_child/3= 61 | *** Predicates 62 | + =RoseTree.Zipper.root?/1= 63 | + =RoseTree.Zipper.leaf?/1= 64 | + =RoseTree.Zipper.first?/1= 65 | + =RoseTree.Zipper.last?/1= 66 | + =RoseTree.Zipper.only_child?/1= 67 | + =RoseTree.Zipper.has_parent?/1= 68 | + =RoseTree.Zipper.has_children?/1= 69 | 70 | ** Protocols 71 | =RoseTree= implements the [[https://hexdocs.pm/elixir/Enumerable.html][Enumerable]] protocol, so all applicable [[https://hexdocs.pm/elixir/Enum.html][Enum]] and [[https://hexdocs.pm/elixir/Stream.html][Stream]] functions are usable with a =RoseTree=. 72 | 73 | ** Installation 74 | Add =rose_tree= to your list of dependencies in =mix.exs=: 75 | #+BEGIN_SRC elixir 76 | def deps do 77 | [{:rose_tree, "~> 0.2.0"}] 78 | end 79 | #+END_SRC 80 | -------------------------------------------------------------------------------- /lib/rose_tree.ex: -------------------------------------------------------------------------------- 1 | defmodule RoseTree do 2 | @moduledoc """ 3 | A rose tree is an n-ary tree in which each node has zero or more children, all 4 | of which are themselves rose trees. 5 | 6 | Trees are unbalanced and children unordered. 7 | """ 8 | 9 | @enforce_keys [:node, :children] 10 | defstruct node: :empty, children: [] 11 | 12 | @type t :: %RoseTree{node: any(), children: [%RoseTree{}]} 13 | 14 | @doc """ 15 | Create a new, empty rose tree. 16 | 17 | ## Example 18 | iex> RoseTree.new() 19 | %RoseTree{node: :empty, children: []} 20 | """ 21 | @spec new() :: RoseTree.t 22 | def new(), do: %RoseTree{node: :empty, children: []} 23 | 24 | @doc """ 25 | Initialize a new rose tree with a node value and empty children. 26 | 27 | ## Examples 28 | iex> RoseTree.new(:a) 29 | %RoseTree{node: :a, children: []} 30 | """ 31 | @spec new(any()) :: RoseTree.t 32 | def new(value), do: %RoseTree{node: value, children: []} 33 | 34 | @doc """ 35 | Initialize a new rose tree with a node value and children. 36 | 37 | ## Examples 38 | iex> b = RoseTree.new(:b) 39 | ...> RoseTree.new(:a, [b, :c]) 40 | %RoseTree{node: :a, children: [ 41 | %RoseTree{node: :b, children: []}, 42 | %RoseTree{node: :c, children: []} 43 | ]} 44 | """ 45 | @spec new(any(), any()) :: RoseTree.t 46 | def new(value, children) do 47 | children_trees = children 48 | |> List.wrap() 49 | |> Enum.map(fn(child) -> if is_rose_tree?(child), do: child, else: new(child) end) 50 | %RoseTree{node: value, children: children_trees} 51 | end 52 | 53 | @doc """ 54 | Add a new child to a tree. 55 | 56 | If the tree already has children, the new child is added to the front. 57 | 58 | If the value of the new child's node is already present in the tree's children, 59 | then the new child's children are merged with the existing child's children. 60 | 61 | ## Examples 62 | iex> hello = RoseTree.new(:hello) 63 | ...> world = RoseTree.new(:world) 64 | ...> RoseTree.add_child(hello, world) 65 | %RoseTree{node: :hello, children: [ 66 | %RoseTree{node: :world, children: []} 67 | ]} 68 | 69 | iex> hello = RoseTree.new(:hello) 70 | ...> world_wide = RoseTree.new(:world, :wide) 71 | ...> world_champ = RoseTree.new(:world, :champ) 72 | ...> dave = RoseTree.new(:dave) 73 | ...> hello 74 | ...> |> RoseTree.add_child(world_wide) 75 | ...> |> RoseTree.add_child(world_champ) 76 | ...> |> RoseTree.add_child(dave) 77 | %RoseTree{children: [ 78 | %RoseTree{children: [], node: :dave}, 79 | %RoseTree{children: [ 80 | %RoseTree{node: :wide, children: []}, 81 | %RoseTree{node: :champ, children: []}], 82 | node: :world}], 83 | node: :hello} 84 | """ 85 | @spec add_child(RoseTree.t, RoseTree.t) :: RoseTree.t 86 | def add_child(%RoseTree{node: n, children: children} = tree, child) do 87 | if is_child?(tree, child) do 88 | {matching_node, node_index} = children 89 | |> Stream.with_index() 90 | |> Enum.find(fn({c, _i}) -> c.node == child.node end) 91 | merged_children = merge_nodes(matching_node, child) 92 | updated_children = List.replace_at(children, node_index, merged_children) 93 | %RoseTree{node: n, children: updated_children} 94 | else 95 | %RoseTree{node: n, children: [child | children]} 96 | end 97 | end 98 | 99 | @doc """ 100 | Retrieve a list of the values of a node's immediate children. 101 | 102 | ## Examples 103 | iex> c = RoseTree.new(:c, [:d, :z]) 104 | ...> tree = RoseTree.new(:a, [:b, c]) 105 | ...> RoseTree.child_values(tree) 106 | [:b, :c] 107 | 108 | iex> tree = RoseTree.new(:hello) 109 | ...> RoseTree.child_values(tree) 110 | [] 111 | """ 112 | @spec child_values(RoseTree.t) :: [any()] 113 | def child_values(%RoseTree{children: children}) do 114 | children 115 | |> Enum.map(&(&1.node)) 116 | end 117 | 118 | @doc """ 119 | Determines whether a node is a child of a tree. 120 | 121 | The determination is based on whether the value of the node of the potential child 122 | matches a node value in the tree of interest. 123 | 124 | ## Examples 125 | iex> b = RoseTree.new(:b) 126 | ...> c = RoseTree.new(:c, [:d, :z]) 127 | ...> tree = RoseTree.new(:a, [b, c]) 128 | ...> RoseTree.is_child?(tree, b) 129 | true 130 | ...> x = RoseTree.new(:x) 131 | ...> RoseTree.is_child?(tree, x) 132 | false 133 | """ 134 | @spec is_child?(RoseTree.t, RoseTree.t) :: boolean 135 | def is_child?(%RoseTree{children: children}, %RoseTree{node: child_node}) do 136 | children 137 | |> Stream.map(&(&1.node)) 138 | |> Enum.member?(child_node) 139 | end 140 | 141 | @doc """ 142 | Map a function over a tree, preserving the tree's structure (compare 143 | with `Enum.map` for rose trees, which maps over a list of node values). 144 | 145 | ## Examples 146 | iex> RoseTree.new(0, [1, RoseTree.new(10, [11, 12])]) 147 | ...> |> RoseTree.map(fn (value) -> value * value end) 148 | %RoseTree{node: 0, children: [ 149 | %RoseTree{node: 1, children: []}, 150 | %RoseTree{node: 100, children: [ 151 | %RoseTree{node: 121, children: []}, 152 | %RoseTree{node: 144, children: []} 153 | ]} 154 | ]} 155 | """ 156 | @spec map(RoseTree.t, (any() -> any())) :: RoseTree.t 157 | def map(%RoseTree{node: node, children: []}, func) do 158 | %RoseTree{node: func.(node), children: []} 159 | end 160 | def map(%RoseTree{node: node, children: children}, func) do 161 | updated_children = for child <- children, do: map(child, func) 162 | %RoseTree{node: func.(node), children: updated_children} 163 | end 164 | 165 | @doc """ 166 | Merge two nodes that have the same value. 167 | 168 | If the sets of children in the two nodes do not share any members, the 169 | children of `tree_a` are prepended to `tree_b`'s children. 170 | 171 | If there are children with the same node value present in both trees, 172 | the children are themselves merged with the child in `tree_a`'s children (updated 173 | with the merge of the children of `tree_b`'s child) that matched prepended to 174 | the unique children from `tree_b`. 175 | 176 | ## Examples 177 | iex> world_wide = RoseTree.new(:world, :wide) 178 | ...> world_champ = RoseTree.new(:world, :champ) 179 | ...> RoseTree.merge_nodes(world_champ, world_wide) 180 | %RoseTree{ 181 | children: [ 182 | %RoseTree{children: [], node: :champ}, 183 | %RoseTree{children: [], node: :wide} 184 | ], node: :world 185 | } 186 | """ 187 | @spec merge_nodes(RoseTree.t, RoseTree.t) :: RoseTree.t 188 | def merge_nodes(%RoseTree{node: node} = tree_a, %RoseTree{node: node} = tree_b) do 189 | children_a_nodes = MapSet.new(child_values(tree_a)) 190 | children_b_nodes = MapSet.new(child_values(tree_b)) 191 | if MapSet.disjoint?(children_a_nodes, children_b_nodes) do 192 | # If no children are shared, prepend a's children to b's children. 193 | %RoseTree{node: node, children: List.flatten([tree_a.children | tree_b.children])} 194 | else 195 | # If children are shared, their children have to be merged. 196 | matching_children = MapSet.intersection(children_a_nodes, children_b_nodes) 197 | unique_children_b = MapSet.difference(children_b_nodes, children_a_nodes) 198 | new_children = tree_a.children 199 | |> Enum.reduce([], fn (child, acc) -> 200 | if MapSet.member?(matching_children, child) do 201 | match = Enum.find(tree_b.children, fn(c) -> c.node == child.node end) 202 | [merge_nodes(child, match) | acc] 203 | else 204 | [child | acc] 205 | end 206 | end) 207 | |> child_merge_helper(tree_b.children, unique_children_b) 208 | %RoseTree{node: node, children: new_children} 209 | end 210 | end 211 | 212 | defp child_merge_helper(children_a, children_b, children_b_unique_nodes) do 213 | children_b 214 | |> Stream.filter(fn (child) -> MapSet.member?(children_b_unique_nodes, child.node) end) 215 | |> Enum.reduce(children_a, fn (child, acc) -> [child | acc] end) 216 | |> Enum.reverse 217 | end 218 | 219 | @doc """ 220 | Remove the first child from the children of a node. 221 | 222 | Returns a tuple containing the child that was removed and the modified tree. 223 | 224 | ## Examples 225 | iex> RoseTree.new(:a, [:b, RoseTree.new(:c, [:d, :z])]) 226 | ...> |> RoseTree.pop_child() 227 | {%RoseTree{node: :b, children: []}, %RoseTree{ 228 | node: :a, children: [ 229 | %RoseTree{node: :c, children: [ 230 | %RoseTree{node: :d, children: []}, 231 | %RoseTree{node: :z, children: []} 232 | ]} 233 | ] 234 | }} 235 | 236 | iex> RoseTree.new(:hello) 237 | ...> |> RoseTree.pop_child() 238 | {nil, %RoseTree{node: :hello, children: []}} 239 | """ 240 | @spec pop_child(RoseTree.t) :: {RoseTree.t, RoseTree.t} | {nil, RoseTree.t} 241 | def pop_child(tree), do: pop_child_at(tree, 0) 242 | 243 | @doc """ 244 | Remove the child at a given index from the children of a node. 245 | 246 | Returns a tuple containing the child that was removed and the modified tree. 247 | 248 | ## Examples 249 | iex> RoseTree.new(:a, [:b, RoseTree.new(:c, [:d, :z])]) 250 | ...> |> RoseTree.pop_child_at(1) 251 | {%RoseTree{node: :c, children: [ 252 | %RoseTree{node: :d, children: []}, 253 | %RoseTree{node: :z, children: []} 254 | ]}, %RoseTree{node: :a, children: [ 255 | %RoseTree{node: :b, children: []} 256 | ]} 257 | } 258 | """ 259 | @spec pop_child_at(RoseTree.t, integer()) :: {RoseTree.t, RoseTree.t} | {nil, RoseTree.t} 260 | def pop_child_at(%RoseTree{children: []} = tree, _idx), do: {nil, tree} 261 | def pop_child_at(%RoseTree{node: node, children: children} = tree, idx) when is_number(idx) do 262 | {child, updated_children} = List.pop_at(children, idx) 263 | {child, update_children(tree, node, updated_children)} 264 | end 265 | 266 | @doc """ 267 | Convert a map into a rose tree. 268 | 269 | ## Examples 270 | iex> RoseTree.from_map(%{a: [:b]}) 271 | {:ok, %RoseTree{node: :a, children: [%RoseTree{node: :b, children: []}]}} 272 | 273 | iex> RoseTree.from_map(%{a: %{b: [:c]}}) 274 | {:ok, %RoseTree{node: :a, children: [ 275 | %RoseTree{node: :b, children: [ 276 | %RoseTree{node: :c, children: []} 277 | ]} 278 | ]}} 279 | """ 280 | @spec from_map(map()) :: {:error, tuple()} | {:ok, RoseTree.t} 281 | def from_map(%{} = map) do 282 | {:ok, from_map_helper(map)} 283 | end 284 | 285 | defp from_map_helper(children) when is_list(children) do 286 | for child <- children, do: new(child) 287 | end 288 | defp from_map_helper(%{} = map) do 289 | if length(Map.keys(map)) > 1 do 290 | {:error, {:rose_tree, :one_node_root}} 291 | else 292 | res = for {k, v} <- map do 293 | new(k, from_map_helper(v)) 294 | end 295 | hd(res) 296 | end 297 | end 298 | 299 | @doc """ 300 | List all of the possible paths through a tree. 301 | 302 | ## Examples 303 | iex> RoseTree.new(:a, [:b, RoseTree.new(:c, [:d, :z])]) 304 | ...> |> RoseTree.paths() 305 | [[:a, :b], [[:a, :c, :d], [:a, :c, :z]]] 306 | """ 307 | @spec paths(RoseTree.t) :: [any()] 308 | def paths(%RoseTree{} = tree), do: paths(tree, []) 309 | defp paths(%RoseTree{node: node, children: []}, acc), do: Enum.reverse([node | acc]) 310 | defp paths(%RoseTree{node: node, children: children}, acc) do 311 | for child <- children, do: paths(child, [node | acc]) 312 | end 313 | 314 | @doc """ 315 | Extract the node values of a rose tree into a list. 316 | 317 | ## Examples 318 | iex> RoseTree.new(:a, [:b, RoseTree.new(:c, [:d, :z])]) 319 | ...> |> RoseTree.to_list() 320 | [:a, :b, :c, :d, :z] 321 | """ 322 | @spec to_list(RoseTree.t) :: [any()] 323 | def to_list(%RoseTree{} = tree), do: to_list(tree, []) 324 | defp to_list(%RoseTree{node: node, children: []}, _acc), do: [node] 325 | defp to_list(%RoseTree{node: node, children: children}, acc) do 326 | reduced_children = for child <- children do 327 | to_list(child, acc) 328 | end 329 | List.flatten([node | reduced_children]) 330 | end 331 | 332 | @doc """ 333 | Fetch a node's value by traversing a path of indicies through nodes and their children. 334 | 335 | ## Examples 336 | iex> tree = RoseTree.new(:a, [:b, RoseTree.new(:c, [:d, :z])]) 337 | ...> RoseTree.elem_at(tree, []) 338 | {:ok, :a} 339 | ...> RoseTree.elem_at(tree, [1, 0]) 340 | {:ok, :d} 341 | ...> RoseTree.elem_at(tree, [1, 2]) 342 | {:error, {:rose_tree, :bad_path}} 343 | """ 344 | @spec elem_at(RoseTree.t, list(integer())) :: {:ok, any()} | {:error, tuple()} 345 | def elem_at(%RoseTree{node: node} = _tree, [] = _index_path), do: {:ok, node} 346 | def elem_at(%RoseTree{children: children} = _tree, [h | t] = _index_path) when h <= length(children) do 347 | children 348 | |> Enum.at(h) 349 | |> elem_at(t) 350 | end 351 | def elem_at(_tree, _index_path), do: {:error, {:rose_tree, :bad_path}} 352 | 353 | @doc """ 354 | Replace the value of every node that matches a given value. 355 | 356 | Note: this will update the value of *every* match. If there 357 | are multiple nodes throughout the tree that match, they will 358 | all be updated. 359 | 360 | ## Examples 361 | iex> RoseTree.new(:a, [:b]) 362 | ...> |> RoseTree.update_node(:a, :hello) 363 | %RoseTree{node: :hello, children: [%RoseTree{node: :b, children: []}]} 364 | """ 365 | @spec update_node(RoseTree.t, any(), any()) :: RoseTree.t 366 | def update_node(%RoseTree{node: value, children: children}, value, new_value) do 367 | %RoseTree{node: new_value, children: children} 368 | end 369 | def update_node(%RoseTree{children: []} = tree, _value, _new_value), do: tree 370 | def update_node(%RoseTree{node: node, children: children}, value, new_value) do 371 | updated_children = for child <- children, do: update_node(child, value, new_value) 372 | %RoseTree{node: node, children: updated_children} 373 | end 374 | 375 | @doc """ 376 | Replace the children of a node that matches a given value. 377 | 378 | Note: this will update the value of *every* match. If there 379 | are multiple nodes throughout the tree that match, they will 380 | all be updated. 381 | 382 | ## Examples 383 | iex> RoseTree.new(:a, [:b]) 384 | ...> |> RoseTree.update_children(:a, [RoseTree.new(:c)]) 385 | %RoseTree{node: :a, children: [%RoseTree{node: :c, children: []}]} 386 | """ 387 | @spec update_children(RoseTree.t, any(), any()) :: RoseTree.t 388 | def update_children(%RoseTree{children: []} = tree, _value, _new_children), do: tree 389 | def update_children(%RoseTree{node: value}, value, new_children) do 390 | %RoseTree{node: value, children: new_children} 391 | end 392 | def update_children(%RoseTree{node: node, children: children}, value, new_children) do 393 | updated_children = for child <- children, do: update_children(child, value, new_children) 394 | %RoseTree{node: node, children: updated_children} 395 | end 396 | 397 | defp is_rose_tree?(%RoseTree{}), do: true 398 | defp is_rose_tree?(_), do: false 399 | 400 | defimpl Enumerable do 401 | def count(%RoseTree{} = tree), do: {:ok, count(tree, 0)} 402 | 403 | defp count(%RoseTree{children: []}, acc), do: acc + 1 # This counts the leaves 404 | defp count(%RoseTree{children: children}, acc) do 405 | Enum.sum(Enum.map(children, &count(&1, acc))) + 1 406 | end 407 | 408 | def member?(%RoseTree{node: node}, node), do: {:ok, true} 409 | def member?(%RoseTree{node: node, children: []}, elem) when node != elem, do: {:ok, false} 410 | def member?(%RoseTree{} = tree, elem) do 411 | {:ok, member?(tree, elem, false)} 412 | end 413 | 414 | defp member?(%RoseTree{children: children}, elem, acc) do 415 | Enum.reduce(children, acc, fn(child, acc) -> 416 | {:ok, res} = member?(child, elem) 417 | if res, do: true, else: acc 418 | end) 419 | end 420 | 421 | def reduce(tree, acc, f) do 422 | reduce_tree(RoseTree.to_list(tree), acc, f) 423 | end 424 | 425 | defp reduce_tree(_, {:halt, acc}, _f), do: {:halted, acc} 426 | defp reduce_tree(tree, {:suspend, acc}, f), do: {:suspended, acc, &reduce_tree(tree, &1, f)} 427 | defp reduce_tree([], {:cont, acc}, _f), do: {:done, acc} 428 | defp reduce_tree([h | t], {:cont, acc}, f), do: reduce_tree(t, f.(h, acc), f) 429 | end 430 | end 431 | -------------------------------------------------------------------------------- /lib/zipper/zipper.ex: -------------------------------------------------------------------------------- 1 | defmodule RoseTree.Zipper do 2 | alias RoseTree.Zipper 3 | @moduledoc """ 4 | A zipper provides a mechanism for traversing a tree by focusing on 5 | a given node and maintaining enough data to reconstruct the overall 6 | tree from any given node. 7 | 8 | Because most of the functions in `RoseTree.Zipper` can attempt to access data that 9 | may not exist, e.g. an out-of-bounds index in a node's array of chlidren, the majority 10 | of the functions return values with tagged tuples to let the user explicitly handle 11 | success and error cases: `{:ok, {%RoseTree{}, []}} | {:error, {:rose_tree, error_message}}` 12 | 13 | To make working with these values easier, the function `lift/2` takes one of these tagged 14 | tuple (`either zipper | error`) values and a function. If the first argument is an error, 15 | the error is passed through; if it is an `{:ok, zipper}` tuple, the function is applied to 16 | the zipper. This lets you easily chain successive calls to tree manipulation functions. 17 | 18 | Adapted from [Huet (1997)](https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf); 19 | additional functionality inspired by [Data.Tree.Zipper](https://hackage.haskell.org/package/rosezipper-0.1/docs/Data-Tree-Zipper.html). 20 | """ 21 | 22 | @type breadcrumb :: %{parent: any(), left_siblings: [any()], right_siblings: [any()]} 23 | @type t :: {RoseTree.t, [breadcrumb]} 24 | @type either_zipper :: {:ok, Zipper.t} | {:error, tuple()} 25 | 26 | @doc """ 27 | Build a zipper focusing on the current tree. 28 | 29 | ## Examples 30 | iex> RoseTree.new(:a, [:b, RoseTree.new(:c, [:d, :z])]) 31 | ...> |> Zipper.from_tree() 32 | {%RoseTree{node: :a, children: [ 33 | %RoseTree{node: :b, children: []}, 34 | %RoseTree{node: :c, children: [ 35 | %RoseTree{node: :d, children: []}, 36 | %RoseTree{node: :z, children: []} 37 | ]} 38 | ]}, []} 39 | """ 40 | @spec from_tree(RoseTree.t) :: Zipper.t 41 | def from_tree(%RoseTree{} = tree), do: {tree, []} 42 | 43 | @doc """ 44 | Extract the currently focused tree from a zipper. 45 | 46 | ## Examples 47 | iex> RoseTree.new(:a, [:b, RoseTree.new(:c, [:d, :z])]) 48 | ...> |> Zipper.from_tree() 49 | ...> |> Zipper.nth_child(0) 50 | ...> |> Zipper.lift(&Zipper.to_tree(&1)) 51 | %RoseTree{node: :b, children: []} 52 | """ 53 | @spec to_tree(Zipper.t) :: RoseTree.t 54 | def to_tree({%RoseTree{} = tree, _crumbs} = _zipper), do: tree 55 | 56 | @doc """ 57 | Move the zipper's focus to the child at the given index. 58 | 59 | ## Examples 60 | iex> RoseTree.new(:a, [:b, RoseTree.new(:c, [:d, :z])]) 61 | ...> |> Zipper.from_tree() 62 | ...> |> Zipper.nth_child(0) 63 | {:ok, {%RoseTree{node: :b, children: []}, [%{ 64 | parent: :a, 65 | left_siblings: [], 66 | right_siblings: [ 67 | %RoseTree{node: :c, children: [ 68 | %RoseTree{node: :d, children: []}, 69 | %RoseTree{node: :z, children: []} 70 | ]} 71 | ] 72 | }]}} 73 | """ 74 | @spec nth_child(Zipper.t, integer()) :: {:ok, Zipper.t} | {:error, {:rose_tree, :no_children}} 75 | def nth_child({%RoseTree{children: []}, _breadcrumbs} = _zipper, _index), do: {:error, {:rose_tree, :no_children}} 76 | def nth_child({%RoseTree{node: node, children: [h | t]}, breadcrumbs} = zipper, index) when is_list(breadcrumbs) and is_integer(index) do 77 | case index do 78 | 0 -> 79 | {:ok, {h, [%{parent: node, left_siblings: [], right_siblings: t} | breadcrumbs]}} 80 | _ -> 81 | lift(nth_child(zipper, index - 1), &next_sibling/1) 82 | end 83 | end 84 | 85 | @doc """ 86 | Move up the tree to the parent of the node that the zipper is currently focused on. 87 | 88 | ## Examples 89 | iex> zipper = RoseTree.new(:a, [:b, RoseTree.new(:c, [:d, :z])]) 90 | ...> |> Zipper.from_tree() 91 | ...> {:ok, nth_child} = Zipper.nth_child(zipper, 0) 92 | ...> Zipper.ascend(nth_child) 93 | {:ok, 94 | {%RoseTree{node: :a, children: [ 95 | %RoseTree{node: :b, children: []}, 96 | %RoseTree{node: :c, children: [ 97 | %RoseTree{node: :d, children: []}, 98 | %RoseTree{node: :z, children: []} 99 | ]} 100 | ]}, []} 101 | } 102 | """ 103 | @spec ascend(Zipper.t) :: {:ok, Zipper.t} | {:error, {:rose_tree, :no_parent}} 104 | def ascend({%RoseTree{}, []} = _zipper), do: {:error, {:rose_tree, :no_parent}} 105 | def ascend({%RoseTree{} = tree, [%{parent: parent_value, left_siblings: l, right_siblings: r} | crumbs]} = _zipper) do 106 | children = Enum.reverse(l) ++ [tree | r] 107 | {:ok, {%RoseTree{node: parent_value, children: children}, crumbs}} 108 | end 109 | 110 | @doc """ 111 | Apply a function to the value of a tree under the focus of a zipper. 112 | 113 | ## Examples 114 | iex> zipper = RoseTree.new(0, [1, RoseTree.new(10, [11, 12])]) 115 | ...> |> Zipper.from_tree() 116 | ...> {:ok, nth_child} = Zipper.nth_child(zipper, 0) 117 | ...> Zipper.modify(nth_child, fn(x) -> x * 5 end) 118 | {%RoseTree{node: 5, children: []}, [%{ 119 | parent: 0, 120 | left_siblings: [], 121 | right_siblings: [ 122 | %RoseTree{node: 10, children: [ 123 | %RoseTree{node: 11, children: []}, 124 | %RoseTree{node: 12, children: []} 125 | ]} 126 | ] 127 | }]} 128 | """ 129 | @spec modify(Zipper.t, (any() -> any())) :: Zipper.t 130 | def modify({%RoseTree{node: node} = tree, crumbs} = _zipper, f) do 131 | {%RoseTree{tree | node: f.(node)}, crumbs} 132 | end 133 | 134 | @doc """ 135 | Remove a subtree from a tree and move focus to the parent node. 136 | 137 | ## Examples 138 | iex> RoseTree.new(:a, [:b, RoseTree.new(:c, [:d, :z])]) 139 | ...> |> Zipper.from_tree() 140 | ...> |> Zipper.nth_child(0) 141 | ...> |> Zipper.lift(&Zipper.prune(&1)) 142 | ...> |> Zipper.to_tree() 143 | %RoseTree{node: :a, children: [ 144 | %RoseTree{node: :c, children: [ 145 | %RoseTree{node: :d, children: []}, 146 | %RoseTree{node: :z, children: []} 147 | ]} 148 | ]} 149 | """ 150 | @spec prune(Zipper.t) :: Zipper.t 151 | def prune({%RoseTree{}, [%{parent: value, left_siblings: l, right_siblings: r} | crumbs]} = _zipper) do 152 | {%RoseTree{node: value, children: l ++ r}, crumbs} 153 | end 154 | 155 | @doc """ 156 | Ascend from any node to the root of the tree. 157 | 158 | If the focus is already on the root, it does not move. 159 | 160 | ## Examples 161 | iex> RoseTree.new(:a, [:b, RoseTree.new(:c, [:d, :z])]) 162 | ...> |> Zipper.from_tree() 163 | ...> |> Zipper.nth_child(1) 164 | ...> |> Zipper.lift(&Zipper.nth_child(&1, 0)) 165 | ...> |> Zipper.lift(&Zipper.to_root(&1)) 166 | {%RoseTree{node: :a, children: [ 167 | %RoseTree{node: :b, children: []}, 168 | %RoseTree{node: :c, children: [ 169 | %RoseTree{node: :d, children: []}, 170 | %RoseTree{node: :z, children: []} 171 | ]} 172 | ]}, []} 173 | 174 | iex> RoseTree.new(:a, [RoseTree.new(:b, [:x, :y, :z]), RoseTree.new(:c, [:d, :e])]) 175 | ...> |> Zipper.from_tree() 176 | ...> |> Zipper.first_child() 177 | ...> |> Zipper.lift(&Zipper.nth_child(&1, 2)) 178 | ...> |> Zipper.lift(&Zipper.to_root/1) 179 | {%RoseTree{node: :a, children: [ 180 | %RoseTree{node: :b, children: [ 181 | %RoseTree{node: :x, children: []}, 182 | %RoseTree{node: :y, children: []}, 183 | %RoseTree{node: :z, children: []}, 184 | ]}, 185 | %RoseTree{node: :c, children: [ 186 | %RoseTree{node: :d, children: []}, 187 | %RoseTree{node: :e, children: []}, 188 | ]}, 189 | ]}, []} 190 | """ 191 | @spec to_root(Zipper.t) :: Zipper.t 192 | def to_root({%RoseTree{}, []} = zipper), do: zipper 193 | def to_root({_tree, _crumbs} = zipper), do: lift(ascend(zipper), &to_root/1) 194 | 195 | @doc """ 196 | Nth_Child from the current focus to the left-most child tree. 197 | 198 | If the focus is already on a leaf, it does not move. 199 | 200 | ## Examples 201 | iex> RoseTree.new(:a, [:b, RoseTree.new(:c, [:d, :z])]) 202 | ...> |> Zipper.from_tree() 203 | ...> |> Zipper.to_leaf() 204 | {%RoseTree{node: :b, children: []}, [ 205 | %{ 206 | parent: :a, 207 | left_siblings: [], 208 | right_siblings: [ 209 | %RoseTree{node: :c, children: [ 210 | %RoseTree{node: :d, children: []}, 211 | %RoseTree{node: :z, children: []} 212 | ]} 213 | ] 214 | } 215 | ]} 216 | 217 | iex> RoseTree.new(:a, [RoseTree.new(:b, [:x, :y, :z]), RoseTree.new(:c, [:d, :e])]) 218 | ...> |> Zipper.from_tree() 219 | ...> |> Zipper.to_leaf() 220 | {%RoseTree{node: :x, children: []}, [ 221 | %{parent: :b, 222 | left_siblings: [], 223 | right_siblings: [ 224 | %RoseTree{node: :y, children: []}, 225 | %RoseTree{node: :z, children: []}, 226 | ] 227 | }, 228 | %{parent: :a, 229 | left_siblings: [], 230 | right_siblings: [ 231 | %RoseTree{node: :c, children: [ 232 | %RoseTree{node: :d, children: []}, 233 | %RoseTree{node: :e, children: []} 234 | ]} 235 | ] 236 | } 237 | ]} 238 | """ 239 | @spec to_leaf(Zipper.t) :: Zipper.t 240 | def to_leaf({%RoseTree{children: []}, _crumbs} = zipper), do: zipper 241 | def to_leaf({%RoseTree{children: _children}, _crumbs} = zipper) do 242 | zipper 243 | |> first_child() 244 | |> lift(&to_leaf/1) 245 | end 246 | 247 | 248 | @doc """ 249 | Move the zipper's focus to the node's first child. 250 | 251 | If the node is a leaf (and thus has no children), returns {:error, {:rose_tree, :no_children}}. 252 | Otherwise, returns {:ok, zipper} 253 | 254 | ## Examples 255 | iex> RoseTree.new(:a, [:b, RoseTree.new(:c, [:d, :z])]) 256 | ...> |> Zipper.from_tree() 257 | ...> |> Zipper.first_child() 258 | {:ok, {%RoseTree{node: :b, children: []}, [%{ 259 | parent: :a, 260 | left_siblings: [], 261 | right_siblings: [ 262 | %RoseTree{node: :c, children: [ 263 | %RoseTree{node: :d, children: []}, 264 | %RoseTree{node: :z, children: []} 265 | ]} 266 | ] 267 | }]}} 268 | """ 269 | @spec first_child(Zipper.t) :: {:ok, Zipper.t} | {:error, {:rose_tree, :no_children}} 270 | def first_child({%RoseTree{children: []}, _crumbs}), do: {:error, {:rose_tree, :bad_path}} 271 | def first_child({%RoseTree{}, _crumbs} = zipper), do: nth_child(zipper, 0) 272 | 273 | @doc """ 274 | Move the zipper's focus to the node's last child. 275 | 276 | If the node is a leaf (and thus has no children), returns {:error, {:rose_tree, :no_children}}. 277 | Otherwise, returns {:ok, zipper} 278 | 279 | ## Examples 280 | iex> RoseTree.new(:a, [:b, RoseTree.new(:c, [:d, :z])]) 281 | ...> |> Zipper.from_tree() 282 | ...> |> Zipper.last_child() 283 | {:ok, { 284 | %RoseTree{node: :c, children: [ 285 | %RoseTree{node: :d, children: []}, 286 | %RoseTree{node: :z, children: []} 287 | ]}, 288 | [%{parent: :a, left_siblings: [%RoseTree{node: :b, children: []}], right_siblings: []}] 289 | }} 290 | """ 291 | @spec last_child(Zipper.t) :: {:ok, Zipper.t} | {:error, {:rose_tree, :no_children}} 292 | def last_child({%RoseTree{children: []}, _crumbs}), do: {:error, {:rose_tree, :bad_path}} 293 | def last_child({%RoseTree{} = tree, _crumbs} = zipper), do: nth_child(zipper, Enum.count(tree.children) - 1) 294 | 295 | @doc """ 296 | Move the zipper's focus to the next sibling of the currently focused node. 297 | 298 | ## Examples 299 | iex> RoseTree.new(:a, [RoseTree.new(:c, [:d, :z])]) 300 | ...> |> Zipper.from_tree() 301 | ...> |> Zipper.first_child() 302 | ...> |> Zipper.lift(&Zipper.first_child(&1)) 303 | ...> |> Zipper.lift(&Zipper.next_sibling/1) 304 | {:ok, { 305 | %RoseTree{children: [], node: :z}, [ 306 | %{parent: :c, left_siblings: [%RoseTree{children: [], node: :d}], right_siblings: []}, 307 | %{parent: :a, left_siblings: [], right_siblings: []} 308 | ] 309 | }} 310 | 311 | iex> RoseTree.new(:a, [RoseTree.new(:c, [:d, :z])]) 312 | ...> |> Zipper.from_tree() 313 | ...> |> Zipper.first_child() 314 | ...> |> Zipper.lift(&Zipper.nth_child(&1, 1)) 315 | ...> |> Zipper.lift(&Zipper.next_sibling/1) 316 | {:error, {:rose_tree, :no_next_sibling}} 317 | """ 318 | @spec next_sibling(Zipper.t) :: {:ok, Zipper.t} | {:error, {:rose_tree, :no_siblings}} | {:error, {:rose_tree, :no_next_sibling}} 319 | def next_sibling({%RoseTree{}, []} = _zipper), do: {:error, {:rose_tree, :no_siblings}} 320 | def next_sibling({%RoseTree{} = tree, [%{} = h | t] = _crumbs} = _zipper) do 321 | case h.right_siblings do 322 | [] -> {:error, {:rose_tree, :no_next_sibling}} 323 | [r | rs] -> {:ok, {r, [%{h | right_siblings: rs, left_siblings: [tree | h.left_siblings]} | t]}} 324 | end 325 | end 326 | 327 | @doc """ 328 | Move the zipper's focus to the previous sibling of the currently focused node. 329 | 330 | ## Examples 331 | iex> RoseTree.new(:a, [RoseTree.new(:c, [:d, :z])]) 332 | ...> |> Zipper.from_tree() 333 | ...> |> Zipper.nth_child(0) 334 | ...> |> Zipper.lift(&Zipper.nth_child(&1, 1)) 335 | ...> |> Zipper.lift(&Zipper.previous_sibling/1) 336 | {:ok, { 337 | %RoseTree{children: [], node: :d}, [ 338 | %{parent: :c, left_siblings: [], right_siblings: [%RoseTree{children: [], node: :z}]}, 339 | %{parent: :a, left_siblings: [], right_siblings: []} 340 | ] 341 | }} 342 | 343 | iex> RoseTree.new(:a, [RoseTree.new(:c, [:d, :z])]) 344 | ...> |> Zipper.from_tree() 345 | ...> |> Zipper.nth_child(0) 346 | ...> |> Zipper.lift(&Zipper.nth_child(&1, 0)) 347 | ...> |> Zipper.lift(&Zipper.previous_sibling/1) 348 | {:error, {:rose_tree, :no_previous_sibling}} 349 | """ 350 | @spec previous_sibling(Zipper.t) :: {:ok, Zipper.t} | {:error, {:rose_tree, :no_siblings}} | {:error, {:rose_tree, :no_previous_sibling}} 351 | def previous_sibling({%RoseTree{}, []} = _zipper), do: {:error, {:rose_tree, :no_siblings}} 352 | def previous_sibling({%RoseTree{} = tree, [%{} = h | t] = _crumbs} = _zipper) do 353 | case h.left_siblings do 354 | [] -> {:error, {:rose_tree, :no_previous_sibling}} 355 | [l | ls] -> {:ok, {l, [%{h | left_siblings: ls, right_siblings: [tree | h.right_siblings]} | t]}} 356 | end 357 | end 358 | 359 | @doc """ 360 | Move the zipper's focus to the node's first child that matches a predicate. 361 | 362 | ## Examples 363 | iex> RoseTree.new(:a, [RoseTree.new(1, [15, 5])]) 364 | ...> |> Zipper.from_tree() 365 | ...> |> Zipper.nth_child(0) 366 | ...> |> Zipper.lift(&Zipper.find_child(&1, fn(child) -> child.node > 10 end)) 367 | {:ok, { 368 | %RoseTree{children: [], node: 15}, [ 369 | %{parent: 1, left_siblings: [], right_siblings: [%RoseTree{children: [], node: 5}]}, 370 | %{parent: :a, left_siblings: [], right_siblings: []} 371 | ] 372 | }} 373 | 374 | iex> RoseTree.new(:a, [RoseTree.new(1, [15, 5])]) 375 | ...> |> Zipper.from_tree() 376 | ...> |> Zipper.nth_child(0) 377 | ...> |> Zipper.lift(&Zipper.find_child(&1, fn(child) -> length(child.children) > 5 end)) 378 | {:error, {:rose_tree, :no_child_match}} 379 | """ 380 | @spec find_child(Zipper.t, (any() -> any())) :: {:ok, Zipper.t} | {:error, {:rose_tree, :no_child_match}} 381 | def find_child({%RoseTree{children: children}, _crumbs} = zipper, predicate) when is_function(predicate) do 382 | matching_index = Enum.find_index(children, predicate) 383 | if matching_index == nil do 384 | {:error, {:rose_tree, :no_child_match}} 385 | else 386 | nth_child(zipper, matching_index) 387 | end 388 | end 389 | 390 | @doc """ 391 | Lift a function expecting a zipper as an argument to one that can handle either 392 | {:ok, zipper} | {:error, error}. 393 | 394 | If the first argument is an :error tuple, the error is passed through. 395 | Otherwise, the function is applied to the zipper in the :ok tuple. 396 | 397 | ## Examples 398 | iex> RoseTree.new(:a, [:b, RoseTree.new(:c, [:d, :z])]) 399 | ...> |> Zipper.from_tree() 400 | ...> |> Zipper.nth_child(1) 401 | ...> |> Zipper.lift(&Zipper.nth_child(&1, 0)) 402 | ...> |> Zipper.lift(&Zipper.to_tree(&1)) 403 | %RoseTree{node: :d, children: []} 404 | 405 | iex> RoseTree.new(:a, [:b, RoseTree.new(:c, [:d, :z])]) 406 | ...> |> Zipper.from_tree() 407 | ...> |> Zipper.nth_child(1) 408 | ...> |> Zipper.lift(&Zipper.nth_child(&1, 0)) 409 | ...> |> Zipper.lift(&Zipper.nth_child(&1, 1)) 410 | {:error, {:rose_tree, :no_children}} 411 | """ 412 | @spec lift(either_zipper, (any() -> any())) :: either_zipper 413 | def lift({:ok, zipper} = _either_zipper_error, f), do: f.(zipper) 414 | def lift({:error, _} = either_zipper_error, _f), do: either_zipper_error 415 | 416 | ## Predicates 417 | @doc """ 418 | Test whether the current focus is the root of the tree. 419 | 420 | ## Examples 421 | iex> RoseTree.new(:a, :b) 422 | ...> |> Zipper.from_tree() 423 | ...> |> Zipper.root?() 424 | true 425 | 426 | iex> RoseTree.new(:a, :b) 427 | ...> |> Zipper.from_tree() 428 | ...> |> Zipper.first_child() 429 | ...> |> Zipper.lift(&Zipper.root?/1) 430 | false 431 | """ 432 | @spec root?(Zipper.t) :: boolean() 433 | def root?({%RoseTree{}, []} = _zipper), do: true 434 | def root?({%RoseTree{}, _crumbs} = _zipper), do: false 435 | 436 | @doc """ 437 | Test whether the current focus is a leaf of the tree. 438 | 439 | ## Examples 440 | iex> RoseTree.new(:a, :b) 441 | ...> |> Zipper.from_tree() 442 | ...> |> Zipper.first_child() 443 | ...> |> Zipper.lift(&Zipper.leaf?/1) 444 | true 445 | 446 | iex> RoseTree.new(:a, :b) 447 | ...> |> Zipper.from_tree() 448 | ...> |> Zipper.leaf?() 449 | false 450 | """ 451 | @spec leaf?(Zipper.t) :: boolean() 452 | def leaf?({%RoseTree{children: []}, _crumbs} = _zipper), do: true 453 | def leaf?({%RoseTree{}, _crumbs} = _zipper), do: false 454 | 455 | @doc """ 456 | Test whether the current focus is a the first (left-most) node among its siblings. 457 | 458 | ## Examples 459 | iex> RoseTree.new(:a, [:x, :y, :z]) 460 | ...> |> Zipper.from_tree() 461 | ...> |> Zipper.first_child() 462 | ...> |> Zipper.lift(&Zipper.first?/1) 463 | true 464 | 465 | iex> RoseTree.new(:a, [:x, :y, :z]) 466 | ...> |> Zipper.from_tree() 467 | ...> |> Zipper.first_child() 468 | ...> |> Zipper.lift(&Zipper.next_sibling/1) 469 | ...> |> Zipper.lift(&Zipper.next_sibling/1) 470 | ...> |> Zipper.lift(&Zipper.first?/1) 471 | false 472 | """ 473 | @spec first?(Zipper.t) :: boolean() 474 | def first?({%RoseTree{}, [%{left_siblings: []} | _t]} = _zipper), do: true 475 | def first?({%RoseTree{}, _crumbs} = _zipper), do: false 476 | 477 | @doc """ 478 | Test whether the current focus is a the last (right-most) node among its siblings. 479 | 480 | ## Examples 481 | iex> RoseTree.new(:a, [:x, :y, :z]) 482 | ...> |> Zipper.from_tree() 483 | ...> |> Zipper.first_child() 484 | ...> |> Zipper.lift(&Zipper.next_sibling/1) 485 | ...> |> Zipper.lift(&Zipper.next_sibling/1) 486 | ...> |> Zipper.lift(&Zipper.last?/1) 487 | true 488 | 489 | iex> RoseTree.new(:a, [:x, :y, :z]) 490 | ...> |> Zipper.from_tree() 491 | ...> |> Zipper.first_child() 492 | ...> |> Zipper.lift(&Zipper.last?/1) 493 | false 494 | """ 495 | @spec last?(Zipper.t) :: boolean() 496 | def last?({%RoseTree{}, [%{right_siblings: []} | _t]} = _zipper), do: true 497 | def last?({%RoseTree{}, _crumbs} = _zipper), do: false 498 | 499 | @doc """ 500 | Test whether the current focus is a the only child of its parent. 501 | 502 | ## Examples 503 | iex> RoseTree.new(:a, :b) 504 | ...> |> Zipper.from_tree() 505 | ...> |> Zipper.first_child() 506 | ...> |> Zipper.lift(&Zipper.only_child?/1) 507 | true 508 | 509 | iex> RoseTree.new(:a, [:x, :y, :z]) 510 | ...> |> Zipper.from_tree() 511 | ...> |> Zipper.first_child() 512 | ...> |> Zipper.lift(&Zipper.only_child?/1) 513 | false 514 | """ 515 | @spec only_child?(Zipper.t) :: boolean() 516 | def only_child?({%RoseTree{}, [%{right_siblings: [], left_siblings: []} | _t]} = _zipper), do: true 517 | def only_child?({%RoseTree{}, _crumbs} = _zipper), do: false 518 | 519 | 520 | @doc """ 521 | Test whether the current focus has a parent node. 522 | 523 | ## Examples 524 | iex> RoseTree.new(:a, :b) 525 | ...> |> Zipper.from_tree() 526 | ...> |> Zipper.first_child() 527 | ...> |> Zipper.lift(&Zipper.has_parent?/1) 528 | true 529 | 530 | iex> RoseTree.new(:a, :b) 531 | ...> |> Zipper.from_tree() 532 | ...> |> Zipper.has_parent?() 533 | false 534 | """ 535 | @spec has_parent?(Zipper.t) :: boolean() 536 | def has_parent?({%RoseTree{}, _crumbs} = zipper), do: !root?(zipper) 537 | 538 | @doc """ 539 | Test whether the current focus has any child nodes. 540 | 541 | ## Examples 542 | iex> RoseTree.new(:a, :b) 543 | ...> |> Zipper.from_tree() 544 | ...> |> Zipper.has_children?() 545 | true 546 | 547 | iex> RoseTree.new(:a, :b) 548 | ...> |> Zipper.from_tree() 549 | ...> |> Zipper.first_child() 550 | ...> |> Zipper.lift(&Zipper.has_children?/1) 551 | false 552 | """ 553 | @spec has_children?(Zipper.t) :: boolean() 554 | def has_children?({%RoseTree{children: []}, _crumbs} = _zipper), do: false 555 | def has_children?({%RoseTree{}, _crumbs} = _zipper), do: true 556 | 557 | ## Tree modification 558 | @doc """ 559 | Insert a tree to the left of the current focus. Focus moves to the 560 | newly inserted tree. 561 | 562 | ## Example 563 | iex> RoseTree.new(:a, [RoseTree.new(:b, [:y, :z]), RoseTree.new(:c, [:d, :e])]) 564 | ...> |> Zipper.from_tree() 565 | ...> |> Zipper.first_child() 566 | ...> |> Zipper.lift(&Zipper.nth_child(&1, 1)) 567 | ...> |> Zipper.lift(&Zipper.insert_left(&1, RoseTree.new(:x))) 568 | {:ok, {%RoseTree{node: :x, children: []}, [ 569 | %{parent: :b, 570 | left_siblings: [ 571 | %RoseTree{node: :y, children: []}, 572 | ], 573 | right_siblings: [ 574 | %RoseTree{node: :z, children: []}, 575 | ] 576 | }, 577 | %{parent: :a, 578 | left_siblings: [], 579 | right_siblings: [ 580 | %RoseTree{node: :c, children: [ 581 | %RoseTree{node: :d, children: []}, 582 | %RoseTree{node: :e, children: []} 583 | ]} 584 | ] 585 | } 586 | ]}} 587 | """ 588 | @spec insert_left(Zipper.t, RoseTree.t) :: either_zipper 589 | def insert_left({%RoseTree{}, []} = _zipper, _tree), do: {:error, {:rose_tree, :root_cannot_have_siblings}} 590 | def insert_left({%RoseTree{} = focus, [%{left_siblings: siblings} = h | t]} = _zipper, %RoseTree{} = tree) do 591 | {focus, [%{h | left_siblings: [tree | siblings]} | t]} 592 | |> previous_sibling() 593 | end 594 | 595 | @doc """ 596 | Insert a tree to the right of the current focus. Focus moves to the 597 | newly inserted tree. 598 | 599 | ## Example 600 | iex> RoseTree.new(:a, [RoseTree.new(:b, [:y, :z]), RoseTree.new(:c, [:d, :e])]) 601 | ...> |> Zipper.from_tree() 602 | ...> |> Zipper.first_child() 603 | ...> |> Zipper.lift(&Zipper.nth_child(&1, 1)) 604 | ...> |> Zipper.lift(&Zipper.insert_right(&1, RoseTree.new(:x))) 605 | {:ok, {%RoseTree{node: :x, children: []}, [ 606 | %{parent: :b, 607 | left_siblings: [ 608 | %RoseTree{node: :z, children: []}, 609 | %RoseTree{node: :y, children: []}, 610 | ], 611 | right_siblings: [ 612 | ] 613 | }, 614 | %{parent: :a, 615 | left_siblings: [], 616 | right_siblings: [ 617 | %RoseTree{node: :c, children: [ 618 | %RoseTree{node: :d, children: []}, 619 | %RoseTree{node: :e, children: []} 620 | ]} 621 | ] 622 | } 623 | ]}} 624 | """ 625 | @spec insert_right(Zipper.t, RoseTree.t) :: either_zipper 626 | def insert_right({%RoseTree{}, []} = _zipper, _tree), do: {:error, {:rose_tree, :root_cannot_have_siblings}} 627 | def insert_right({%RoseTree{} = focus, [%{right_siblings: siblings} = h | t]} = _zipper, %RoseTree{} = tree) do 628 | {focus, [%{h | right_siblings: [tree | siblings]} | t]} 629 | |> next_sibling() 630 | end 631 | 632 | @doc """ 633 | Insert a tree as the first child of the current node. Focus moves to the 634 | newly inserted tree. 635 | 636 | ## Example 637 | iex> RoseTree.new(:a, [RoseTree.new(:b, [:y, :z]), RoseTree.new(:c, [:d, :e])]) 638 | ...> |> Zipper.from_tree() 639 | ...> |> Zipper.insert_first_child(RoseTree.new(:x)) 640 | {:ok, {%RoseTree{node: :x, children: []}, [ 641 | %{parent: :a, 642 | left_siblings: [], 643 | right_siblings: [ 644 | %RoseTree{node: :b, children: [ 645 | %RoseTree{node: :y, children: []}, 646 | %RoseTree{node: :z, children: []} 647 | ]}, 648 | %RoseTree{node: :c, children: [ 649 | %RoseTree{node: :d, children: []}, 650 | %RoseTree{node: :e, children: []} 651 | ]} 652 | ] 653 | } 654 | ]}} 655 | """ 656 | @spec insert_first_child(Zipper.t, RoseTree.t) :: {:ok, Zipper.t} 657 | def insert_first_child({%RoseTree{children: children} = focus, crumbs} = _zipper, %RoseTree{} = tree) do 658 | {%{focus | children: [tree | children]}, crumbs} 659 | |> first_child() 660 | end 661 | 662 | @doc """ 663 | Insert a tree as the last child of the current node. Focus moves to the 664 | newly inserted tree. 665 | 666 | ## Example 667 | iex> t = RoseTree.new(:a, [RoseTree.new(:b, [:y, :z]), RoseTree.new(:c, [:d, :e])]) 668 | ...> |> Zipper.from_tree() 669 | ...> |> Zipper.insert_last_child(RoseTree.new(:x)) 670 | {:ok, {%RoseTree{node: :x, children: []}, [ 671 | %{parent: :a, 672 | left_siblings: [ 673 | %RoseTree{node: :c, children: [ 674 | %RoseTree{node: :d, children: []}, 675 | %RoseTree{node: :e, children: []} 676 | ]}, 677 | %RoseTree{node: :b, children: [ 678 | %RoseTree{node: :y, children: []}, 679 | %RoseTree{node: :z, children: []} 680 | ]} 681 | ], 682 | right_siblings: [] 683 | } 684 | ]}} 685 | iex> Zipper.lift(t, &Zipper.to_root/1) 686 | ...> |> Zipper.to_tree() 687 | %RoseTree{node: :a, 688 | children: [ 689 | %RoseTree{node: :b, children: [ 690 | %RoseTree{node: :y, children: []}, 691 | %RoseTree{node: :z, children: []} 692 | ]}, 693 | %RoseTree{node: :c, children: [ 694 | %RoseTree{node: :d, children: []}, 695 | %RoseTree{node: :e, children: []} 696 | ]}, 697 | %RoseTree{node: :x, children: []} 698 | ] 699 | } 700 | """ 701 | @spec insert_last_child(Zipper.t, RoseTree.t) :: {:ok, Zipper.t} 702 | def insert_last_child({%RoseTree{children: children} = focus, crumbs} = _zipper, %RoseTree{} = tree) do 703 | {%{focus | children: children ++ [tree]}, crumbs} 704 | |> last_child() 705 | end 706 | 707 | @doc """ 708 | Insert a tree as the nth child of the current node. Focus moves to the 709 | newly inserted tree. 710 | 711 | ## Example 712 | iex> RoseTree.new(:a, [RoseTree.new(:b, [:y, :z]), RoseTree.new(:c, [:d, :e])]) 713 | ...> |> Zipper.from_tree() 714 | ...> |> Zipper.insert_nth_child(1, RoseTree.new(:x)) 715 | {:ok, {%RoseTree{node: :x, children: []}, [ 716 | %{parent: :a, 717 | left_siblings: [ 718 | %RoseTree{node: :b, children: [ 719 | %RoseTree{node: :y, children: []}, 720 | %RoseTree{node: :z, children: []} 721 | ]} 722 | ], 723 | right_siblings: [ 724 | %RoseTree{node: :c, children: [ 725 | %RoseTree{node: :d, children: []}, 726 | %RoseTree{node: :e, children: []} 727 | ]}, 728 | ] 729 | } 730 | ]}} 731 | 732 | iex> RoseTree.new(:a, [RoseTree.new(:b, [:y, :z]), RoseTree.new(:c, [:d, :e])]) 733 | ...> |> Zipper.from_tree() 734 | ...> |> Zipper.insert_nth_child(4, RoseTree.new(:x)) 735 | {:error, {:rose_tree, :bad_insertion_index}} 736 | """ 737 | @spec insert_nth_child(Zipper.t, pos_integer(), RoseTree.t) :: either_zipper 738 | def insert_nth_child({%RoseTree{children: children}, _crumbs} = _zipper, index, _tree) when index > length(children) do 739 | {:error, {:rose_tree, :bad_insertion_index}} 740 | end 741 | def insert_nth_child({%RoseTree{children: children} = focus, crumbs} = _zipper, index, %RoseTree{} = tree) do 742 | {%{focus | children: List.insert_at(children, index, tree)}, crumbs} 743 | |> nth_child(index) 744 | end 745 | end 746 | --------------------------------------------------------------------------------