Chapter 3 Stacks and Queues

Problem 3.1: Describe how you could use a single array to implement three stacks.

Splitting the array into three individual parts is quite intuitive.

How can we take full advantage of the unused space? I was inspired by the implementation of circular queue. Actually, in my perspective, my solution is better than the one on answer page. The idea is not complicated: When there is no free space next to top of the stack, into which we want to push a value, I shift other stacks to make a free space available for the stack.

It takes me more than two hours to implement the class. Thanks to work break down method, I encapsulated some work into utility functions and made the implementation quite smooth.
class n_stacks:
    def __init__(self, stacks_num, array_size):
        if array_size < stacks_num:
            print "array_size is too small"
            return
        self.stacks_num = stacks_num
        self.array = [None for i in range(0, array_size)]
        size_per_stack = int(array_size/stacks_num)
        self.tops = [size_per_stack*i for i in range(0, self.stacks_num)]
        self.bottoms = self.tops[:]
    def push(self, stack_no, value):
        next_stack_no = next_in_circle(stack_no, self.stacks_num)
        # If there is no room between given stack and the next stack
        if self.tops[stack_no] == self.bottoms[next_stack_no]:
            # If failed to make room
            if not self.make_room_for(stack_no):
                return False
        # If we can come here, there is some room available
        self.array[self.tops[stack_no]] = value
        self.tops[stack_no] = next_in_circle(self.tops[stack_no], len(self.array))
        return True
    def pop(self, stack_no):
        # If there is no element in this stack
        if self.tops[stack_no] == self.bottoms[stack_no]:
            return None
        self.tops[stack_no] = pre_in_circle(self.tops[stack_no], len(self.array))
        return self.array[self.tops[stack_no]]
    def length(self, stack_no):
        if self.bottoms[stack_no] <= self.tops[stack_no]:
            return (self.tops[stack_no] - self.bottoms[stack_no])
        else:
            return (len(self.array) - (self.bottoms[stack_no] - self.tops[stack_no])) 
    def make_room_for(self, stack_no):
        # Iterate all stacks after the given one
        # and try to find a free element between two of them
        current_no = next_in_circle(stack_no, self.stacks_num)
        while current_no != stack_no:
            next_no = next_in_circle(current_no, self.stacks_num)
            # If there is a free element between current stack and next one
            if self.tops[current_no] != self.bottoms[next_no]:
                # A stack occupies at least one element (even when it is empty)
                # Whether there is a free element based on the additional condition above?
                current_top_next = next_in_circle(self.tops[current_no], len(self.array))
                if (current_top_next == self.bottoms[next_no]) and (self.tops[current_no] == self.bottoms[current_no]):
                    # If there is no free element, continue
                    pass
                else:
                    # If there is a free element, break and utilize the free element
                    break
            current_no = next_no
        # If we did not find a free element
        if current_no == stack_no:
            return False
        # If we find one, shift stacks to make the
        # free element available for given stack
        while current_no != stack_no:
            self.shift_stack_forward(current_no)
            current_no = pre_in_circle(current_no, self.stacks_num)
        return True
    def shift_stack_forward(self, stack_no):
        # Shift all elements in the given stack 
        n = self.tops[stack_no]
        while n != self.bottoms[stack_no]:
            n_pre = pre_in_circle(n, len(self.array))
            self.array[n] = self.array[n_pre] 
            n = n_pre
        # Shift the top and bottom
        self.tops[stack_no] = next_in_circle(self.tops[stack_no], len(self.array))
        self.bottoms[stack_no] = next_in_circle(self.bottoms[stack_no], len(self.array))
            
# Utility function to get next index in a circle
def next_in_circle(current, total):
    return (current+1)%total
# Utility function to get previous index in a circle
def pre_in_circle(current, total):
    result = current - 1
    if result < 0:
        return (total + result)
    else:
        return result

