{"id":13747395,"url":"https://github.com/marcelotto/sycamore","last_synced_at":"2025-05-10T19:37:55.934Z","repository":{"id":59157017,"uuid":"54910943","full_name":"marcelotto/sycamore","owner":"marcelotto","description":"A tree data structure for Ruby","archived":false,"fork":false,"pushed_at":"2018-05-29T22:41:08.000Z","size":295,"stargazers_count":93,"open_issues_count":0,"forks_count":5,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-05-05T19:01:50.319Z","etag":null,"topics":["datastructures","ruby","tree"],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/sycamore","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/marcelotto.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-03-28T17:41:48.000Z","updated_at":"2024-01-23T16:34:18.000Z","dependencies_parsed_at":"2022-09-13T17:52:13.706Z","dependency_job_id":null,"html_url":"https://github.com/marcelotto/sycamore","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcelotto%2Fsycamore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcelotto%2Fsycamore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcelotto%2Fsycamore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcelotto%2Fsycamore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marcelotto","download_url":"https://codeload.github.com/marcelotto/sycamore/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253238791,"owners_count":21876452,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["datastructures","ruby","tree"],"created_at":"2024-08-03T06:01:27.501Z","updated_at":"2025-05-09T10:45:38.847Z","avatar_url":"https://github.com/marcelotto.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"\n# Sycamore\n\n\u003e _\"The Egyptians' Holy Sycamore also stood on the threshold of life and death, connecting the two worlds.\"_  \n\u003e   -- [Wikipedia: Tree of Life](http://en.wikipedia.org/wiki/Tree_of_life)\n\n[![Gem Version](https://badge.fury.io/rb/sycamore.svg)](http://badge.fury.io/rb/sycamore)\n[![Travis CI Build Status](https://secure.travis-ci.org/marcelotto/sycamore.png)](https://travis-ci.org/marcelotto/sycamore?branch=master)\n[![Coverage Status](https://coveralls.io/repos/marcelotto/sycamore/badge.png)](https://coveralls.io/r/marcelotto/sycamore)\n[![Inline docs](http://inch-ci.org/github/marcelotto/sycamore.png)](http://inch-ci.org/github/marcelotto/sycamore)\n[![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://rubydoc.org/gems/sycamore/frames)\n[![Gitter Chat](http://img.shields.io/badge/chat-gitter.im-orange.svg)](https://gitter.im/marcelotto/sycamore)\n[![License](http://img.shields.io/license/MIT.png?color=green)](http://opensource.org/licenses/MIT)\n\n**Sycamore is an implementation of an unordered tree data structure.**\n\nFeatures:\n\n- easy, hassle-free access to arbitrarily deep nested elements\n- grows automatically when needed\n- familiar Hash interface\n- no more `nil`-induced errors\n\nImagine 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.\n\nRestrictions:\n\n- 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.\n- The nodes are unordered and can't contain duplicates.\n- A Sycamore tree is uni-directional, i.e. has no relationship to its parent.\n\n## Why\n\nTrees 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.\n\nIn 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.\n\nAnother 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 \n\n```\nNoMethodError: undefined method '[]' for nil:NilClass\n```\n\nWith Sycamore this is a thing of the past.\n\n\n## Supported Ruby versions\n\n- MRI \u003e= 2.1\n- JRuby\n- Rubinius\n\n\n## Dependencies\n\n- none\n\n## Installation\n\nThe recommended installation method is via [RubyGems](http://rubygems.org/).\n\n    $ gem install sycamore\n\n\n## Usage\n\nI will introduce Sycamore's Tree API by comparing it with [Ruby's Hash API](http://ruby-doc.org/core-2.2.3/Hash.html).\n\nIn 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 \n\n```ruby\nrequire 'sycamore/extension'\n``` \n\nWhen 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\n\n```ruby\nrequire 'sycamore/stree'\n``` \n\nto get an alias constant `STree` with less potential for conflicts.\n\nI recommend trying the following code yourself in a Ruby REPL like [Pry](http://pryrepl.org).\n\n\n### Creating trees\n\nA `Sycamore::Tree` can be created similar to Hashes with the standard constructor or the class-level `[]` operator.\n\n`Tree.new` creates an empty `Sycamore::Tree`.\n\n```ruby\ntree = Tree.new\ntree.empty?  # =\u003e true\n```\n\nNo 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, so I'd like to reserve them for something more useful.\n\nThe `[]` 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. \n\n```ruby\nTree[1]           # =\u003e #\u003cSycamore::Tree:0x3fcfe51a5a3c {1=\u003en/a}\u003e\nTree[1, 2, 3]     # =\u003e #\u003cSycamore::Tree:0x3fcfe51a56f4 {1=\u003en/a, 2=\u003en/a, 3=\u003en/a}\u003e\nTree[1, 2, 2, 3]  # =\u003e #\u003cSycamore::Tree:0x3fcfe51a52d0 {1=\u003en/a, 2=\u003en/a, 3=\u003en/a}\u003e\nTree[x: 1, y: 2]  # =\u003e #\u003cSycamore::Tree:0x3fcfe51a4e34 {:x=\u003e1, :y=\u003e2}\u003e\n```\n\nAs you can see in line 3 nodes are stored as a set, i.e. with duplicates removed.\n\nNote 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.\n\n```ruby\nHash[1, 2, 3, 4]  # =\u003e {1=\u003e2, 3=\u003e4}\nHash[1, 2, 3]     # =\u003e ArgumentError: odd number of arguments for Hash\n```\n\nYou 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.\n\n```ruby\nTree[x: 1, y: 2, z: nil]  \n# =\u003e #\u003cSycamore::Tree:0x3fcfe51a4e34 {:x=\u003e1, :y=\u003e2, :z=\u003en/a}\u003e\n```\n\nIn 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.\n\n```ruby\nTree[:a, :b, c: {d: 1, e: nil}]\n# =\u003e #\u003cSycamore::Tree:0x3fd3f9c6bb0c {:a=\u003en/a, :b=\u003en/a, :c=\u003e{:d=\u003e1, :e=\u003en/a}}\u003e\n```\n\nIf you really want to have a node with `nil` as a child, you'll have to put the `nil` in an array.\n\n```ruby\nTree[x: 1, y: 2, z: [nil]]  \n# =\u003e #\u003cSycamore::Tree:0x3fd641858264 {:x=\u003e1, :y=\u003e2, :z=\u003enil}\u003e\n```\n\n\n### Accessing trees\n\nAccess 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.\n\nThe main method for accessing a tree is the `[]` operator.\n\n```ruby\ntree = Tree[x: 1, y: {2 =\u003e \"a\"}]\n\ntree[:x]    # =\u003e #\u003cSycamore::Tree:0x3fea48d24d40 {1=\u003en/a}\u003e\ntree[:y]    # =\u003e #\u003cSycamore::Tree:0x3fea48d24b74 {2=\u003e\"a\"}\u003e\ntree[:y][2] # =\u003e #\u003cSycamore::Tree:0x3fea48d248f4 {\"a\"=\u003en/a}\u003e\n```\n\nThe actual nodes of a tree can be retrieved with the method `nodes`.\n\n```ruby\ntree.nodes  # =\u003e [:x, :y]\ntree[:x].nodes  # =\u003e [1]\ntree[:y].nodes  # =\u003e [2]\ntree[:y][2].nodes  # =\u003e [\"a\"]\n```\n\nIf it's certain that a tree has at most one element, you can also use `node` to get that node directly.\n\n```ruby\ntree[:y].node     # =\u003e 2\ntree[:y][2].node  # =\u003e \"a\"\ntree[:x][1].node  # =\u003e nil\ntree.node  # Sycamore::NonUniqueNodeSet: multiple nodes present: [:x, :y]\n```\n\nThe bang variant `node!` raises an error when the node set is empty, instead of returning `nil`.\n\n```ruby\ntree[:y][2].node!  # =\u003e \"a\"\ntree[:x][1].node!  # =\u003e # Sycamore::EmptyNodeSet: no node present\n```\n\nAs opposed to Hash, the `[]` operator of `Sycamore::Tree` also supports multiple arguments which get interpreted as a path.\n\n```ruby\ntree[:y, 2].node  # =\u003e \"a\"\n```\n\nFor compatibility with Ruby 2.3 Hashes, this can also be done with the `dig` method.\n\n```ruby\ntree.dig(:y, 2).node  # =\u003e \"a\"\n```\n\n`fetch`, as a more controlled way to access the elements, is also supported.\n\n```ruby\ntree.fetch(:x)               # =\u003e #\u003cSycamore::Tree:0x3fea48d24d40 {1=\u003en/a}\u003e\ntree.fetch(:z)               # =\u003e KeyError: key not found: :z\ntree.fetch(:z, :default)     # =\u003e :default\ntree.fetch(:z) { :default }  # =\u003e :default\n```\n\nFetching 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.\n\n`fetch_path` allows a `dig` similar access with `fetch` semantics, except it requires the path of nodes to be given as an Enumerable.\n\n```ruby\ntree.fetch_path([:y, 2]).node  # =\u003e \"a\"\ntree.fetch_path([:y, 3])               # =\u003e KeyError: key not found: 3\ntree.fetch_path([:y, 3], :default)     # =\u003e :default\ntree.fetch_path([:y, 3]) { :default }  # =\u003e :default\n```\n\nThe number of nodes of a tree can be determined with `size`. This will only count direct nodes.\n\n```ruby\ntree.size  # =\u003e 2\n```\n\n`total_size` or its short alias `tsize` returns the total number of nodes of a tree, including the nodes of children.\n\n```ruby\ntree.total_size  # =\u003e 5\ntree[:y].tsize   # =\u003e 2\n```\n\nThe height of a tree, i.e. the length of its longest path can be computed with  the method `height`.\n\n```ruby\ntree.height  # =\u003e 3\n```\n\n`empty?` checks if a tree is empty.\n\n```ruby\ntree.empty?         # =\u003e false\ntree[:x, 1].empty?  # =\u003e true\n```\n\n`leaf?` checks if a node is a leaf.\n\n```ruby\ntree.leaf? :x     # =\u003e false\ntree[:x].leaf? 1  # =\u003e true\n```\n\n`leaves?` (or one of its aliases `external?` and `flat?`) can be used to determine this for more nodes at once.\n\n```ruby\nTree[1, 2, 3].leaves?(1, 2)  # =\u003e true\n```\n\nWithout any arguments `leaves?` returns whether all nodes of a tree are leaves.\n\n```ruby\nTree[1, 2].leaves?  # =\u003e true\n```\n\n`include?` checks whether one or more nodes are in the set of nodes of this tree.\n\n```ruby\ntree.include? :x        # =\u003e true\ntree.include? [:x, :y]  # =\u003e true\n```\n\n`include?` can also check whether a tree structure (incl. a hash) is a sub tree of a `Sycamore::Tree`.\n\n```ruby\ntree.include?(x: 1, y: 2)  # =\u003e true\n```\n\n`to_h` returns the tree as a Hash.\n\n```ruby\ntree.to_h  # =\u003e {:x=\u003e1, :y=\u003e{2=\u003e\"a\"}}\n```\n\n### Accessing absent trees\n\nThere is another major difference in the access method behaviour of a Scyamore tree in comparison to hashes: The child access methods even return a 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.\n\n```ruby\nhash = {x: 1, y: {2 =\u003e \"a\"}}\nhash[:z]  # =\u003e nil\nhash[:z][3]  # =\u003e NoMethodError: undefined method `[]' for nil:NilClass\n```\n\nSycamore on the other side returns a special tree, the `Nothing` tree:\n\n```ruby\ntree = Tree[x: 1, y: {2 =\u003e \"a\"}]\ntree[:z]     # =\u003e #\u003cSycamore::Nothing\u003e\ntree[:z][3]  # =\u003e #\u003cSycamore::Nothing\u003e\n```\n\n`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.\n\n```ruby\nSycamore::Nothing.empty?  # =\u003e true\nSycamore::Nothing.size    # =\u003e 0\nSycamore::Nothing[42]     # =\u003e #\u003cSycamore::Nothing\u003e\n```\n\nSycamore 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.\n\nAmong 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`.\n\n```ruby\nTree.command_methods\n# =\u003e [:add, :\u003c\u003c, :replace, :create_child, :[]=, :delete, :\u003e\u003e, :clear, :compact, :replace, :[]=, :freeze]\nTree.additive_command_methods\n# =\u003e [:add, :\u003c\u003c, :replace, :create_child, :[]=]\nTree.pure_additive_command_methods\n# =\u003e [:add, :\u003c\u003c, :create_child]\nTree.pure_destructive_command_methods\n# =\u003e [:delete, :\u003e\u003e, :clear, :compact]\n```\n\nPure destructive command methods on `Sycamore::Nothing` are no-ops. All other command methods raise an exception.\n\n```ruby\nSycamore::Nothing.clear  # =\u003e #\u003cSycamore::Nothing\u003e\nSycamore::Nothing[:foo] = :bar  \n# =\u003e Sycamore::NothingMutation: attempt to change the Nothing tree\n```\n\nBut inspecting the `Nothing` tree returned by `Tree#[]` further shows, that this isn't the end of the story.\n\n```ruby\ntree[:z].inspect\n# =\u003e absent child of node :z in #\u003cSycamore::Tree:0x3fc88e04a470 {:x=\u003e1, :y=\u003e{2=\u003e\"a\"}}\u003e\ntree[:z][3].inspect\n# =\u003e absent child of node 3 in absent child of node :z in #\u003cSycamore::Tree:0x3fc88e04a470 {:x=\u003e1, :y=\u003e{2=\u003e\"a\"}}\u003e\n```\n\nWe'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.\n\n```ruby\ntree[:z] = 3\ntree.to_h  # =\u003e {:x=\u003e1, :y=\u003e{2=\u003e\"a\"}, :z=\u003e3}\n```\n\nSo 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.\n\n```ruby\ntree[:some][:very][:deep] = :node\ntree.to_h  # =\u003e {:x=\u003e1, :y=\u003e{2=\u003e\"a\"}, :z=\u003e3, :some=\u003e{:very=\u003e{:deep=\u003e:node}}}\n```\n\nIn order to determine whether a node has no children, you can simply use `empty?`.\n\n```ruby\ntree = Tree[a: 1]\ntree[:a].empty?  # =\u003e false\ntree[:b].empty?  # =\u003e true  \n```\n\nBut how can you distinguish an empty from a missing tree?\n\n```ruby\nuser = Tree[name: 'Adam', shopping_cart_items: []]\n\nuser[:shopping_cart_items].empty?  # =\u003e true\nuser[:foo].empty?                  # =\u003e true\n```\n\nOne way is the use of the `absent?` method, which only returns `true` on an `Absence` object.\n\n```ruby\nuser[:shopping_cart_items].absent?  # =\u003e false\nuser[:foo].absent?                  # =\u003e true\n```\n\nAnother 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.\n\n```ruby\nuser.leaf? :shopping_cart_items         # =\u003e true\nuser.leaf? :foo                         # =\u003e false\n```\n\nBut the `leaf?` method has as similar problem in this respect: it doesn't differentiate between absent and empty children.\n\n```ruby\ntree = Tree[foo: nil, bar: []]\ntree.leaf? :foo         # =\u003e true\ntree.leaf? :bar         # =\u003e true\n```\n\n`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.\n\n```ruby\ntree.strict_leaf? :foo  # =\u003e true\ntree.strict_leaf? :bar  # =\u003e false\n```\n\nBesides `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.\n\n```ruby\nuser[:shopping_cart_items].absent?   # =\u003e false\nuser[:shopping_cart_items].present?  # =\u003e false\n```\n\nThe 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?`.\n\nBeside these options, `fetch` is also a method to handle this situation in a nuanced way.\n\n```ruby\nuser.fetch(:shopping_cart_items)  # =\u003e #\u003cSycamore::Tree:0x3febb9c9b3d4 {}\u003e\nuser.fetch(:foo)                            \n# =\u003e KeyError: key not found: :foo\nuser.fetch(:foo, :default)  # =\u003e :default\n```\n\nEmpty 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.\n\n```ruby\nTree[:foo].eql? Tree[foo: []]  # =\u003e false\nTree[:foo] == Tree[foo: []]    # =\u003e true\n```\n\nAll empty child trees can be removed with `compact`.\n\n```ruby\nTree[:foo].eql? Tree[foo: []].compact  # =\u003e true\n```\n\nAn arbitrary structure can be compared with a `Sycamore::Tree` for equality with `===`.\n\n```ruby\nTree[:foo, :bar] === [:foo, :bar]      # =\u003e true\nTree[:foo, :bar] === Set[:foo, :bar]   # =\u003e true\nTree[:foo =\u003e :bar] === {:foo =\u003e :bar}  # =\u003e true\n```\n\n\n### Changing trees\n\nLet's examine the command methods to change the contents of a tree. The `add` method or the `\u003c\u003c` operator as its alias allows the addition of one, multiple or a tree structure of nodes.\n\n```ruby\ntree = Tree.new\ntree \u003c\u003c 1\ntree \u003c\u003c [2, 3]\ntree \u003c\u003c {3 =\u003e :a, 4 =\u003e :b}\nputs tree \n\u003e Tree[1=\u003enil, 2=\u003enil, 3=\u003e:a, 4=\u003e:b]\n```\n\nThe `[]=` operator is Hash-compatible supported.\n\n```ruby\ntree[5] = :c\nputs tree \n\u003e Tree[1=\u003enil, 2=\u003enil, 3=\u003e:a, 4=\u003e:b, 5=\u003e:c]\n```\n\nNote 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.\n\nIf you want to explicitly state, that a node doesn't have any children, you can specify it in the following equivalent ways.\n\n```ruby\ntree[:foo] = []\ntree[:foo] = {}\n```\n\nTo remove a child tree entirely, you can assign `Nothing` or `nil` to the parent node.\n\n```ruby\ntree[:foo] = Nothing\ntree[:foo] = nil\n```\n\nIf you really want to overwrite the current child nodes with a single `nil` node, you have to do it in the following way.\n\n```ruby\ntree[:foo] = [nil]\n```\n\nNote that all of these values are interpreted consistently inside input tree structures on creation, addition, deletion etc., i.e. empty Enumerables become empty child trees, `Nothing` or `nil` are used as place holders for the explicit negation of a child and `[nil]` is used for a child trees with a single `nil` node.\n\n```ruby\nputs Tree[ a: { b: nil }, c: { d: []}, d: [nil] ]\n\u003eTree[:a=\u003e:b, :c=\u003e{:d=\u003e[]}, :d=\u003e[nil]]\n```\n\nBeside 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 `\u003e\u003e` operator.\n\n```ruby\ntree \u003e\u003e 1\ntree \u003e\u003e [2, 3]\ntree \u003e\u003e {4 =\u003e :b}\nputs tree \n\u003e Tree[5=\u003e:c, :foo=\u003e[]]\n```\n\nWhen removing a tree structure, only child trees with no more existing nodes get deleted.\n\n```ruby\ntree = Tree[a: [1,2]]\ntree \u003e\u003e {a: 1}\nputs tree \n\u003e Tree[:a=\u003e2]\n\ntree = Tree[a: 1, b: 2]\ntree \u003e\u003e {a: 1}\nputs tree \n\u003e Tree[:b=\u003e2]\n```\n\n\n### Iterating trees\n\nThe fundamental `each` and with that all Enumerable methods behave Hash-compatible.\n\n```ruby\ntree = Tree[ 1 =\u003e {a: 'foo'}, 2 =\u003e :b, 3 =\u003e nil ]\ntree.each { |node, child| puts \"#{node} =\u003e #{child}\" }\n\n\u003e 1 =\u003e Tree[:a=\u003e\"foo\"]\n\u003e 2 =\u003e Tree[:b]\n\u003e 3 =\u003e Tree[]\n```\n\n`each_path` iterates over all paths to leafs of a tree. \n\n```ruby\ntree.each_path { |path| puts path }\n\n\u003e #\u003cPath: /1/a/foo\u003e\n\u003e #\u003cPath: /2/b\u003e\n\u003e #\u003cPath: /3\u003e\n```\n\nThe 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.\n\n```ruby\nTree['some possibly very big data chunk' =\u003e [1, 2]].each_path.to_a\n# =\u003e [#\u003cSycamore::Path[\"some possibly very big data chunk\",1]\u003e,\n#     #\u003cSycamore::Path[\"some possibly very big data chunk\",2]\u003e]\n```\n\n\n### Searching in trees\n\n`search` returns the set of all paths to child trees containing a node or tree.\n\n```ruby\ntree = Tree[ 1 =\u003e {a: 'foo'}, 2 =\u003e :b, 3 =\u003e [:a, :b, :c] ]\ntree.search :a        # =\u003e [#\u003cSycamore::Path[1]\u003e, #\u003cSycamore::Path[1]\u003e]\ntree.search a: 'foo'  # =\u003e [#\u003cSycamore::Path[1]\u003e]\n```\n\nIf you search for multiple nodes, only the paths to child trees containing all of the given nodes are returned.\n\n```ruby\ntree.search [:b, :c]  # =\u003e [#\u003cSycamore::Path[3]\u003e]\n```\n\nAll `Tree` methods for which it makes sense accept path objects as input instead or in combination with nodes or tree structures. This allows it to apply the search results to any of these methods.\n\n\n## Getting help\n\n- [RDoc](http://www.rubydoc.info/gems/sycamore/)\n- [Gitter](https://gitter.im/marcelotto/sycamore)\n\n\n## Contributing\n\nsee [CONTRIBUTING](CONTRIBUTING.md) for details.\n\n\n## License and Copyright\n\n(c) 2015-2016 Marcel Otto. MIT Licensed, see [LICENSE](LICENSE.txt) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcelotto%2Fsycamore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcelotto%2Fsycamore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcelotto%2Fsycamore/lists"}