How to Implement Data Structures in Ruby?
Last Updated :
05 Apr, 2024
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:

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:

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:

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:

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:

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:

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:

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:

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:
- Left rotation
- Right rotation
- Left-right rotation (also known as double rotation)
- 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:

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:

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:

Similar Reads
Ruby | Struct to_a() function
The to_a() is an inbuilt method in Ruby that returns an array with the value of the particular struct. Syntax: struct_name.to_a[integer] Parameters: The function accepts an integer parameter which specifies the index of the struct value to be returned. Return Value: It returns the value of struct. E
1 min read
Data Structures in R Programming
A data structure is a particular way of organizing data in a computer so that it can be used effectively. The idea is to reduce the space and time complexities of different tasks. Data structures in R programming are tools for holding multiple values. Râs base data structures are often organized by
6 min read
How to Sort a Hash in Ruby?
In this article, we will learn how to sort a Hash in ruby. We can sort the map by key, from low to high or high to low, using the sortBy method. Syntax:sorted_hash = original_hash.sort_by { |key, value| expression }.to_h Example 1: In this example, we sort a hash in ascending order [GFGTABS] Ruby #
2 min read
Ruby | Struct values_at() function
The values_at() is an inbuilt method in Ruby that returns an array with the struct members values. Selector can be of two types: Integer or Range offset. Syntax: struct_name.values_at(range) Parameters: The function takes a single parameter range which will specify the start and end of the struct me
1 min read
How to import structtype in Scala?
Scala stands for scalable language. It was developed in 2003 by Martin Odersky. It is an object-oriented language that provides support for a functional programming approach as well. Everything in scala is an object e.g. - values like 1,2 can invoke functions like toString(). Scala is a statically t
4 min read
How to Work with JSON Data in Ruby?
JSON, which stands for JavaScript Object Notation, is a format used to exchange data between different systems. It is popular because it is easy for both computers and humans to understand. This article focuses on discussing working with JSON data in Ruby. Table of Content Installing the JSON GemPar
3 min read
How to Convert String to JSON in Ruby?
In this article, we will learn how to convert a string to JSON in Ruby. String to JSON conversion consists of parsing a JSON-formatted string into a corresponding JSON object, enabling structured data manipulation and interoperability within Ruby applications. Converting String to JSON in RubyBelow
2 min read
What is Queue Data Structure?
What is Queue Data Structure?A Queue is defined as a linear data structure that is open at both ends and the operations are performed in First In First Out (FIFO) order. We define a queue to be a list in which all additions to the list are made at one end, and all deletions from the list are made at
2 min read
What is Data Structure?
A data structure is a way of organizing and storing data in a computer so that it can be accessed and used efficiently. It refers to the logical or mathematical representation of data, as well as the implementation in a computer program. Classification:Data structures can be classified into two broa
2 min read
How To Convert Data Types in Ruby?
Data type conversion is a frequent job in Ruby, particularly when working with various data types or integrating with other systems. Ruby offers a wide range of operators and methods for fluid data type conversion. Effective Ruby programming requires an understanding of these conversion methods. The
2 min read