sycamore 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 367667ecc8ca68b1e4da9252767849e41d85f535
4
+ data.tar.gz: 04523c2d3b2f7968403cd2bd51c2418136923fd0
5
+ SHA512:
6
+ metadata.gz: 1c96901b05579d97588ea3652622d2c270f8cc747698eb07462a73b84309dbec14b39668766e0b7aff913b397355f33a88659f8abea34cb40d3ce154e0c35d78
7
+ data.tar.gz: 92317760649683d727cb3904d6e70a680e9a87cac405dba65f827e93e7ee1f2ca7901895d4c701d73b68bf8c7b79eaf953aebe696542374c5e4c99d471187341
data/.editorconfig ADDED
@@ -0,0 +1,24 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+
9
+ [*.rb]
10
+ indent_style = space
11
+ indent_size = 2
12
+
13
+ [*.gemspec]
14
+ indent_style = space
15
+ indent_size = 2
16
+
17
+ [*.yml]
18
+ indent_style = space
19
+ indent_size = 2
20
+
21
+ [*.md]
22
+ indent_style = space
23
+ indent_size = 2
24
+ trim_trailing_whitespace = false
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc
7
+ /pkg/
8
+ /spec/reports/
9
+ /spec/examples.txt
10
+ /tmp/
11
+
data/.rspec ADDED
@@ -0,0 +1,6 @@
1
+ --color
2
+ #--format documentation
3
+ #--format progress
4
+ #--format Fuubar
5
+ #--fail-fast
6
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.0
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1
4
+ - 2.2.4
5
+ - 2.3.0
6
+ - jruby-9.0.4.0
7
+ cache: bundler
8
+ before_install:
9
+ - gem update bundler
data/.yardopts ADDED
@@ -0,0 +1,11 @@
1
+ --title "Sycamore"
2
+ --output-dir doc/yard
3
+ --protected
4
+ --private
5
+ --no-private
6
+ --readme README.md
7
+ -
8
+ AUTHORS
9
+ CREDITS
10
+ LICENSE.txt
11
+ VERSION
data/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ - Marcel Otto <marcelotto.de@gmail.com>
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ This project adheres to [Semantic Versioning](http://semver.org/) and
5
+ [Keep a CHANGELOG](http://keepachangelog.com).
6
+
7
+
8
+ ## 0.1.0 - 2016-03-28
9
+
10
+ Initial release
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,5 @@
1
+ 1. Fork it ( <https://github.com/marcelotto/sycamore/fork> )
2
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
3
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
4
+ 4. Push to the branch (`git push origin my-new-feature`)
5
+ 5. Create a new Pull Request
data/CREDITS ADDED
File without changes
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'coveralls', require: false, platform: :mri
7
+ end
data/Guardfile ADDED
@@ -0,0 +1,11 @@
1
+ # -*- ruby -*-
2
+
3
+ guard 'rspec', cmd: 'bundle exec rspec', all_after_pass: false do
4
+ watch(%r{^spec/.+_spec\.rb$})
5
+ watch('spec/spec_helper.rb') { 'spec' }
6
+ watch(/spec\/support\/(.+)\.rb/) { 'spec' }
7
+
8
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
9
+ watch(%r{^lib/sycamore/(.+)\.rb$}) { |m| "spec/unit/sycamore/#{m[1]}/" }
10
+
11
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Marcel Otto
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,516 @@
1
+
2
+ # Sycamore
3
+
4
+ > _"The Egyptians' Holy Sycamore also stood on the threshold of life and death, connecting the two worlds."_
5
+ > -- [Wikipedia: Tree of Life](http://en.wikipedia.org/wiki/Tree_of_life)
6
+
7
+ [![Travis CI Build Status](https://secure.travis-ci.org/marcelotto/sycamore.png)](https://travis-ci.org/marcelotto/sycamore?branch=master)
8
+ [![Coverage Status](https://coveralls.io/repos/marcelotto/sycamore/badge.png)](https://coveralls.io/r/marcelotto/sycamore)
9
+ [![Inline docs](http://inch-ci.org/github/marcelotto/sycamore.png)](http://inch-ci.org/github/marcelotto/sycamore)
10
+ [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://rubydoc.org/gems/spread2rdf/frames)
11
+ [![Gitter Chat](http://img.shields.io/badge/chat-gitter.im-orange.svg)](https://gitter.im/marcelotto/sycamore)
12
+ [![License](http://img.shields.io/license/MIT.png?color=green)](http://opensource.org/licenses/MIT)
13
+
14
+ **Sycamore is an implementation of an unordered tree data structure.**
15
+
16
+ Features:
17
+
18
+ - easy, hassle-free access to arbitrarily deep nested elements
19
+ - grows automatically when needed
20
+ - familiar Hash interface
21
+ - no more `nil`-induced errors
22
+
23
+ Imagine a Sycamore tree as a recursively nested set. The elements of this set, called nodes, are associated with a child tree of additional nodes and so on. This might be different to your usual understanding of a tree, which has to have one single root node, but this notion is much more general. The usual tree is just a special case with just one node at the first level. But I prefer to think of the root to be implicit. Effectively every object is a tree in this sense. You can assume `self` to be the implicit root.
24
+
25
+ Restrictions:
26
+
27
+ - Only values you would use as keys of a hash should be used as nodes of a Sycamore tree. Although Ruby's official Hash documentation says *a Hash allows you to use any object type*, one is well advised [to use immutable objects only](http://jafrog.com/2012/10/07/mutable-objects-as-hash-keys-in-ruby.html). Enumerables as nodes are explicitly excluded by Sycamore.
28
+ - The nodes are unordered and can't contain duplicates.
29
+ - A Sycamore tree is uni-directional, i.e. has no relationship to its parent.
30
+
31
+ ## Why
32
+
33
+ JSON, document-oriented databases, GraphQL and much more - trees in the sense of recursively nested sets are omnipresent today. But why then are there so few implementations of tree data structures? The answer is simple: because of Ruby's powerful built-in hashes. The problem is that while Ruby's Hash, as an implementation of the [Hash map data structure](https://en.wikipedia.org/wiki/Hash_table), might be perfectly fine for flat dictionary like structures, it is not very well-suited for storing tree structures. Ruby's hash literals, which allow it to easily nest multiple hashes, belie this fact. But it catches up when you want to build up a tree with hashes dynamically and have to manage the hash nesting manually.
34
+
35
+ In contrast to the few existing implementations of tree data structures in Ruby, Sycamores is based on Ruby's very efficient hashes and contains the values directly without any additional overhead. It only wraps the hashes itself. This wrapper object is very thin, containing nothing more than the hash itself. This comes at the price of the aforementioned restrictions, prohibiting it to be a general applicable tree implementation. But I hope to get around some of these restrictions in the future.
36
+
37
+ Another compelling reason for the use of Sycamore is its handling of `nil`. Much has [been](https://www.youtube.com/watch?v=OMPfEXIlTVE) [said](http://programmers.stackexchange.com/questions/12777/are-null-references-really-a-bad-thing) about the problem of `nil` (or equivalent null-values in other languages), including: ["It was my Billion-dollar mistake"](http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare) from its founder, Tony Hoare. Every developer has experienced it in the form of errors such as
38
+
39
+ ```
40
+ NoMethodError: undefined method '[]' for nil:NilClass
41
+ ```
42
+
43
+ With Sycamore this is a thing of the past.
44
+
45
+
46
+ ## Supported Ruby versions
47
+
48
+ - MRI >= 2.1
49
+ - JRuby
50
+
51
+
52
+ ## Dependencies
53
+
54
+ - none
55
+
56
+ ## Installation
57
+
58
+ The recommended installation method is via [RubyGems](http://rubygems.org/).
59
+
60
+ $ gem install sycamore
61
+
62
+
63
+ ## Usage
64
+
65
+ I will introduce Sycamore's Tree API by comparing it with [Rubys native Hash API](http://ruby-doc.org/core-2.2.3/Hash.html).
66
+
67
+ In the following I'll always write `Tree` for the Sycamore tree class, instead of the fully qualified `Sycamore::Tree`. By default, this global `Tree` constant is not available. If you want this, you'll have to
68
+
69
+ ```ruby
70
+ require 'sycamore/extension'
71
+ ```
72
+
73
+ When you can't or don't to want to have the `Tree` alias constant in the global namespace, but still want a short alternative name, you can alternatively
74
+
75
+ ```ruby
76
+ require 'sycamore/stree'
77
+ ```
78
+
79
+ to get an alias constant `STree` with less potential for conflicts.
80
+
81
+ I recommend trying the following code yourself in a Ruby REPL like [Pry](http://pryrepl.org).
82
+
83
+
84
+ ### Creating trees
85
+
86
+ A `Sycamore::Tree` can be created similar to Hashes with the standard constructor or the class-level `[]` operator.
87
+
88
+ `Tree.new` creates an empty `Sycamore::Tree`.
89
+
90
+ ```ruby
91
+ tree = Tree.new
92
+ tree.empty? # => true
93
+ ```
94
+
95
+ No additional arguments are supported at the time. As you'll see, for a `Sycamore::Tree` the functionality of the Hash constructor to specify the default value behaviour is of too little value to justify its use in the default constructor. The decision of its use can be tracked in [issue #1]().
96
+
97
+ The `[]` operator creates a new `Tree` and adds the arguments as its initial input. It can handle a single node value, a collection of nodes or a complete tree.
98
+
99
+ ```ruby
100
+ Tree[1] # => #<Sycamore::Tree:0x3fcfe51a5a3c {1=>n/a}>
101
+ Tree[1, 2, 3] # => #<Sycamore::Tree:0x3fcfe51a56f4 {1=>n/a, 2=>n/a, 3=>n/a}>
102
+ Tree[1, 2, 2, 3] # => #<Sycamore::Tree:0x3fcfe51a52d0 {1=>n/a, 2=>n/a, 3=>n/a}>
103
+ Tree[x: 1, y: 2] # => #<Sycamore::Tree:0x3fcfe51a4e34 {:x=>1, :y=>2}>
104
+ ```
105
+
106
+ As you can see in line 3 nodes are stored as a set, i.e. with duplicates removed.
107
+
108
+ Note that multiple arguments are not interpreted as an associative array as `Hash[]` does, but rather as a set of leaves, i.e. nodes without children.
109
+
110
+ ```ruby
111
+ Hash[1, 2, 3, 4] # => {1=>2, 3=>4}
112
+ Hash[1, 2, 3] # => ArgumentError: odd number of arguments for Hash
113
+ ```
114
+
115
+ You can also see that children of leaves, i.e. nodes without children, are signified with `n/a`. When providing input data with Hashes, you can use `nil` as the child value of a leaf.
116
+
117
+ ```ruby
118
+ Tree[x: 1, y: 2, z: nil]
119
+ # => #<Sycamore::Tree:0x3fcfe51a4e34 {:x=>1, :y=>2, :z=>n/a}>
120
+ ```
121
+
122
+ In general the `nil` child value for leaves in Hash literals is mandatory, but on the first level it can be ommitted, by providing the leaves as an argument before the non-leaf nodes.
123
+
124
+ ```ruby
125
+ Tree[:a, :b, c: {d: 1, e: nil}]
126
+ # => #<Sycamore::Tree:0x3fd3f9c6bb0c {:a=>n/a, :b=>n/a, :c=>{:d=>1, :e=>n/a}}>
127
+ ```
128
+
129
+ If you really want to have a node with `nil` as a child, you'll have to put the `nil` in an array.
130
+
131
+ ```ruby
132
+ Tree[x: 1, y: 2, z: [nil]]
133
+ # => #<Sycamore::Tree:0x3fd641858264 {:x=>1, :y=>2, :z=>nil}>
134
+ ```
135
+
136
+
137
+ ### Accessing trees
138
+
139
+ Access to elements of a `Sycamore::Tree` is mostly API-compatible to that of Rubys Hash class. But there is one major difference in the return type of most of the access methods: Since we are dealing with a recursively defined tree structure, the returned children are always trees as well.
140
+
141
+ The main method for accessing a tree is the `[]` operator.
142
+
143
+ ```ruby
144
+ tree = Tree[x: 1, y: {2 => "a"}]
145
+
146
+ tree[:x] # => #<Sycamore::Tree:0x3fea48d24d40 {1=>n/a}>
147
+ tree[:y] # => #<Sycamore::Tree:0x3fea48d24b74 {2=>"a"}>
148
+ tree[:y][2] # => #<Sycamore::Tree:0x3fea48d248f4 {"a"=>n/a}>
149
+ ```
150
+
151
+ The actual nodes of a tree can be retrieved with the method `nodes`.
152
+
153
+ ```ruby
154
+ tree.nodes # => [:x, :y]
155
+ tree[:x].nodes # => [1]
156
+ tree[:y].nodes # => [2]
157
+ tree[:y][2].nodes # => ["a"]
158
+ ```
159
+
160
+ If it's certain that a tree has at most one element, you can also use `node` to get that node directly.
161
+
162
+ ```ruby
163
+ tree[:y].node # => 2
164
+ tree[:y][2].node # => "a"
165
+ tree[:x][1].node # => nil
166
+ tree.node # Sycamore::NonUniqueNodeSet: multiple nodes present: [:x, :y]
167
+ ```
168
+
169
+ As opposed to Hash, the `[]` operator of `Sycamore::Tree` also supports multiple arguments which get interpreted as a path.
170
+
171
+ ```ruby
172
+ tree[:y, 2].node # => "a"
173
+ ```
174
+
175
+ For compatibility with Ruby 2.3 hashes, this can also be done with the `dig` method.
176
+
177
+ ```ruby
178
+ tree.dig(:y, 2).node # => "a"
179
+ ```
180
+
181
+ `fetch`, as a more controlled way to access the elements, is also supported.
182
+
183
+ ```ruby
184
+ tree.fetch(:x) # => #<Sycamore::Tree:0x3fea48d24d40 {1=>n/a}>
185
+ tree.fetch(:z) # => KeyError: key not found: :z
186
+ tree.fetch(:z, :default) # => :default
187
+ tree.fetch(:z) { :default } # => :default
188
+ ```
189
+
190
+ Fetching the child of a leaf behaves almost the same as fetching the child of a non-existing node, i.e. the default value is returned or a `KeyError` gets raised. In order to differentiate these cases, a `Sycamore::ChildError` as a subclass of `KeyError` is raised when accessing the child of a leaf.
191
+
192
+ The number of nodes of a tree can be determined with `size`. This will only count direct nodes.
193
+
194
+ ```ruby
195
+ tree.size # => 2
196
+ ```
197
+
198
+ `total_size` or its short alias `tsize` returns the total number of nodes of a tree, including the nodes of children.
199
+
200
+ ```ruby
201
+ tree.total_size # => 5
202
+ tree[:y].tsize # => 2
203
+ ```
204
+
205
+ The height of a tree, i.e. the length of its longest path can be computed with the method `height`.
206
+
207
+ ```ruby
208
+ tree.height # => 3
209
+ ```
210
+
211
+ `empty?` checks if a tree is empty.
212
+
213
+ ```ruby
214
+ tree.empty? # => false
215
+ tree[:x, 1].empty? # => true
216
+ ```
217
+
218
+ `leaf?` checks if a node is a leaf.
219
+
220
+ ```ruby
221
+ tree.leaf? :x # => false
222
+ tree[:x].leaf? 1 # => true
223
+ ```
224
+
225
+ `leaves?` (or one of its aliases `external?` and `flat?`) can be used to determine this for more nodes at once.
226
+
227
+ ```ruby
228
+ Tree[1, 2, 3].leaves?(1, 2) # => true
229
+ ```
230
+
231
+ Without any arguments `leaves?` returns whether all nodes of a tree are leaves.
232
+
233
+ ```ruby
234
+ Tree[1, 2].leaves? # => true
235
+ ```
236
+
237
+ `include?` checks whether one or more nodes are in the set of nodes of this tree.
238
+
239
+ ```ruby
240
+ tree.include? :x # => true
241
+ tree.include? [:x, :y] # => true
242
+ ```
243
+
244
+ `include?` can also check whether a tree structure (incl. a hash) is a sub tree of a `Sycamore::Tree`.
245
+
246
+ ```ruby
247
+ tree.include?(x: 1, y: 2) # => true
248
+ ```
249
+
250
+
251
+ ### Accessing absent trees
252
+
253
+ There is another major difference to a hash, which is in fact just a consequence of the already mentioned difference, that the access methods (except `fetch`) **always** return trees, when asked for children: They even return a child tree, when it does not exist. When you ask a hash for a non-existent element with the `[]` operator, you'll get a `nil`, which is an incarnation of the null-problem and the cause of many bug tracking sessions.
254
+
255
+ ```ruby
256
+ hash = {x: 1, y: {2 => "a"}}
257
+ hash[:z] # => nil
258
+ hash[:z][3] # => NoMethodError: undefined method `[]' for nil:NilClass
259
+ ```
260
+
261
+ Sycamore on the other side returns a special tree, the `Nothing` tree:
262
+
263
+ ```ruby
264
+ tree = Tree[x: 1, y: {2 => "a"}]
265
+ tree[:z] # => #<Sycamore::Nothing>
266
+ tree[:z][3] # => #<Sycamore::Nothing>
267
+ ```
268
+
269
+ `Sycamore::Nothing` is a singleton `Tree` implementing a [null object](https://en.wikipedia.org/wiki/Null_Object_pattern). It behaves on every query method call like an empty tree.
270
+
271
+ ```ruby
272
+ Sycamore::Nothing.empty? # => true
273
+ Sycamore::Nothing.size # => 0
274
+ Sycamore::Nothing[42] # => #<Sycamore::Nothing>
275
+ ```
276
+
277
+ Sycamore adheres to a strict [command-query-separation (CQS)](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation). A method is either a command changing the state of the tree and returning `self` or a query method, which only computes and returns the results of the query, but leaves the state unchanged. The only exception to this strict separation is made, when it is necessary in order to preserve hash compatibility. All query methods are supported by the `Sycamore::Nothing` tree with empty tree semantics.
278
+
279
+ Among the command methods are two subclasses: additive command methods, which add elements and destructive command methods, which remove elements. These are further refined into pure additive and pure destructive command methods, which either support additions or deletions only, not both operations at once. The `Sycamore::Tree` extends Ruby's reflection API with class methods to retrieve the respective methods: `query_methods`, `command_methods`, `additive_command_methods`, `destructive_command_methods`, `pure_additive_command_methods`, `pure_destructive_command_methods`.
280
+
281
+ ```ruby
282
+ Tree.command_methods
283
+ # => [:add, :<<, :replace, :create_child, :[]=, :delete, :>>, :clear, :compact, :replace, :[]=, :freeze]
284
+ Tree.additive_command_methods
285
+ # => [:add, :<<, :replace, :create_child, :[]=]
286
+ Tree.pure_additive_command_methods
287
+ # => [:add, :<<, :create_child]
288
+ Tree.pure_destructive_command_methods
289
+ # => [:delete, :>>, :clear, :compact]
290
+ ```
291
+
292
+ Pure destructive command methods on `Sycamore::Nothing` are no-ops. All other command methods raise an exception.
293
+
294
+ ```ruby
295
+ Sycamore::Nothing.clear # => #<Sycamore::Nothing>
296
+ Sycamore::Nothing[:foo] = :bar
297
+ # => Sycamore::NothingMutation: attempt to change the Nothing tree
298
+ ```
299
+
300
+ But inspecting the `Nothing` tree returned by `Tree#[]` further shows, that this isn't the end of the story.
301
+
302
+ ```ruby
303
+ tree[:z].inspect
304
+ # => absent child of node :z in #<Sycamore::Tree:0x3fc88e04a470 {:x=>1, :y=>{2=>"a"}}>
305
+ tree[:z][3].inspect
306
+ # => absent child of node 3 in absent child of node :z in #<Sycamore::Tree:0x3fc88e04a470 {:x=>1, :y=>{2=>"a"}}>
307
+ ```
308
+
309
+ We'll actually get an `Absence` object, a [proxy object](https://en.wikipedia.org/wiki/Proxy_pattern) for the requested not yet existing tree. As long as we don't try to change it, this `Absence` object delegates all method calls to `Sycamore::Nothing`. But as soon as we call a non-pure-destructive command method, the missing tree will be created, added to the parent tree and the method call gets delegated to the now existing tree.
310
+
311
+ ```ruby
312
+ tree[:z] = 3
313
+ tree.to_h # => {:x=>1, :y=>{2=>"a"}, :z=>3}
314
+ ```
315
+
316
+ So a `Sycamore::Tree` is a tree, on which the nodes grow automatically, but only when needed. And this works recursively on arbitrarily deep nested absent trees.
317
+
318
+ ```ruby
319
+ tree[:some][:very][:deep] = :node
320
+ tree.to_h # => {:x=>1, :y=>{2=>"a"}, :z=>3, :some=>{:very=>{:deep=>:node}}}
321
+ ```
322
+
323
+ In order to determine whether a node has no children, you can simply use `empty?`.
324
+
325
+ ```ruby
326
+ tree = Tree[a: 1]
327
+ tree[:a].empty? # => false
328
+ tree[:b].empty? # => true
329
+ ```
330
+
331
+ But how can you distinguish an empty from a missing tree?
332
+
333
+ ```ruby
334
+ user = Tree[name: 'Adam', shopping_cart_items: []]
335
+
336
+ user[:shopping_cart_items].empty? # => true
337
+ user[:foo].empty? # => true
338
+ ```
339
+
340
+ One way is the use of the `absent?` method, which only returns `true` on an `Absence` object.
341
+
342
+ ```ruby
343
+ user[:shopping_cart_items].absent? # => false
344
+ user[:foo].absent? # => true
345
+ ```
346
+
347
+ Another possibility, without the need to create the `Absence` in the first place is the `leaf?` method, since it also checks for the presence of a node.
348
+
349
+ ```ruby
350
+ user.leaf? :shopping_cart_items # => true
351
+ user.leaf? :foo # => false
352
+ ```
353
+
354
+ But the `leaf?` method has as similar problem in this respect: it doesn't differentiate between absent and empty children.
355
+
356
+ ```ruby
357
+ tree = Tree[foo: nil, bar: []]
358
+ tree.leaf? :foo # => true
359
+ tree.leaf? :bar # => true
360
+ ```
361
+
362
+ `strict_leaf?` and `strict_leaves?` (or their short aliases `sleaf?` and `sleaves?`) are more strict in this regard: when a node has an empty child tree it is considered a leaf, but not a strict leaf.
363
+
364
+ ```ruby
365
+ tree.strict_leaf? :foo # => true
366
+ tree.strict_leaf? :bar # => false
367
+ ```
368
+
369
+ Besides `absent?`, the congeneric methods `blank?` (as an alias of `empty?`) and its negation `present?` are ActiveSupport compatible available. Unfortunately, the natural expectation of `Tree#present?` and `Tree#absent?` to be mutually opposed leads astray.
370
+
371
+ ```ruby
372
+ user[:shopping_cart_items].absent? # => false
373
+ user[:shopping_cart_items].present? # => false
374
+ ```
375
+
376
+ The risks rising from an ActiveSupport incompatible `present?` is probably greater then this inconsistence. So, if you want check if a tree is not absent, use `existent?` as the negation of `absent?`.
377
+
378
+ Beside these options, `fetch` is also a method to handle this situation in a nuanced way.
379
+
380
+ ```ruby
381
+ user.fetch(:shopping_cart_items) # => #<Sycamore::Tree:0x3febb9c9b3d4 {}>
382
+ user.fetch(:foo)
383
+ # => KeyError: key not found: :foo
384
+ user.fetch(:foo, :default) # => :default
385
+ ```
386
+
387
+ Empty child trees also play a role when determining equality. The `eql?` and `==` equivalence differ exactly in their handling of this question: `==` treats empty child trees as absent trees, while `eql?` doesn't.
388
+
389
+ ```ruby
390
+ Tree[:foo].eql? Tree[foo: []] # => false
391
+ Tree[:foo] == Tree[foo: []] # => true
392
+ ```
393
+
394
+ All empty child trees can be removed with `compact`.
395
+
396
+ ```ruby
397
+ Tree[:foo].eql? Tree[foo: []].compact # => true
398
+ ```
399
+
400
+ An arbitrary structure can be compared with a `Sycamore::Tree` for equality with `===`.
401
+
402
+ ```ruby
403
+ Tree[:foo, :bar] === [:foo, :bar] # => true
404
+ Tree[:foo, :bar] === Set[:foo, :bar] # => true
405
+ Tree[:foo => :bar] === {:foo => :bar} # => true
406
+ ```
407
+
408
+
409
+ ### Changing trees
410
+
411
+ Let's examine the command methods to change the contents of a tree. The `add` method or the `<<` operator as its alias allows the addition of one, multiple or a tree structure of nodes.
412
+
413
+ ```ruby
414
+ tree = Tree.new
415
+ tree << 1
416
+ tree << [2, 3]
417
+ tree << {3 => :a, 4 => :b}
418
+ puts tree
419
+ > Tree[1=>nil, 2=>nil, 3=>:a, 4=>:b]
420
+ ```
421
+
422
+ The `[]=` operator is Hash-compatible supported.
423
+
424
+ ```ruby
425
+ tree[5] = :c
426
+ puts tree
427
+ > Tree[1=>nil, 2=>nil, 3=>:a, 4=>:b, 5=>:c]
428
+ ```
429
+
430
+ Note that this is just an `add` with a previous call of `clear`, which deletes all elements of the tree. This means, you can safely assign another tree without having to think about object identity.
431
+
432
+ If you want to explicitly state, that a node doesn't have any children, you can specify it in the following equivalent ways.
433
+
434
+ ```ruby
435
+ tree[:foo] = []
436
+ tree[:foo] = {}
437
+ ```
438
+
439
+ Note that these values are interpreted similarly inside tree structures, i.e. empty Enumerables become empty child trees, while `Nothing` or `nil` are used as place holders for the explicit negation of a child.
440
+
441
+ ```ruby
442
+ puts Tree[ a: { b: nil }, c: { d: []} ]
443
+ >Tree[:a=>:b, :c=>{:d=>[]}]
444
+ ```
445
+
446
+ Beside the deletion of all elements with the already mentioned `clear` method, single or multiple nodes and entire tree structures can be removed with `delete` or the `>>` operator.
447
+
448
+ ```ruby
449
+ tree >> 1
450
+ tree >> [2, 3]
451
+ tree >> {4 => :b}
452
+ puts tree
453
+ > Tree[5=>:c, :foo=>[]]
454
+ ```
455
+
456
+ When removing a tree structure, only child trees with no more existing nodes get deleted.
457
+
458
+ ```ruby
459
+ tree = Tree[a: [1,2]]
460
+ tree >> {a: 1}
461
+ puts tree
462
+ > Tree[:a=>2]
463
+
464
+ tree = Tree[a: 1, b: 2]
465
+ tree >> {a: 1}
466
+ puts tree
467
+ > Tree[:b=>2]
468
+ ```
469
+
470
+
471
+ ### Iterating trees
472
+
473
+ The fundamental `each` and with that all Enumerable methods behave Hash-compatible.
474
+
475
+ ```ruby
476
+ tree = Tree[ 1 => {a: 'foo'}, 2 => :b, 3 => nil ]
477
+ tree.each { |node, child| puts "#{node} => #{child}" }
478
+
479
+ > 1 => Tree[:a=>"foo"]
480
+ > 2 => Tree[:b]
481
+ > 3 => Tree[]
482
+ ```
483
+
484
+ `each_path` iterates over all paths to leafs of a tree.
485
+
486
+ ```ruby
487
+ tree.each_path { |path| puts path }
488
+
489
+ > #<Path: /1/a/foo>
490
+ > #<Path: /2/b>
491
+ > #<Path: /3>
492
+ ```
493
+
494
+ The paths are represented by `Sycamore::Path` objects and are basically an Enumerable of the nodes on the path, specifically optimized for the enumeration of the set of paths of a tree. It does this, by sharing nodes between the different path objects. This means in the set of all paths, every node is contained exactly once, even the internal nodes being part of multiple paths.
495
+
496
+ ```ruby
497
+ Tree['some possibly very big data chunk' => [1, 2]].each_path.to_a
498
+ # => [#<Sycamore::Path["some possibly very big data chunk",1]>,
499
+ # #<Sycamore::Path["some possibly very big data chunk",2]>]
500
+ ```
501
+
502
+
503
+ ## Getting help
504
+
505
+ - [RDoc](http://www.rubydoc.info/gems/sycamore/)
506
+ - [Gitter](https://gitter.im/marcelotto/sycamore)
507
+
508
+
509
+ ## Contributing
510
+
511
+ see [CONTRIBUTING](CONTRIBUTING.md) for details.
512
+
513
+
514
+ ## License and Copyright
515
+
516
+ (c) 2015-2016 Marcel Otto. MIT Licensed, see [LICENSE](LICENSE.TXT) for details.