Open In App

How to Implement Data Structures in Ruby?

Last Updated : 05 Apr, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

Data structures are fundamental components of any programming language, allowing developers to organize and manipulate data efficiently. In Ruby, a versatile and expressive language, implementing various data structures is straightforward. In this article, we’ll explore how to implement common data structures such as arrays, linked lists, stacks, queues, trees, graphs, and hashmaps in Ruby. The article focuses on discussing data structures in Ruby.

Singly-Linked Lists

A singly linked list is a linear data structure consisting of a sequence of elements, where each element points to the next element in the sequence. The last element points to nil, indicating the end of the list. Singly-linked lists are simple to implement and efficient for operations such as insertion and deletion at the beginning of the list.

Below is the Ruby program to implement a singly-linked list:

Ruby
class Node
  attr_accessor :value, :next

  def initialize(value)
    @value = value
    @next = nil
  end
end

class SinglyLinkedList
  def initialize
    @head = nil
  end

  def append(value)
    if @head.nil?
      @head = Node.new(value)
    else
      current = @head
      while current.next
        current = current.next
      end
      current.next = Node.new(value)
    end
  end

  def display
    current = @head
    while current
      puts current.value
      current = current.next
    end
  end
end

list = SinglyLinkedList.new
list.append(1)
list.append(2)
list.append(3)
list.display

Output:

sl1

Doubly-Linked Lists

A doubly-linked list is a type of linked list where each element contains pointers to both the next and previous elements in the sequence. This allows for traversal in both forward and backward directions. Doubly-linked lists support efficient insertion and deletion operations at both ends of the list.

Below is the Ruby program to implement a doubly-linked list:

Ruby
class Node
  attr_accessor :value, :next, :prev

  def initialize(value)
    @value = value
    @next = nil
    @prev = nil
  end
end

class DoublyLinkedList
  def initialize
    @head = nil
    @tail = nil
  end

  def append(value)
    if @head.nil?
      @head = Node.new(value)
      @tail = @head
    else
      new_node = Node.new(value)
      new_node.prev = @tail
      @tail.next = new_node
      @tail = new_node
    end
  end

  def display_forward
    current = @head
    while current
      puts current.value
      current = current.next
    end
  end

  def display_backward
    current = @tail
    while current
      puts current.value
      current = current.prev
    end
  end
end

list = DoublyLinkedList.new
list.append(1)
list.append(2)
list.append(3)
list.display_forward
puts "--------"
list.display_backward

Output:

dl

Circular Linked Lists

A circular linked list is a variation of a linked list where the last element points back to the first element, forming a circular structure. This can be achieved by making the next pointer of the last node point to the first node. Circular linked lists can be singly or doubly-linked and are useful for applications like managing circular buffers or implementing algorithms like Floyd’s cycle detection algorithm.

Below is the Ruby program to implement a circular-linked list:

Ruby
class Node
  attr_accessor :value, :next

  def initialize(value)
    @value = value
    @next = nil
  end
end

class CircularSinglyLinkedList
  def initialize
    @head = nil
  end

  def append(value)
    if @head.nil?
      @head = Node.new(value)
      @head.next = @head  # Make it circular
    else
      current = @head
      while current.next != @head
        current = current.next
      end
      current.next = Node.new(value)
      current.next.next = @head  # Make it circular
    end
  end

  def display
    current = @head
    loop do
      puts current.value
      current = current.next
      break if current == @head
    end
  end
end

list = CircularSinglyLinkedList.new
list.append(1)
list.append(2)
list.append(3)
list.display

Output:
sl1

Queues

A queue is a linear data structure that follows the First-In-First-Out (FIFO) principle. In a queue, elements are inserted at the rear (enqueue) and removed from the front (dequeue). It operates in a similar way to a queue of people waiting in line for a service.

Below is the Ruby program to implement a queue:

Ruby
class Queue
  def initialize
    @elements = []
  end

  def enqueue(item)
    @elements.push(item)
  end

  def dequeue
    @elements.shift
  end

  def empty?
    @elements.empty?
  end
end

# Example Usage
queue = Queue.new
queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3)

puts queue.dequeue # Output: 1
puts queue.dequeue # Output: 2
puts queue.empty?  # Output: false
puts queue.dequeue # Output: 3
puts queue.empty?  # Output: true

Output:

Screenshot-2024-03-29-213918

Stack

A stack is a linear data structure that follows the Last-In-First-Out (LIFO) principle. In a stack, elements are inserted and removed from the same end, called the top of the stack. It operates similarly to a stack of plates where you can only add or remove the top plate.

