Giter Club home page Giter Club logo

traversal's Introduction

Synopsys

Simple traversal API for pure Ruby objects. Also it can be used it with ActiveRecord or DataMapper (or any other ORM).

Installation

Install it via rubygems:

gem install traversal

In your Gemfile:

gem 'traversal'

Usage

Imagine tree(-ish) structure:

plants:
  vegetables:
    - cucumber
    - tomato
  fruits:
    - apple
    - banana

Let's create tree structure from above in Ruby:

class Node
  attr_reader :name, :children

  def initialize(name)
    @name = name
    @children = []
  end
end

# create data now
root       = Node.new('plants')
vegetables = Node.new('vegetables')
fruits     = Node.new('fruits')
cucumber   = Node.new('cucumber')
tomato     = Node.new('tomato')
apple      = Node.new('apple')
banana     = Node.new('banana')

root.children << vegetables << fruits
vegetables.children << cucumber << tomato
fruits.children << apple << banana

So, we have a simple tree with root element on the top of it. Now let's create a traversal description.

require 'traversal'

traversal = Traversal::Description.new
traversal.traverse(root).    # start from root node
          follow(:children)  # move forward via children relations

Or you can use shortcut:

class Node
  acts_as_traversable
end

traversal = root.traverse(:children) # same as above

It's a minimal traversal description. It has start node and relation pointer (children, in this case). Traversal description is Enumerable (iterable) object. Let's examine our traverse:

traversal.map(&:name) # should be equal to [root, vegetables, cucumber, tomato, fruits, apple, banana]

Let's look closer:

  1. We are starting from root node. It's first element.
  2. Traversal cursor follows root's relation children and moves to the first child of root: vegetables
  3. Cursor moves deeper (it is default strategy), to first child of vegetables node: cucumber
  4. cucumber has no children, traversal cursor moves to the next child of vegetables: tomato
  5. tomato has no children, and vegetables has no more unvisited descendant nodes, so the traversal cursor moves to the next child of root: fruits
  6. Traversal cursor visits children of fruits: apple and banana
  7. All nodes are visited, cursor closed.

If you want the cursor to visit all children before visiting grandchildren, then you have to declare breadth_first traversal visiting strategy:

traversal.breadth_first # in opposite of traversal.depth_first

You can exclude nodes (but allow cursor to follow relations) from final result:

traversal.exclude { |node| node.children.length > 0 } # all nodes with children will be excluded from result

# opposite version:
traversal.include_only { |node| node.children.length > 0 }

You can prune away (do not expand, i.e. do not follow node's relations) any node (but leave the node in final result):

traversal.prune(vegetables).
          map(&:name) # will produce [root, vegetables, fruits, apple, banana]

# opposite version:
traversal.expand_only(root, vegetables) # it means: expand root node and vegetables node, prune away other nodes

Or, you can exclude node and prune away it's children:

traversal.prune_and_exclude { |node| node.name == "vegetables" }.
          map(&:name) # will produce [root, fruits, apple, banana]

Also, you can mark any node as "loop terminator":

traversal.stop_before(vegetables).to_a # will produce only [root]
traversal.stop_after(vegetables).to_a # will produce [root, vegetables]

By default traverser does not visit already visited nodes, but you can change that behavior:

traversal.uniq(false)

Real world example

require "traversable"

class Page < ActiveRecord::Base
  acts_as_tree
  acts_as_traversable
end

lim = 15

# traverse through first 15 descendant
Page.root.traverse(:children).
          exclude(:root?).
          exclude_and_prune(:is_deleted?).
          stop_after { (lmt -= 1) == 0 }

# traverse through whole tree, yield only leaf nodes
Page.root.traverse(:children).
          exclude_and_prune(:is_deleted?).
          include_only { |page| page.children.empty? }

leaf = Page.last # not root node

# traverse through ancestors, exclude last ancestor and its children
leaf.traverse(:parent).
     exclude(:root?).
     exclude { |node| node.member_of?(root.children) }

traversal's People

Contributors

take-five avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.