diff --git a/lib/scenic/graph.ex b/lib/scenic/graph.ex index b773fb3c..907e8af1 100644 --- a/lib/scenic/graph.ex +++ b/lib/scenic/graph.ex @@ -583,10 +583,29 @@ defmodule Scenic.Graph do """ @spec find(graph :: t(), (any -> as_boolean(term()))) :: list(Primitive.t()) - def find(graph, finder) + def find(%__MODULE__{} = graph, finder) do + reduce(graph, [], fn p, acc -> + p + |> finder.() + |> case do + true -> [p | acc] + false -> acc + end + end) + |> Enum.reverse() + end - # pass in an atom based id, and it will transform all mapped uids - def find(%__MODULE__{} = graph, finder) when is_function(finder, 1) do + @doc """ + Find one or more primitives in a graph by id via a filter function. + + Pass in a function that accepts a primitive's id and returns a boolean. + + Returns a list of primitives. + + __Warning:__ This function crawls the entire graph and is thus slower than + accessing items via a fully-specified id. + """ + def find_by_id(%__MODULE__{} = graph, finder) do reduce(graph, [], fn p, acc -> Map.get(p, :id) |> finder.() @@ -652,7 +671,7 @@ defmodule Scenic.Graph do # pass in a finder function def modify(%__MODULE__{} = graph, finder, action) when is_function(finder, 1) do graph - |> find(finder) + |> find_by_id(finder) |> Enum.map(fn %{id: id} -> id end) |> Enum.uniq() |> Enum.reduce(graph, &modify(&2, &1, action)) diff --git a/test/scenic/graph_test.exs b/test/scenic/graph_test.exs index 5695fe55..185b86ff 100644 --- a/test/scenic/graph_test.exs +++ b/test/scenic/graph_test.exs @@ -267,7 +267,25 @@ defmodule Scenic.GraphTest do end # ============================================================================ - test "find returns the matching items" do + test "find returns the matching items" do + graph = + Graph.build() + |> Text.add_to_graph("text one", id: {:a, :one}) + |> Text.add_to_graph("text two", id: {:a, :two}) + |> Text.add_to_graph("text three", id: {:b, :three}) + + # confirm result + assert Graph.find(graph, &match?({:a, :one}, &1.id)) == [ + Graph.get!(graph, {:a, :one}) + ] + + assert Graph.find(graph, &match?({:b, :three}, &1.id)) == [ + Graph.get!(graph, {:b, :three}) + ] + end + + # ============================================================================ + test "find_by_id returns the matching items" do graph = Graph.build() |> Text.add_to_graph("text one", id: {:a, :one}) @@ -275,12 +293,12 @@ defmodule Scenic.GraphTest do |> Text.add_to_graph("text three", id: {:b, :three}) # confirm result - assert Graph.find(graph, &match?({:a, _}, &1)) == [ + assert Graph.find_by_id(graph, &match?({:a, _}, &1)) == [ Graph.get!(graph, {:a, :one}), Graph.get!(graph, {:a, :two}) ] - assert Graph.find(graph, &match?({:b, _}, &1)) == [ + assert Graph.find_by_id(graph, &match?({:b, _}, &1)) == [ Graph.get!(graph, {:b, :three}) ] end