Linked Lists are one approach to producing a dynamically sized collection of elements. In a Linked List, a collection of elements is represented as a chain of Nodes.
The most basic Node has two things:
- A value (also known as an element)
- A pointer to another Node
In other words, Linked Lists are lists that are composed of many Nodes each referencing the next.
Linked Lists are useful when you need a dynamically sized collection. Unlike a fixed-array, you can expand a Linked List by simple pointing its last Node to a new Node.
Linked Lists are also efficient when inserting nodes in the middle of a collection. We'll see why that is as we build out our LinkedList
implementation.
In the following releases do not use Ruby's built in data structures, including Arrays, Hashes or Sets in your implementation. The only structures you should use are objects from classes you define. You may also use the FixedArray class if you see a use.
The elements (values) you store in your list may be any kind of object, because they do not affect the implementation.
Since Linked Lists are made up of Nodes, let's start by creating a Node
class.
Implement and write RSpec tests for the Node
class, supporting the following interface:
Node#new(element)
: Instantiate a new node containingelement
Node#insert_after(other_node)
: Insertother_node
after this node. In other words,other_node
becomes the node that this instance points to.Node#remove_after()
: Remove the node that this node points to (aka the node "after" this node)
Now that you've implemented and tested Node
let's build up our LinkedList
class.
Implement and write RSpec tests for the LinkedList
class, supporting the following interface.
LinkedList#new
: Instantiate a new linked listLinkedList#insert_first(element)
: Insert an element at the front of the listLinkedList#remove_first
: Remove the element at the front of the list ornil
if absentLinkedList#insert_last(element)
: Insert an element at the back of the listLinkedList#remove_last
: Remove the element at the back of the list ornil
if absent
Now you have a basic LinkedList
class implemented. Let's expand it to make it more useful as a collection.
Implement and write RSpec tests for the LinkedList
class, expanding to support the following interface.
LinkedList#get(index)
: Get the element in the list atindex
LinkedList#set(index, element)
: Set the element in the list atindex
LinkedList#size
: Return the size of the list
By now you have the following methods on your LinkedList class:
LinkedList#new
LinkedList#insert_first(element)
LinkedList#remove_first(element)
LinkedList#insert_last(element)
LinkedList#remove_last(element)
LinkedList#get(index)
LinkedList#set(index, element)
LinkedList#size
For each of these methods, determine the big-O complexity of the method. Create a file complexity.md
and write the big-O for each method, explaining why. How do these methods compare to similar methods in your Resizable Array class?
For example, LinkedList#new
is O(1)
— whether our list ends up containing 0 elemnets or 1000, #new
will always take the same amount of time.
After you have figured out the big-O for each method, answer the following question in complexity.md
:
- Why is inserting a value in the middle of the collection faster with a LinkedList than it is with an Array?
After the last release, you should have a sense of which methods in your Linked List implementation are fast and which are slow.
Let's revisit our implementation and speed it up.
In this release, ensure that remove_first
and remove_last
run in constant (O(1)
) time. Remember, that means the remove_*
methods should run just as fast whether it's a list of 2 nodes or 1000 nodes.
Think about what state the list needs to keep track of to accomplish this. What design trade offs must you make to achieve this result?
Since you've already written tests for these methods, ensure that they still run. You're using your tests as a safety net in this refactor.
Change the implementation of you class to ensure that the size
method runs in constant time as well. What did you have to do to make
What changes did you need to make to complete releases 4 and 5? What are the downsides to the change you made? Add your thoughts to complexity.md
.