I am in the process of learning Ruby and as practice I am making a linked list class. I am in the process of writing the delete method for a doubly linked list. My question is, if I represent the list by its head node, how do I delete the head? It seems like Ruby wont allow you to assign to the self variable, so I can't change the reference of the caller to be the next node. One solution is that I can copy the key from the next node and swap references, but in general, is there a way in Ruby to change the reference of the caller?
class LinkedListNode
attr_accessor :next, :previous, :key
def initialize(key=nil, next_node=nil, previous=nil)
#next = next_node
#previous = previous
#key = key
end
def append(key=nil)
newnode = LinkedListNode.new(key)
seeker = self
while seeker.next != nil
seeker = seeker.next
end
newnode.previous = seeker
seeker.next = newnode
end
def delete(key=nil)
seeker = self
while seeker.key != key
return if seeker.next == nil
seeker = seeker.next
end
if seeker.previous != nil
if seeker.next != nil
seeker.previous.next = seeker.next
seeker.next.previous = seeker.previous
else
seeker.previous.next = nil
end
else
return self = self.next
end
return seeker = nil
end
def print
seeker = self
string = ""
while 1
if seeker.next == nil
string += seeker.key.to_s
break
else
string += seeker.key.to_s + " -> "
end
seeker = seeker.next
end
puts string
end
end
if __FILE__ == $0
ll = LinkedListNode.new(1)
ll.append(2)
ll.append(3)
ll.append(4)
ll.append(5)
ll.print
ll.delete(5)
ll.print
ll.delete(1)
ll.print
end
You can't change the which object is being pointed to by the caller (i.e. modify self), but you can manipulate the object in any way you want, as you've already thought through. The short answer is that it can't be done. You can come up with other ways to model it, but I think you're already on the right track.
You need to conceptualize a linked list differently. A LinkedListNode is a component of a LinkedList, not a LinkedList itself. Operations such as append, delete, and print should go in your LinkedList class, not your LinkedListNode class. Try starting with something like
class LinkedList
# This one-liner defines a LinkedList::Node with associated constructor
# and accessors for the three tags provided. Any tags omitted during
# construction will be initialized to nil.
Node = Struct.new(:key, :previous, :next)
attr_reader :head, :tail
def initialize
# start with no Nodes in the list
#head = #tail = nil
end
def append(key)
# Make the LinkedList tail a new node that stores the key,
# points to the prior tail as its previous reference, and
# has no next.
#tail = Node.new(key, #tail)
if #tail.previous # the prior tail was not nil
#tail.previous.next = #tail # make the prior tail point to the new one
else # if there wasn't any tail before the list was empty
#head = #tail # so the new tail Node is also the head
end
end
# ...
end
Related
I was trying to implement a ruby binary search tree, creating, adding and printing node is fine, the problem rose when i was implementing #delete
(i will post the code below) the structure for this tree is nested binary nodes (why? I am no nothing about ruby pointers)
class BinaryNode
attr_accessor :value
attr_accessor :left_node
attr_accessor :right_node
def initialize (value = nil, left_node = nil, right_node = nil)
#value = value
#left_node = left_node
#right_node = right_node
end
end
the left and right nodes will be another binary node, and it goes on
so when i want to insert a node (which is fine), i use a temp binary node to traverse the tree and when i reach my goal (if no duplicates encountered) I simply make the temp's child (left or right according to comparison), but why this works, i mean i copied the binary node, AND modified the COPIED one, but works,
here is the #insert if you need more insight
def insert (value, node = #root)
if value == node.value
return nil
elsif value > node.value
if node.right_node == nil
node.right_node = BinaryNode.new(value)
else
insert value, node.right_node
end
else
if node.left_node == nil
node.left_node = BinaryTree.new(value)
else
insert value, node.left_node
end
end
end
now when i apply the same logic for deleting node (currently stuck with leaves, have not yet explored and tested other cases) it fail, here is the code if my statement is not sufficient
def delete (value)
temp = #root
sup_node = temp
while temp.value != value
puts "currently at #{temp.value}"
if temp.value > value
temp = temp.left_node
puts "going left"
elsif temp.value < value
temp = temp.right_node
puts "going right"
end
target_node = temp
puts "target_node: #{target_node.value}"
end
if target_node.right_node == nil
puts "right node is nil"
if target_node.left_node == nil
puts "left node is nil"
puts "deleting node"
target_node = nil
else
temp_node = target_node.left_node
target_node.left_node = nil
target_node = temp_node
end
else
target_node_right = target_node.right_node
last_left_node = target_node_right
while last_left_node.left_node != nil
last_left_node = last_left_node.left_node
end
if last_left_node.right_node == nil
target_node.value = last_left_node.value
last_left_node = nil
else
last_left_parent_node = target_node_right
while last_left_parent_node.left_node != last_left_node
last_left_parent_node == last_left_parent_node.left_node
end
#some chaos going on here
last_left_parent_node.right_node = last_left_node.right
last_left_parent_node.left_node = nil
target_node.value = last_left_node.value
last_left_node = nil
end
end
end
My main question why an approach works fine in one situation but break in another, and how ruby can track copied data, and modify original, I am not interested in the binary tree algorithms it self (any problem probably will be easily searchable)
Thanks in advance
by the way sorry for being long, if you want the whole code (although I think what i copied is sufficient) you can find it on github
I'm implementing a binary search tree in ruby, and I'm attempting to define a function to remove an value from the tree. The Tree has a #root value that points to a Node object. which is defined as such:
class Node
attr_reader :value
attr_accessor :left, :right
def initialize value=nil
#value = value
#left = nil
#right = nil
end
# Add new value as child node to self, left or ride
# allows for duplicates, to L side of tree
def insert new_value
if new_value <= #value
#left.nil? ? #left = Node.new(new_value) : #left.insert(new_value)
elsif new_value > #value
#right.nil? ? #right = Node.new(new_value) : #right.insert(new_value)
end
end
end
Thus, the nodes all hold references to their L & R children. Everything works on the tree, inserting, traversing, performing a breadth-first-search (level-order-traverse) to return a Node value, if found.
The problem I'm having is when trying to remove a Node object. I can't figure out how to set the actual OBJECT to nil, or to its child, for example, rather than reassigning a POINTER/variable to nil and the object still existing.
Tree has two functions that are supposed to do this (you can assume that breadth_first_search correctly returns the appropriate found node, as does smallest_r_child_of)
def remove value
node = breadth_first_search(value)
return false if node.nil?
remove_node(node)
end
def remove_node node
if node.left.nil? && node.right.nil?
node = nil
elsif !node.left.nil? && node.right.nil?
node = node.left
elsif node.left.nil? && !node.right.nil?
node = node.right
else
node = smallest_r_child_of(node)
end
return true
end
I thought that by passing in the actual node object to remove_node, that I could call node = ____ and the like to reassign the actual object to something else, but all it does, as far as I can tell, is reset the node argument variable/pointer, while not actually reassigning my data at all.
Does anyone have any tips/suggestions on how to accomplish what I'm trying to do?
You cannot "set an object to nil". An object can never change its class, it either is an instance of Node or it is an instance of NilClass, it cannot at one point in time be an instance of Node and at another point in time be an instance of NilClass.
Likewise, an object cannot change its identity, object #4711 will always be object #4711, however, nil is a singleton, so there is only one nil and it has the same identity during the entire lifetime of the system.
What you can do is to bind the variable which references the object to nil. You are doing the opposite operation inside your insert method.
I have a binary tree implementation as below. I'd like to add a method that recursively sums up all node values of the binary tree:
class BST
class Node
attr_reader :value, :left, :right
def initialize(value)
#value = value
#left = nil
#right = nil
end
def insert(value)
if value <= #value
#left.nil? ? #left = Node.new(value) : #left.insert(value)
elsif value > #value
#right.nil? ? #right = Node.new(value) : #right.insert(value)
end
end
end
def initialize
#root = nil
end
def insert(value)
#root.nil? ? #root = Node.new(value) : #root.insert(value)
end
end
I found the answer for other languages, however unfortunately not for Ruby.
I think your code in the comments was:
def sum(node=#root)
return if node.nil?
total += node.value
sum(node.left)
sum(node.right)
end
The idea is almost okay. There is no summing in nil nodes; and you total up the current node's value, the left node and the right node. Here's the mistakes:
total += node.value is the first time we see total. This initialises it to nil. When you try to add node.value to it, you get the error you described. To avoid it, either total must already exist, or you can just assign node.value to it.
If a function ends without executing a return statement, it returns the last evaluated expression; in this case, sum(node.right). Wouldn't it be better if sum returned total?
Conversely, sum(node.left) will presumably do some summing... but its return value is discarded. It might make sense to add it to the total. Speaking of totals, maybe we should do the same for sum(node.right).
Finally, return if node.nil? says you refuse to sum the nodes that are not actually nodes. That's great... except that return returns nil, and if you try to total a nil with something, it doesn't go well. There are two solutions here: refuse to total a node before you enter it, or say that a nil node has value 0, which does not affect a sum.
Taking it all together, here's my two versions:
# refuse to enter a nil node:
def sum(node=#root)
total = node.value
total += sum(node.left) unless node.left.nil?
total += sum(node.right) unless node.right.nil?
total
end
# treat nil nodes as if they were zeroes:
def sum(node=#root)
return 0 if node.nil?
node.value + sum(node.left) + sum(node.right)
end
Update: I'm realizing now my question should be: how do I pass an argument by REFERENCE? I have a clue here: 'pass parameter by reference' in Ruby? but I don't nkow if it's even possible. Might be worth closing this question in fact.
In reference to this previous question: How to reverse a linked list in Ruby
I'm a bit confused why the list isn't reversed in place.
So my expectation would be that:
print_values(node3)
Would initially give:
12 -> 99 -> 37 -> nil
And then when I call: reverse_list(node3)
I would expect a call to print_values(node3) to give 37 -> 99 -> 12 -> nil
However, I'm instead getting 12 -> nil
To a degree this makes sense because the object referenced by node3 still has a value of 12 and has a next_node of nil, but is there a way to reverse the list completely in place in Ruby?
There seems to be something here about mutability w/r/t ruby that I'm not quite grasping. It's as though with every new addition to the memory stack/function call we get new objects? Some clarification here would be much appreciated.
I've also creatd my own method to try to get the functionality working in place:
def reverse_list(list, previous=nil)
rest_of_list = list.next_node
print "rest of list: "
print_values(rest_of_list)
list.next_node = previous
previous = list
print "new previous: "
print_values(previous)
list = rest_of_list
print "new list: "
print_values(list)
if list.nil?
print "list now nil\n"
list = previous
print "updated list: "
print_values(list)
return list
else
reverse_list(list, previous)
end
end
In answer to your updated question:
Yes you will have to reverse the values of the list, instead of the pointers.
To do this AFAIK you have to first build a new reversed list, and then copy the reversed values back into the original list.
I have updated the original code to be more ruby-ish (i.e. the methods are part of the list class, and the "!" suffix is used to indicate the method will modify the object (rather than just return a new value.)
<script type="text/ruby">
def print(s)
s = s.gsub("\n", "<br/>").gsub(" ", " ")
Element['#output'].html = Element['#output'].html + s
end
class LinkedListNode
attr_accessor :value, :next_node
def initialize(value, next_node=nil)
#value = value
#next_node = next_node
end
def reverse_list(reversed=nil)
if next_node
next_node.reverse_list(LinkedListNode.new(value, reversed))
else
LinkedListNode.new(value, reversed)
end
end
def reverse_list!(list=self, reversed_list = reverse_list)
self.value = reversed_list.value
if next_node
next_node.reverse_list!(list, reversed_list.next_node)
else
list
end
end
def print_values
print "[#{object_id}]#{value} --> "
if next_node.nil?
print "nil\n"
else
next_node.print_values
end
end
end
node1 = LinkedListNode.new(37)
node2 = LinkedListNode.new(99, node1)
node3 = LinkedListNode.new(12, node2)
print "original list: "
node3.print_values
print "reversed list: "
node3.reverse_list.print_values
print "reversed in place: "
node3.reverse_list!
node3.print_values
</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://rawgit.com/reactrb/reactrb-express/master/reactrb-express.js"></script>
<div id="output" style="font-family: courier"></div>
There is no pass by reference in Ruby
For all but the most basic classes such as numbers, there are methods that will modify an object in place, thus accomplishing much of the functionality of call by reference.
I'm writing a recursive toString method in a binary tree class in Ruby. The algorithm uses two methods as shown below. I have a local variable called result, which is to hold the concatenation of all the values in the tree. For some odd reason, when I concatenate result in the toString algorithm, the root of the tree changes.
def toString
currentNode = #root
result = "<"
if nil != currentNode
result << (getString currentNode, result)
end
result = result + ">"
return result
end
def getString(currentNode, result)
if currentNode != nil
result = currentNode.value.toString
if nil != currentNode.left
result << "," # Through debugging, I have noticed that it starts here!!!!
result.concat (self.getString currentNode.left, result)
end
if nil != currentNode.right
result << ","
result.concat (self.getString currentNode.right, result)
end
end
return result
end
So, where I have noted in the comment above, the value at the root of the tree begins to change. Instead of concatenating with result, it concatenates with the root, which changes the tree, instead of simply traversing and concatenating the values. I have used << and concat. Same result. Could someone please help shed some light on this? I am fairly new to Ruby. Thank you. :)
Contrary to other languages (like java or c#) Strings in ruby are not immutable (by default). This means that if currentNode.value.toString returns the actual string value of the value (and not a copy of it) calling << on it will change the value itself:
class Dummy
attr_accessor :value
end
dummy = Dummy.new
dummy.value = 'test'
result = dummy.value
result << 'something else'
# => "testsomething else"
dummy.value
# => "testsomething else" # !!!!
To avoid that you need to make sure you return a duplicate of the value, and not the value itself.
dummy.value = 'test'
result = dummy.value.dup
result << 'something else'
# => "testsomething else"
dummy.value
# => "test" # :-)