Below is the Ruby program to implement a stack:

Ruby
class Stack
  def initialize
    @elements = []
  end

  def push(item)
    @elements.push(item)
  end

  def pop
    @elements.pop
  end

  def empty?
    @elements.empty?
  end
end

# Example Usage
stack = Stack.new
stack.push(1)
stack.push(2)
stack.push(3)

puts stack.pop # Output: 3
puts stack.pop # Output: 2
puts stack.empty? # Output: false
puts stack.pop # Output: 1
puts stack.empty? # Output: true

Output:

Screenshot-2024-03-29-214019

Hash Tables

A hash table is a data structure that stores key-value pairs. It uses a hash function to compute an index into an array where the desired value can be found. Hash tables offer efficient lookup, insertion, and deletion operations.

Below is the Ruby program to implement hash tables:

Ruby
class HashTable
  def initialize(size)
    @size = size
    @table = Array.new(size)
  end

  def put(key, value)
    index = hash(key)
    @table[index] = value
  end

  def get(key)
    index = hash(key)
    @table[index]
  end

  private

  def hash(key)
    key.hash % @size
  end
end

# Example Usage
hash_table = HashTable.new(5)
hash_table.put(:apple, "A fruit")
hash_table.put(:banana, "Another fruit")

puts hash_table.get(:apple)   # Output: "A fruit"
puts hash_table.get(:banana)  # Output: "Another fruit"

Output:
Screenshot-2024-03-29-214111

Sets

In Ruby, a set is a collection of unique elements, meaning each element appears only once in the set. Sets are useful for tasks where you need to ensure uniqueness or perform set operations like union, intersection, and difference.

Ruby provides the Set class in the standard library to work with sets. You can create a set using the Set.new method and perform various set operations using built-in methods like union, intersect, and difference.

Below is the Ruby program to implement sets:

Ruby
require 'set'

# Creating sets
set1 = Set.new([1, 2, 3, 4, 5])
set2 = Set.new([4, 5, 6, 7, 8])

# Union of sets
union_set = set1.union(set2)
puts union_set.inspect  # => #<Set: {1, 2, 3, 4, 5, 6, 7, 8}>

# Intersection of sets
intersection_set = set1.intersection(set2)
puts intersection_set.inspect  # => #<Set: {4, 5}>

# Difference of sets
difference_set = set1.difference(set2)
puts difference_set.inspect  # => #<Set: {1, 2, 3}>

Output:

Screenshot-2024-03-29-214225

Binary Trees

A binary tree is a hierarchical data structure in which each node has at most two children, referred to as the left child and the right child. It consists of nodes, where each node contains a value and references to its left and right children.

In Ruby, a binary tree can be implemented using a class to represent each node and defining methods to manipulate the tree structure.

Below is the Ruby program to implement a binary tree:

Ruby
class TreeNode
  attr_accessor :value, :left, :right

  def initialize(value)
    @value = value
    @left = nil
    @right = nil
  end
end

# Create a binary tree
root = TreeNode.new(1)
root.left = TreeNode.new(2)
root.right = TreeNode.new(3)
root.left.left = TreeNode.new(4)
root.left.right = TreeNode.new(5)

# Traverse the binary tree (e.g., inorder traversal)
def inorder_traversal(node)
  return if node.nil?

  inorder_traversal(node.left)
  puts node.value
  inorder_traversal(node.right)
end

inorder_traversal(root)

Output:

Screenshot-2024-03-29-214314

AVL Trees (Adelson-Velsky and Landis Trees)

AVL trees are self-balancing binary search trees designed to maintain balance during insertions and deletions. In an AVL tree, the heights of the two child subtrees of any node differ by at most one. This ensures that the tree remains balanced, which helps in maintaining efficient search, insertion, and deletion operations.

Balancing Operations

AVL trees employ rotation operations to maintain balance. There are four types of rotations:

  1. Left rotation
  2. Right rotation
  3. Left-right rotation (also known as double rotation)
  4. Right-left rotation (also known as double rotation)

Insertion

When inserting a new node into an AVL tree, the tree may become unbalanced. To restore balance, rotation operations are performed as necessary.

Deletion

Similarly, when deleting a node from an AVL tree, the tree may become unbalanced. Rotation operations are employed to restore balance in this scenario as well.

Below is the Ruby program to implement AVL Tree:

Ruby
class AVLNode
  attr_accessor :value, :left, :right, :height

  def initialize(value)
    @value = value
    @left = nil
    @right = nil
    @height = 1
  end
end