Here are the test cases:
from n_stacks import *
if __name__ == "__main__":
    triple_stacks = n_stacks(3, 9)
    print "stack 0: length", triple_stacks.length(0)
    print "stack 1: length", triple_stacks.length(1)
    print "stack 2: length", triple_stacks.length(2)
    print "stack 0: push 0", triple_stacks.push(0, 0)
    print "stack 0: push 1", triple_stacks.push(0, 1) 
    print "stack 0: push 2", triple_stacks.push(0, 2)
    print "stack 1: push 10", triple_stacks.push(1, 10)
    print "stack 1: push 11", triple_stacks.push(1, 11) 
    print "stack 1: push 12", triple_stacks.push(1, 12)
    print "stack 2: push 20", triple_stacks.push(2, 20)
    print "stack 2: push 21", triple_stacks.push(2, 21) 
    print "stack 2: push 22", triple_stacks.push(2, 22)
    print "stack 2: push 23", triple_stacks.push(2, 23)
    print "stack 0: pop", triple_stacks.pop(0)
    print "stack 2: push 23", triple_stacks.push(2, 23)
    print "stack 2: push 24", triple_stacks.push(2, 24)
    print "stack 1: pop", triple_stacks.pop(1)
    print "stack 2: push 24", triple_stacks.push(2, 24)
    print "stack 2: pop", triple_stacks.pop(2)
    print "stack 2: pop", triple_stacks.pop(2)
    print "stack 2: pop", triple_stacks.pop(2)
    print "stack 2: pop", triple_stacks.pop(2)
    print "stack 2: pop", triple_stacks.pop(2)
    print "stack 2: pop", triple_stacks.pop(2)
    print "stack 0: push 2", triple_stacks.push(0, 2)
    print "stack 0: push 3", triple_stacks.push(0, 3)
    print "stack 0: push 4", triple_stacks.push(0, 4)
    print "stack 0: push 5", triple_stacks.push(0, 5)
    print "stack 0: push 6", triple_stacks.push(0, 6)
    print "stack 2: push 20", triple_stacks.push(2, 20)
    print "stack 2: push 21", triple_stacks.push(2, 21)
    print "stack 0: length", triple_stacks.length(0)
    print "stack 1: length", triple_stacks.length(1)
    print "stack 2: length", triple_stacks.length(2)
    print "stack 0: pop", triple_stacks.pop(0)
    print "stack 0: pop", triple_stacks.pop(0)
    print "stack 0: pop", triple_stacks.pop(0)
    print "stack 0: pop", triple_stacks.pop(0)
    print "stack 0: pop", triple_stacks.pop(0)
    print "stack 0: pop", triple_stacks.pop(0)
    print "stack 0: pop", triple_stacks.pop(0)
    print "stack 1: push 12", triple_stacks.push(1, 12)
    print "stack 1: push 13", triple_stacks.push(1, 13)
    print "stack 1: push 14", triple_stacks.push(1, 14)
    print "stack 1: push 15", triple_stacks.push(1, 15)
    print "stack 1: push 16", triple_stacks.push(1, 16)
    print "stack 1: push 17", triple_stacks.push(1, 17)
    print "stack 0: length", triple_stacks.length(0)
    print "stack 1: length", triple_stacks.length(1)
    print "stack 2: length", triple_stacks.length(2)
    print "stack 1: pop", triple_stacks.pop(1)
    print "stack 1: pop", triple_stacks.pop(1)
    print "stack 1: pop", triple_stacks.pop(1)
    print "stack 1: pop", triple_stacks.pop(1)
    print "stack 1: pop", triple_stacks.pop(1)
    print "stack 1: pop", triple_stacks.pop(1)
    print "stack 1: pop", triple_stacks.pop(1)
    print "stack 1: pop", triple_stacks.pop(1)
    print "stack 0: length", triple_stacks.length(0)
    print "stack 1: length", triple_stacks.length(1)
    print "stack 2: length", triple_stacks.length(2)
    print "stack 2: pop", triple_stacks.pop(2)
    print "stack 2: pop", triple_stacks.pop(2)
    print "stack 0: length", triple_stacks.length(0)
    print "stack 1: length", triple_stacks.length(1)
    print "stack 2: length", triple_stacks.length(2)


;