Skip to content

Commit

Permalink
added lookup/2 and increment/3
Browse files Browse the repository at this point in the history
  • Loading branch information
ts-klassen committed Sep 8, 2023
1 parent 87ed0dd commit e205366
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 71 deletions.
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# D3Trees

English | [日本語](./ja-README.md)

An Erlang tree library for data-driven documents.

## Installation
Expand Down Expand Up @@ -40,6 +38,34 @@ Here's a basic example of how to use it:
}] % end of "FirstNode" children
}] % end of "RootNode" children
}
%% lookup a value in the tree
3> d3trees:lookup(["FirstNode", "SecondNode"], UpdatedTree).
{value, "SecondNodeValue"}
4> d3trees:lookup(["FirstNode"], UpdatedTree).
none
%% increment a value in the tree
5> IntegerTree = d3trees:increment(["x", "y"], 10, Tree).
#{
name => "RootNode"
, children => [#{
name => "x"
, children => [#{
name => "y"
, value => 10
}] % end of "x" children
}] % end of "RootNode" children
}
6> d3trees:increment(["x", "y"], 2, IntegerTree).
#{
name => "RootNode"
, children => [#{
name => "x"
, children => [#{
name => "y"
, value => 12
}] % end of "x" children
}] % end of "RootNode" children
}
```


Expand All @@ -54,6 +80,7 @@ This project is licensed under the Apache License - see the [LICENSE](LICENSE) f
## Version History

- **0.1.0** (Initial Commit) - [Release Notes](https://github.com/ts-klassen/d3trees/releases/tag/0.1.0)
- **0.1.1** (Initial Commit) - [Release Notes](https://github.com/ts-klassen/d3trees/releases/tag/0.1.1)

## Testing

Expand Down
67 changes: 0 additions & 67 deletions ja-README.md

This file was deleted.

2 changes: 1 addition & 1 deletion src/d3trees.app.src
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{application, d3trees,
[{description, "An Erlang tree library for data-driven documents."},
{vsn, "0.1.0"},
{vsn, "0.1.1"},
{registered, []},
{applications,
[kernel,
Expand Down
72 changes: 71 additions & 1 deletion src/d3trees.erl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
-export([
new/1
, upsert/3
, lookup/2
, increment/3
]).
-export_type([
name/0
Expand Down Expand Up @@ -39,7 +41,7 @@ new(Name) ->
%% }] % end of "RootNode" children
%% }
-spec upsert(
PathBelowRoot::path() , Value::value(), Tree::tree()
PathBelowRoot::path(), Value::value(), Tree::tree()
) -> UpdatedTree::tree().
upsert([], Value, Tree) ->
Tree#{value=>Value};
Expand All @@ -53,6 +55,74 @@ upsert([Name|Names], Value, Tree) ->
Children1 = upsert_child(Child1, Children0),
Tree#{children=>Children1}.

%% %%% lookup/2 usage %%%
%% 3> d3trees:lookup(["FirstNode", "SecondNode"], UpdatedTree).
%% {value, "SecondNodeValue"}
%% 4> d3trees:lookup(["FirstNode"], UpdatedTree).
%% none
-spec lookup(
PathBelowRoot::path(), Tree::tree()
) -> {value, Value::value()} | none.
lookup(PathBelowRoot, Tree) ->
try lookup_(PathBelowRoot, Tree) of
Res -> Res
catch
throw:none -> none
end.
lookup_([], Tree) ->
case Tree of
#{value:=Value} -> {value, Value};
_ -> throw(none)
end;
lookup_([Name|Names], Tree) ->
Children = case Tree of
#{children:=C} -> C;
_ -> throw(none)
end,
Child = find_child(Name, Children),
lookup_(Names, Child).

%% %%% increment/3 usage %%%
%% 5> Tree.
%% #{name => "RootNode"}
%% 6> IntegerTree = d3trees:increment(["x", "y"], 10, Tree).
%% #{
%% name => "RootNode"
%% , children => [#{
%% name => "x"
%% , children => [#{
%% name => "y"
%% , value => 10
%% }] % end of "x" children
%% }] % end of "RootNode" children
%% }
%% 7> d3trees:increment(["x", "y"], 2, IntegerTree).
%% #{
%% name => "RootNode"
%% , children => [#{
%% name => "x"
%% , children => [#{
%% name => "y"
%% , value => 12
%% }] % end of "x" children
%% }] % end of "RootNode" children
%% }
-spec increment(
PathBelowRoot::path(), Value::integer(), Tree::tree()
) -> UpdatedTree::tree().
increment(Path, Value, Tree) when is_integer(Value) ->
UpdatedValue = case lookup(Path, Tree) of
{value, V} when is_integer(V) ->
V + Value;
none ->
Value;
{value, V} ->
error({non_integer_value, V, Path})
end,
upsert(Path, UpdatedValue, Tree);
increment(_Path, Value, _Tree) ->
error({non_integer_value, Value}).

find_child(Name, []) ->
#{name=>Name};
find_child(Name, [#{name:=Name}=Child|_]) ->
Expand Down
162 changes: 162 additions & 0 deletions test/d3trees_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,165 @@ update_existing_path_without_children_test() ->
ExpectedTree = #{name => "RootNode", children => [#{name => "FirstNode", value => "NewValue", children => [#{name => "SecondNode", value => "SecondNodeValue"}]}]},
?assertEqual(ExpectedTree, Result).

% Test the lookup/2 function when the path exists in the tree.
lookup_existing_path_test() ->
% Arrange: Create a sample tree with an existing path
InitialTree = #{name => "RootNode", children => [#{name => "FirstNode", children => [#{name => "SecondNode", value => "SecondNodeValue"}]}]},

% Act: Call the lookup/2 function to retrieve a value
Result = d3trees:lookup(["FirstNode", "SecondNode"], InitialTree),

% Assert: Check that the result is as expected
Expected = {value, "SecondNodeValue"},
?assertEqual(Expected, Result).

% Test the lookup/2 function when the path doesn't exist in the tree.
lookup_nonexistent_path_test() ->
% Arrange: Create a sample tree with a different path
InitialTree = #{name => "RootNode", children => [#{name => "FirstNode", value => "FirstNodeValue"}]},

% Act: Call the lookup/2 function to retrieve a value for a non-existent path
Result = d3trees:lookup(["NonExistentNode"], InitialTree),

% Assert: Check that the result is 'none'
?assertEqual(none, Result).

% Test the lookup/2 function when the tree is empty (should return 'none').
lookup_empty_tree_test() ->
% Arrange: Create an empty tree
EmptyTree = #{name => "EmptyTree"},

% Act: Call the lookup/2 function on an empty tree
Result = d3trees:lookup(["FirstNode", "SecondNode"], EmptyTree),

% Assert: Check that the result is 'none'
?assertEqual(none, Result).

% Test the lookup/2 function when the specified path is partially present in the tree.
lookup_partial_path_test() ->
% Arrange: Create a sample tree with a partial path
InitialTree = #{name => "RootNode", children => [#{name => "FirstNode", value => "FirstNodeValue"}]},

% Act: Call the lookup/2 function with a path that is partially present
Result = d3trees:lookup(["FirstNode", "NonExistentNode"], InitialTree),

% Assert: Check that the result is 'none'
?assertEqual(none, Result).

% Test the lookup/2 function when the tree has multiple levels of nesting.
lookup_nested_tree_test() ->
% Arrange: Create a sample tree with multiple levels of nesting
InitialTree = #{name => "RootNode", children => [#{name => "FirstNode", children => [#{name => "SecondNode", children => [#{name => "ThirdNode", value => "ThirdNodeValue"}]}]}]},

% Act: Call the lookup/2 function to retrieve a value from a nested path
Result = d3trees:lookup(["FirstNode", "SecondNode", "ThirdNode"], InitialTree),

% Assert: Check that the result is as expected
Expected = {value, "ThirdNodeValue"},
?assertEqual(Expected, Result).

% Test the lookup/2 function when the path is an empty list (should return 'none').
lookup_empty_path_test() ->
% Arrange: Create a sample tree
InitialTree = #{name => "RootNode", children => [#{name => "FirstNode", value => "FirstNodeValue"}]},

% Act: Call the lookup/2 function with an empty path
Result = d3trees:lookup([], InitialTree),

% Assert: Check that the result is 'none'
?assertEqual(none, Result).

% Test the lookup/2 function when the tree has multiple branches and the path leads to a leaf node.
lookup_leaf_node_test() ->
% Arrange: Create a sample tree with multiple branches and a leaf node
InitialTree = #{name => "RootNode", children => [
#{name => "BranchA", children => [#{name => "LeafA1", value => "LeafA1Value"}]},
#{name => "BranchB", children => [#{name => "LeafB1", value => "LeafB1Value"}]}
]},

% Act: Call the lookup/2 function to retrieve a value from a leaf node
Result = d3trees:lookup(["BranchA", "LeafA1"], InitialTree),

% Assert: Check that the result is as expected
Expected = {value, "LeafA1Value"},
?assertEqual(Expected, Result).

% Test the lookup/2 function when the path contains non-existent intermediate nodes.
lookup_nonexistent_intermediate_nodes_test() ->
% Arrange: Create a sample tree with missing intermediate nodes
InitialTree = #{name => "RootNode", children => [#{name => "FirstNode", value => "FirstNodeValue"}]},

% Act: Call the lookup/2 function with a path containing non-existent intermediate nodes
Result = d3trees:lookup(["FirstNode", "NonExistentNode", "LeafNode"], InitialTree),

% Assert: Check that the result is 'none'
?assertEqual(none, Result).

% Test the lookup/2 function when the tree contains nodes with the same name at different levels.
lookup_same_named_nodes_test() ->
% Arrange: Create a sample tree with nodes having the same name at different levels
InitialTree = #{name => "RootNode", children => [
#{name => "FirstNode", children => [
#{name => "SecondNode", value => "SecondNodeValue"},
#{name => "ThirdNode", value => "ThirdNodeValue"}
]}
]},

% Act: Call the lookup/2 function to retrieve values from nodes with the same name
Result1 = d3trees:lookup(["FirstNode", "SecondNode"], InitialTree),
Result2 = d3trees:lookup(["FirstNode", "ThirdNode"], InitialTree),

% Assert: Check that the results are as expected
Expected1 = {value, "SecondNodeValue"},
Expected2 = {value, "ThirdNodeValue"},
?assertEqual(Expected1, Result1),
?assertEqual(Expected2, Result2).

% Test the increment/3 function when the path exists in the tree.
increment_existing_path_test() ->
% Arrange: Create a sample tree with an existing path and an integer value
InitialTree = #{name => "RootNode", children => [#{name => "x", children => [#{name => "y", value => 10}]}]},

% Act: Call the increment/3 function to increment the value
UpdatedTree = d3trees:increment(["x", "y"], 5, InitialTree),

% Assert: Check that the value is incremented as expected
Result = d3trees:lookup(["x", "y"], UpdatedTree),
Expected = {value, 15},
?assertEqual(Expected, Result).

% Test the increment/3 function when the path doesn't exist in the tree (should create the path).
increment_nonexistent_path_test() ->
% Arrange: Create a sample tree with a different path
InitialTree = #{name => "RootNode", children => [#{name => "a"}]},

% Act: Call the increment/3 function on a non-existent path
UpdatedTree = d3trees:increment(["x", "y"], 5, InitialTree),

% Assert: Check that the path is created and the value is set correctly
Result = d3trees:lookup(["x", "y"], UpdatedTree),
Expected = {value, 5},
?assertEqual(Expected, Result).

% Test the increment/3 function when given non-integer values.
increment_non_integer_value_test() ->
% Arrange: Create a sample tree with an existing path and a integer value
InitialTree = #{name => "RootNode", children => [#{name => "x", children => [#{name => "y", value => 10}]}]},

% Act: Call the increment/3 function on a path with a non-integer value
{'EXIT', {Result, _}} = catch d3trees:increment(["x", "y"], "z", InitialTree),

% Assert: Check that an error is returned
?assertMatch({non_integer_value, "z"}, Result).

% Test the increment/3 function when the path exists but the value is not an integer.
increment_existing_path_non_integer_value_test() ->
% Arrange: Create a sample tree with an existing path and a non-integer value
InitialTree = #{name => "RootNode", children => [#{name => "x", children => [#{name => "y", value => "String"}]}]},

% Act: Call the increment/3 function on a path with a non-integer value
{'EXIT', {Result, _}} = catch d3trees:increment(["x", "y"], 5, InitialTree),

% Assert: Check that an error is returned
?assertMatch({non_integer_value, "String", ["x", "y"]}, Result).

0 comments on commit e205366

Please sign in to comment.