class AVLTree
  attr_reader :root

  def initialize
    @root = nil
  end

  def insert(value)
    @root = insert_node(@root, value)
  end

  def inorder_traversal(node = @root)
    return unless node

    inorder_traversal(node.left)
    puts node.value
    inorder_traversal(node.right)
  end

  private

  def insert_node(node, value)
    return AVLNode.new(value) unless node

    if value < node.value
      node.left = insert_node(node.left, value)
    else
      node.right = insert_node(node.right, value)
    end

    node.height = [height(node.left), height(node.right)].max + 1

    balance = balance_factor(node)

    # Perform rotations if the node becomes unbalanced
    if balance > 1 && value < node.left.value
      return right_rotate(node)
    end

    if balance < -1 && value > node.right.value
      return left_rotate(node)
    end

    if balance > 1 && value > node.left.value
      node.left = left_rotate(node.left)
      return right_rotate(node)
    end

    if balance < -1 && value < node.right.value
      node.right = right_rotate(node.right)
      return left_rotate(node)
    end

    node
  end

  def height(node)
    return 0 unless node

    node.height
  end

  def balance_factor(node)
    return 0 unless node

    height(node.left) - height(node.right)
  end

  def right_rotate(y)
    x = y.left
    t2 = x.right

    x.right = y
    y.left = t2

    y.height = [height(y.left), height(y.right)].max + 1
    x.height = [height(x.left), height(x.right)].max + 1

    x
  end

  def left_rotate(x)
    y = x.right
    t2 = y.left

    y.left = x
    x.right = t2

    x.height = [height(x.left), height(x.right)].max + 1
    y.height = [height(y.left), height(y.right)].max + 1

    y
  end
end

avl_tree = AVLTree.new
avl_tree.insert(10)
avl_tree.insert(20)
avl_tree.insert(30)
avl_tree.insert(40)
avl_tree.insert(50)
avl_tree.insert(25)

puts "In-order traversal of AVL tree:"
avl_tree.inorder_traversal

Output:

Screenshot-2024-03-29-214701

Graphs

A graph is a data structure consisting of a set of vertices (nodes) and a set of edges connecting these vertices. Graphs are widely used to represent relationships between objects, such as social networks, computer networks, and transportation networks. They can be directed (edges have a specific direction) or undirected (edges have no direction).

Graphs can be implemented using various approaches in Ruby. One common approach is to use an adjacency list or adjacency matrix to represent the connections between vertices.

Below is the Ruby program to implement an undirected graph using an adjacency list:

Ruby
class Graph
  def initialize
    @adjacency_list = {}
  end

  def add_vertex(vertex)
    @adjacency_list[vertex] = []
  end

  def add_edge(vertex1, vertex2)
    @adjacency_list[vertex1] << vertex2
    @adjacency_list[vertex2] << vertex1
  end

  def neighbors(vertex)
    @adjacency_list[vertex]
  end
end

graph = Graph.new
graph.add_vertex("A")
graph.add_vertex("B")
graph.add_vertex("C")
graph.add_vertex("D")
graph.add_edge("A", "B")
graph.add_edge("B", "C")
graph.add_edge("C", "D")

puts graph.neighbors("A").inspect  # => ["B"]
puts graph.neighbors("B").inspect  # => ["A", "C"]
puts graph.neighbors("C").inspect  # => ["B", "D"]
puts graph.neighbors("D").inspect  # => ["C"]

Output:
Screenshot-2024-03-29-214800

Persistent Lists

Persistent lists are immutable data structures where elements cannot be modified after creation. Operations on persistent lists create new instances with the desired changes while preserving the original list. This ensures that the original list remains unchanged, making persistent lists ideal for functional programming.

Persistent lists can be implemented using various approaches, such as linked lists or trees.

Below is the Ruby program to implement a persistent list using a linked list structure:

Ruby
class ListNode
  attr_accessor :value, :next

  def initialize(value, next_node = nil)
    @value = value
    @next = next_node
  end
end

class PersistentList
  attr_reader :head

  def initialize(head = nil)
    @head = head
  end

  def prepend(value)
    new_head = ListNode.new(value, @head)
    PersistentList.new(new_head)
  end

  def to_a
    result = []
    current = @head
    while current
      result << current.value
      current = current.next
    end
    result
  end
end

list1 = PersistentList.new
list2 = list1.prepend(1)
list3 = list2.prepend(2)
list4 = list3.prepend(3)

puts list1.to_a.inspect  # => []
puts list2.to_a.inspect  # => [1]
puts list3.to_a.inspect  # => [2, 1]
puts list4.to_a.inspect  # => [3, 2, 1]

Output:
Screenshot-2024-03-29-214843



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads