Esempio n. 1
0
def checkPar(s):
    ''' check balanced parenthesis
    
    Args:
        s (str): string of parenthesis
    
    '''
    
    S = Stack()

    for i in s:
        
        if S.isEmpty():
            
            if i == '(':
                S.push(i)
                
            # imbalance
            else:
                return False
        
        else:
            if S.peek() == i:
                S.push(i)
                
            # cancel out parenthesis
            else:
                S.pop()
    
    # only if empty (all canceled out) then balanced
    return S.isEmpty()
Esempio n. 2
0
    def find_path(self, start: Vertex, end: Vertex):
        """return the path from start to end vertices as a graph"""
        vertice_stack = Stack(implementation='linked_list')
        edge_stack = Stack(implementation='linked_list')
        for i in range(self.nb_vertices):
            vertice_stack.push(end)
            if end is start:
                break
            edge = self.parent_edges[end]
            if edge:
                edge_stack.push(edge)
                end = edge.head
            else:
                raise Exception(f'No path found between {start} and {end}')
        else:
            raise Exception(f'No path found between {start} and {end}')
        vertices = []
        while True:
            try:
                vertices.append(vertice_stack.pop())
            except StackEmptyError:
                break
        edges = []
        while True:
            try:
                edges.append(edge_stack.pop())
            except StackEmptyError:
                break

        return Graph(vertices=vertices, edges=edges, directed=True)
Esempio n. 3
0
    def evaluateRPN(self):
        workstack = Stack()

        # As we pull tokens from the queue, we validate them and if neither a number
        # nor an operator, we abort with an error.
        for t in self.q.copy():
            if (t in self.precedence):
                # As we work backwards, right value is first; validate
                right = workstack.pop()
                if (not str(right).isnumeric()
                        and not right in self.precedence):
                    self.error = True
                    break

                # Now get left value, validate
                # Special case: ! only takes one argument. Make them identical
                if (t == "!"):
                    left = right
                else:
                    left = workstack.pop()
                    if (not str(left).isnumeric()
                            and not left in self.precedence):
                        self.error = True
                        break

                # Both valid, so calculate
                workstack.push(self.calculate(left, right, t))
            else:
                workstack.push(int(t))

        # answer is now on the stack
        if (not self.error):
            return (workstack.pop())
        else:
            return (0)
Esempio n. 4
0
def checkPar(s):
    ''' check balanced parenthesis
    
    Args:
        s (str): string of parenthesis
    
    '''

    S = Stack()

    for i in s:

        if S.isEmpty():

            if i == '(':
                S.push(i)

            # imbalance
            else:
                return False

        else:
            if S.peek() == i:
                S.push(i)

            # cancel out parenthesis
            else:
                S.pop()

    # only if empty (all canceled out) then balanced
    return S.isEmpty()
class SetOfStacks:

    LIMIT_PER_STACK = 2

    def __init__(self):
        self.main_stack = Stack()

    def pop(self):
        if self.is_empty():
            return None
        elif self._top_stack().is_empty():
            self.main_stack.pop()
            self.pop()

        return self._top_stack().pop()

    def push(self, item):
        if self.is_empty():
            self.main_stack.push(Stack())
        self._top_stack().push(item)

    def is_empty(self):
        return self.main_stack.is_empty()

    def peek(self):
        if self.is_empty():
            return None
        return self._top_stack().peek().value

    def _top_stack(self):
        return self.main_stack.peek()
def test_linked_list_implementation():
    stack = Stack(implementation='linked_list')
    a, b, c, d = list('abcd')
    stack.push(a)
    assert stack.pop() is a
    stack.push(b)
    stack.push(c)
    assert stack.pop() is c
    stack.push(d)
    assert stack.pop() is d
    assert stack.pop() is b
Esempio n. 7
0
def evalpostfix(postfixexpr):
    nums = Stack()
    tokenlist = list(postfixexpr.replace(' ', ''))
    for token in tokenlist:
        if token not in prec.keys():
            nums.push(token)
        else:
            right = nums.pop()
            left = nums.pop()
            expression = left + token + right
            result = eval(expression)
            nums.push(str(result))
    if nums.size() == 1:
        return nums.pop()
class Queue2Stack(object):

    def __init__(self):
        from datastructures import Stack
        self.inbox = Stack()
        self.outbox = Stack()

    def push(self,item):
        self.inbox.push(item)

    def pop(self):
        if self.outbox.isEmpty():
            while not self.inbox.isEmpty():
                self.outbox.push(self.inbox.pop())
        return self.outbox.pop()
Esempio n. 9
0
def ids(problem):
    """
  Implement iterative deepening search.
  """
    start = problem.get_start_state()
    depth = 1
    while True:
        s = Stack()
        visited = {}
        d_map = {}
        cur = start
        s.push(cur)
        visited[start] = None
        d_map[start] = 1
        while not s.is_empty():
            cur = s.pop()
            if problem.is_goal_state(cur):
                path = []
                while cur is not None:
                    path.append(cur)
                    cur = visited[cur]
                path.reverse()
                print len(visited)
                return path
            if d_map[cur] != depth:
                for state in problem.get_successors(cur):
                    if state not in visited or d_map[cur] + 1 < d_map[state]:
                        s.push(state)
                        visited[state] = cur
                        d_map[state] = d_map[cur] + 1
        depth += 1
Esempio n. 10
0
def dfs(problem):
    """
  Implement depth-first search.
  """
    start = problem.get_start_state()
    s = Stack()
    visited = {}
    cur = start
    s.push(start)
    visited[start] = None
    while not s.is_empty():
        cur = s.pop()
        if problem.is_goal_state(cur):
            break
        for state in problem.get_successors(cur):
            if state not in visited:
                s.push(state)
                visited[state] = cur
    path = []
    while cur is not None:
        path.append(cur)
        cur = visited[cur]
    path.reverse()
    print len(visited)
    return path
Esempio n. 11
0
def checkBalance(s, d = {'(':')', '[':']', '{': '}', '<':'>'}):
    ''' check balanced symbols
    
    Args:
        s (str): string of parenthesis
        d (dict): dictionary key: open symbol, value = close symbol
        
    Returns:
        boolean: True if balanced, False if not balanced
    
    '''
    
    S = Stack()

    for i in s:
        
        # push if open symbol
        if i in d:
            S.push(i)
        
        # unbalanced if no opening to match closing or mismatch open/close symbols
        elif S.isEmpty() or d[S.pop()] != i:
            return False
        
        # no need because popped in previous statment instead of peeked
        # there is a match in open/close so cancel out by popping
        # else:
           #S.pop()
                
    
    # only if empty (all canceled out) then balanced
    return S.isEmpty()
Esempio n. 12
0
    def critical(self, start="START"):
        """
        1. for each node follow the predecessors if the node has no slack.
        2. traverse predecessor and repeat step 1.
        
        The function will return one critical path. If there are more critical
        paths it will only return one arbitrary of them.
        
        """
        top = self.topological()
        path = Stack([self.tasks[start]])
        bad = set()

        def _visits(task):
            if len(task.successors.values()) == 0:
                return True
            for t in task.successors.values():
                if t.earliest_start == t.latest_start and t not in bad:
                    path.push(t)
                    return _visits(t)
            return False

        for t in top:
            if _visits(t):
                break
            pop = path.pop()
            bad.add(t)
        return path
Esempio n. 13
0
    def find_shortest_path(self, start_vetex, end_vertex):
        """
        Return a list of vertices creating a path from the start to end vertices.

        Keyword arguments:
        start_vetex -- the starting vertex.
        end_vertex -- the ending vertex.

        Time complexity: O(V)
        Space complexity: O(V)
        """
        if end_vertex.distance == float('inf'):
            return []
        else:
            reverse = Stack()
            current_vertex = end_vertex
            reverse.push(current_vertex)
            while current_vertex != start_vetex:
                current_vertex = current_vertex.previous_vertex
                reverse.push(current_vertex)

            path = []
            while not reverse.is_empty():
                path.append(reverse.pop())

            return path
Esempio n. 14
0
def checkBalance(s, d={'(': ')', '[': ']', '{': '}', '<': '>'}):
    ''' check balanced symbols
    
    Args:
        s (str): string of parenthesis
        d (dict): dictionary key: open symbol, value = close symbol
        
    Returns:
        boolean: True if balanced, False if not balanced
    
    '''

    S = Stack()

    for i in s:

        # push if open symbol
        if i in d:
            S.push(i)

        # unbalanced if no opening to match closing or mismatch open/close symbols
        elif S.isEmpty() or d[S.pop()] != i:
            return False

        # no need because popped in previous statment instead of peeked
        # there is a match in open/close so cancel out by popping
        # else:
        #S.pop()

    # only if empty (all canceled out) then balanced
    return S.isEmpty()
Esempio n. 15
0
def my_pow(base, exp, modulus):
    from datastructures import Stack
    powers = Stack()
    var_exp = exp
    while var_exp > 0:
        powers.push(var_exp % 2)
        var_exp = var_exp / 2
    rem = 1
    while not powers.is_empty():
        p = powers.pop()
        rem = ((base ** p) * ((rem ** 2) % modulus)) % modulus
    return rem
Esempio n. 16
0
def infix2postfix(infixexpr):
    opstack = Stack()
    postfix_list = []
    tokenlist = list(infixexpr.replace(' ', ''))
    for token in tokenlist:
        if token not in prec.keys() and token != ')':
            postfix_list.append(token)
        elif token == '(':
            opstack.push(token)
        elif token == ')':
            top_token = opstack.pop()
            while top_token != '(':
                postfix_list.append(top_token)
                top_token = opstack.pop()
        else:
            while (not opstack.isEmpty()
                   and prec[opstack.peek()] >= prec[token]):
                postfix_list.append(opstack.pop())
            opstack.push(token)
    while not opstack.isEmpty():
        postfix_list.append(opstack.pop())
    return ''.join(postfix_list)
def backtrack(
    a: list,
    is_a_solution: Callable[[list, int, Any], bool],
    construct_candidates: Callable[[list, int, Any], Iterator],
    inputs: Any = None,
    process_solution: Callable[[list, int, Any], Any] = None,
    make_move: Callable[[list, int, Any], Any] = None,
    unmake_move: Callable[[list, int, Any], Any] = None
) -> Iterator[Tuple[list, int]]:
    """backtracking DFS style"""
    if process_solution is None:
        process_solution = lambda a, k, inputs: None
    if make_move is None:
        make_move = lambda a, k, inputs: None
    if unmake_move is None:
        unmake_move = lambda a, k, inputs: None

    stack = Stack(implementation='linked_list')

    class StackItem:
        def __init__(self, k: int, candidates: Iterator = None):
            self.k = k
            self.candidates = candidates

    stack.push(StackItem(k=0))

    while True:
        try:
            stack_item = stack.pop()
            try:
                if stack_item.candidates is None:
                    stack_item.candidates = construct_candidates(
                        a=a, k=stack_item.k, inputs=inputs)
                    candidate = next(stack_item.candidates)
                else:
                    unmake_move(a, stack_item.k, inputs)
                    candidate = next(stack_item.candidates)
            except StopIteration:
                pass
            else:
                a[stack_item.k] = candidate
                make_move(a, stack_item.k, inputs)
                if is_a_solution(a, stack_item.k, inputs):
                    process_solution(a, stack_item.k, inputs)
                    yield a[:stack_item.k + 1]
                    stack.push(stack_item)
                else:
                    stack.push(stack_item)
                    stack.push(StackItem(k=stack_item.k + 1))
        except StackEmptyError:
            break
Esempio n. 18
0
def quicksort(items: Sequence[KeyedItem], order=None) -> None:
    """O(n*log n) expected - O(n**2) worst case"""
    comp = lambda x, y: x > y if order == 'max' else x < y
    stack = Stack(implementation='linked_list')

    class StackItem:
        def __init__(self, low, high, status=0):
            self.low = low
            self.high = high
            self.status = status

    stack_item = StackItem(0, len(items) - 1)

    while True:
        low = stack_item.low
        high = stack_item.high
        if low < high:
            if not stack_item.status:
                # partition according to pivot with linear scan
                # pick pivot
                pivot_index = random.randint(low, high)
                pivot = items[pivot_index]
                items[high], items[pivot_index] = items[pivot_index], items[
                    high]
                # all items between low+1 and i are partionned against the pivot
                # all items below pivot index are comp(item.key, pivot.key)
                pivot_index = low
                for i in range(low, high):
                    item = items[i]
                    if comp(item.key, pivot.key):
                        items[i], items[pivot_index] = items[
                            pivot_index], items[i]
                        pivot_index += 1
                # swap pivot to its rightful position
                items[high], items[pivot_index] = items[pivot_index], items[
                    high]

                # sort left of pivot
                stack_item.status = 1
                stack.push(stack_item)
                stack.push(StackItem(low, pivot_index - 1))
            elif stack_item.status == 1:
                # sort right of pivot
                stack_item.status = 2
                stack.push(stack_item)
                stack.push(StackItem(pivot_index + 1, high))
        try:
            stack_item = stack.pop()
        except StackEmptyError:
            break
def mergesort(items: Sequence[KeyedItem], order=None) -> None:
    """O(n*log n)"""
    comp = lambda x, y: x > y if order == 'max' else x < y
    stack = Stack(implementation='linked_list')

    class StackItem:
        def __init__(self, low, high, status=0):
            self.low = low
            self.high = high
            self.status = status

    stack_item = StackItem(0, len(items) - 1)

    while True:
        low = stack_item.low
        high = stack_item.high
        if low != high:
            median = (low + high) // 2
            if not stack_item.status:
                # sort left
                stack_item.status = 1
                stack.push(stack_item)
                stack.push(StackItem(low, median))
            elif stack_item.status == 1:
                # sort right
                stack_item.status = 2
                stack.push(stack_item)
                stack.push(StackItem(median + 1, high))
            else:
                # merge
                left_queue = Queue(implementation='doubly_linked_list')
                for i in range(low, median+1):
                    left_queue.enqueue(items[i])
                right_queue = Queue(implementation='doubly_linked_list')
                for i in range(median+1, high+1):
                    right_queue.enqueue(items[i])
                for i in range(low, high + 1):
                    if not left_queue.head:
                        items[i] = right_queue.dequeue()
                    elif not right_queue.head:
                        items[i] = left_queue.dequeue()
                    else:
                        if comp(left_queue.head.key, right_queue.head.key):
                            items[i] = left_queue.dequeue()
                        else:
                            items[i] = right_queue.dequeue()
        try:
            stack_item = stack.pop()
        except StackEmptyError:
            break
def sort_stack(stack):
    temp_stack = Stack()

    threshold = stack.pop()
    while not stack.is_empty():
        if stack.peek() >= threshold:
            stack_node = stack.pop()
            temp_stack.push(threshold)
            threshold = stack_node

        elif stack.peek() < threshold:
            temp_stack.push(threshold)
            threshold = stack.pop()
            while not temp_stack.is_empty() and threshold < temp_stack.peek():
                stack.push(temp_stack.pop())
            temp_stack.push(threshold)
            if not stack.is_empty():
                threshold = stack.pop()

    temp_stack.push(threshold)
    while not temp_stack.is_empty():
        stack.push(temp_stack.pop())

    return stack
Esempio n. 21
0
def topological_sort(graph: Graph) -> List[Vertex]:
    """DFS: returns the list of topologically-ordered vertices in a
    Directed Acyclic Graph (DAG)"""
    if not graph.directed:
        raise GraphDirectionTypeError

    vertices = [v for v in graph.adjacency_lists]

    stack = Stack(implementation='linked_list')
    discovered_vertices = NodeList(vertices, default=False)
    processed_vertices = NodeList(vertices, default=False)
    sorted_vertices = []

    def process_vertex_early(vertex: Vertex):
        nonlocal discovered_vertices
        discovered_vertices[vertex] = True

    def process_vertex_late(vertex: Vertex):
        nonlocal stack, processed_vertices
        processed_vertices[vertex] = True
        stack.push(vertex)

    def process_edge(head: Vertex, edgenode: Edgenode):
        # check that the edge is not a back edge
        if edgenode.edgetype is EdgeType.BACK:
            raise CycleInGraphError

    for v in vertices:
        if not discovered_vertices[v]:
            graph.dfs(start=v,
                      process_vertex_early=process_vertex_early,
                      process_vertex_late=process_vertex_late,
                      process_edge=process_edge,
                      discovered_vertices=discovered_vertices,
                      processed_vertices=processed_vertices)

    while True:
        try:
            sorted_vertices.append(stack.pop())
        except StackEmptyError:
            break

    return sorted_vertices
Esempio n. 22
0
 def calculate_path_volume(graph: Graph,
                           start: Vertex,
                           end: Vertex) -> float:
     stack = Stack(implementation='linked_list')
     parent_edges = graph.parent_edges
     while True:
         edge = parent_edges[end]
         if edge:
             stack.push(edge)
             end = edge.head
             if end is start:
                 break
         else:
             return 0
     volume = inf
     while True:
         try:
             volume = min(volume, stack.pop().residual)
         except StackEmptyError:
             break
     return volume
Esempio n. 23
0
def revstring(mystr):
    '''  Reverse the characters in a string using a stack
    
    Args:
        mystr (string): string to reverse
    
    Returns:
        string: reversed string
    
    '''
    
    s = Stack()
    revmystr = ""
    
    for ch in mystr:
        s.push(ch)
    
    while not s.isEmpty():
        revmystr += s.pop()
    
    return revmystr
def df_paths(graph: Dict[int, Dict[str, int]],
             starting_room: int) -> Dict[int, List[str]]:
    ss = Stack()
    visited = set()
    ss.push([starting_room])

    paths = {(starting_room, starting_room): [starting_room]}

    while ss.size > 0:
        path: List[int] = ss.pop()
        room = path[-1]
        if room not in visited:
            visited.add(room)
            for move, nextroom in graph[room].items():
                path_copy = path.copy()
                path_copy.append(nextroom)
                ss.push(path_copy)

                paths[(starting_room,
                       nextroom)] = paths[(starting_room, room)] + [move]

    paths = {key: val[1:] for key, val in paths.items()}
    return paths
Esempio n. 25
0
 def augment_path(graph: Graph,
                  start: Vertex,
                  end: Vertex,
                  volume: float):
     stack = Stack(implementation='linked_list')
     parent_edges = graph.parent_edges
     while True:
         edge = parent_edges[end]
         if edge:
             stack.push(edge)
             end = edge.head
             if end is start:
                 break
         else:
             break
     while True:
         try:
             edge = stack.pop()
         except StackEmptyError:
             break
         else:
             edge.edgenode.flow += volume
             edge.edgenode.residual -= volume
             edge.edgenode.opposite.residual += volume
                stack.push(temp_stack.pop())
            temp_stack.push(threshold)
            if not stack.is_empty():
                threshold = stack.pop()

    temp_stack.push(threshold)
    while not temp_stack.is_empty():
        stack.push(temp_stack.pop())

    return stack


if __name__ == '__main__':  # tests

    stack = Stack()
    original_values = [8, 3, 5, 9, 7, 2]
    for v in original_values:
        stack.push(v)

    sorted_stack = Stack()
    sorted_values = sorted(original_values, reverse=True)
    for v in sorted_values:
        sorted_stack.push(v)

    expected_sorted_stack = sort_stack(stack)
    while not expected_sorted_stack.is_empty() and not sorted_stack.is_empty():
        assert expected_sorted_stack.pop() == sorted_stack.pop()



def test_array_implementation_empty_stack():
    stack = Stack(implementation='array')
    with pytest.raises(StackEmptyError):
        stack.pop()
#print(build_up)
#print(traversal)
visited_rooms = set()
player.currentRoom = world.startingRoom
visited_rooms.add(player.currentRoom)
traversalPath = list()
ss = Stack()
qq = Queue()

while len(visited_rooms) != len(roomGraph.keys()):
    move = random.choice(player.currentRoom.getExits())

    if room_graph[player.currentRoom.id][move] not in visited_rooms:
        ss.push(move)
        while ss.size > 0:
            dir_move: str = ss.pop()

            if dir_move in room_graph[player.currentRoom.id].keys():

                if room_graph[player.currentRoom.id][dir_move] not in visited_rooms:
                    print(dir_move)
                    player.travel(dir_move)
                    traversalPath.append(dir_move)
                    visited_rooms.add(player.currentRoom.id)
                    for mv in player.currentRoom.getExits():

                        if mv in room_graph[player.currentRoom.id].keys():
                            print("     ", mv)
                            if room_graph[player.currentRoom.id][mv] not in visited_rooms:
                                ss.push(mv)
                                continue
Esempio n. 29
0
import sys
sys.path.append('./datastructures')
from datastructures import Stack

if __name__ == '__main__':  # tests

    stack = Stack()

    stack.push(1)
    assert stack.min() == 1

    stack.push(2)
    assert stack.min() == 1

    stack.push(3)
    assert stack.min() == 1

    stack.push(0)
    assert stack.min() == 0

    stack.pop()
    assert stack.min() == 1

def test_linked_list_implementation_empty_stack():
    stack = Stack(implementation='linked_list')
    with pytest.raises(StackEmptyError):
        stack.pop()
Esempio n. 31
0
    def dfs(self,
            start: Vertex,
            process_vertex_early: Callable[[Vertex], Any] = None,
            process_vertex_late: Callable[[Vertex], Any] = None,
            process_edge: Callable[[Vertex, Edgenode], Any] = None,
            discovered_vertices: NodeList = None,
            processed_vertices: NodeList = None):
        """Depth-First Search
        returns entry and exit times for each vertex
        - a vertex v1 is an ancestor of vertex v2 if the time interval of v2 is
        nested in the one of v1
        - the nb of descendants of a vertex v1 is half its time interval"""
        process_vertex_early = process_vertex_early if process_vertex_early else lambda v: None
        process_vertex_late = process_vertex_late if process_vertex_late else lambda v: None
        process_edge = process_edge if process_edge else lambda v, e: None

        vertices = [v for v in self.adjacency_lists]
        self.parent_edges = NodeList(vertices=vertices)  # reinit parents
        discovered = NodeList(
            vertices=vertices
        ) if discovered_vertices is None else discovered_vertices  # added to stack
        processed = NodeList(
            vertices=vertices
        ) if processed_vertices is None else processed_vertices  # processed and out of stack
        discovered[start] = True

        class StackItem:
            def __init__(self, vertex: Vertex, status=0, iter_edgenodes=None):
                self.vertex = vertex
                self.status = status
                self.iter_edgenodes = iter_edgenodes

        stack = Stack(implementation='linked_list')
        stack.push(StackItem(vertex=start))

        counter = 0
        while True:
            try:
                stack_item = stack.pop()
            except StackEmptyError:
                break
            if not stack_item.status:
                stack_item.vertex.entry_time = counter
                counter += 1
                stack_item.iter_edgenodes = iter(
                    self.adjacency_lists[stack_item.vertex].edgenodes)
                process_vertex_early(stack_item.vertex)
                stack_item.status = 1
            if stack_item.status == 1:
                while True:
                    try:
                        edgenode = next(stack_item.iter_edgenodes)
                        next_vertex = edgenode.tail
                    except StopIteration:
                        stack_item.status = 2
                        stack.push(stack_item)
                        break
                    # if undiscovered vertex: add it to stack
                    if not discovered[next_vertex]:
                        discovered[next_vertex] = True
                        stack.push(stack_item)
                        # tree edge
                        edgenode.edgetype = EdgeType.TREE
                        # the parent of the next_vertex is the head of the edge,
                        # i.e., stack_item.vertex
                        self.parent_edges[next_vertex] = edgenode.to_edge(
                            head=stack_item.vertex)
                        process_edge(stack_item.vertex, edgenode)
                        stack.push(StackItem(vertex=next_vertex))
                        break
                    # if discovered vertex, then it has been put in stack before
                    # if it is still unprocessed, then it is still in the stack, so no need to add it
                    # if it has been processed but the graph is directed, the edge has not been processed yet
                    # in both case, the edge needs to be processed
                    # but it is a "back edge", the parent of the next_vertex is already known
                    elif not processed[next_vertex] or self.directed:
                        # discovered but not processed
                        if not processed[next_vertex]:
                            edgenode.edgetype = EdgeType.BACK
                        # discovered, processed but earlier entry time
                        elif stack_item.vertex.entry_time < next_vertex.entry_time:
                            edgenode.edgetype = EdgeType.FORWARD
                        # discovered, processed but later entry time
                        else:
                            edgenode.edgetype = EdgeType.CROSS
                        process_edge(stack_item.vertex, edgenode)
            elif stack_item.status == 2:
                process_vertex_late(stack_item.vertex)
                processed[stack_item.vertex] = True
                stack_item.vertex.exit_time = counter
                counter += 1
Esempio n. 32
0
class DiceResolver:
    def __init__(self):

        # BEDMAS ==> d()!^/%*+-
        # PEMDAS ==> d()!^*/%+-
        # Precedence weighting reflects rule; higher means priority
        # Close bracket ')' not included; it is a special case (collapses stack to '(')
        self.precedence = {
            "(": 0,
            "+": 3,
            "-": 3,
            "/": 5,
            "*": 5,
            "%": 5,
            "C": 5,
            "c": 5,
            "^": 7,
            "!": 8,
            "d": 9
        }

        # 's' is a stack used for rearranging infix arguments
        self.s = Stack()

        # 'q' is a queue used to store the postfix arguments
        self.q = Queue()
        self.error = False

    # Converts a valid infix expression (mathematical expression)
    # to postfix using Reverse Polish Notation (RPN). Infix exp-
    # ression must be valid; this function can not check validi-
    # ty.  Note that by design, this only supports integer expr-
    # ession (no floating point support). FP support can be add-
    # ed if while building numbers, the '.' character is accepted.

    # Example: Expression="1 + 2 * 3"  --> 7, NOT 9
    # RPN="1 2 3 * +"  --> 7
    # Note that the order of operations is preserved in the RPN.
    def infixToRPN(self, expression):

        # Since a number may be multiple characters, we start with an empty string,
        # and while each character is numeric, we append the number until a non-
        # numeric value is encountered.
        num = ""

        # Tokenize expression character by character
        for c in expression:
            token = str(c)

            # Case: we had accumulated a number but this character is not a
            # numeric value; so save accumulated number, and reset accumulator.
            if (num != "" and not token.isnumeric()):
                self.q.enqueue(num)
                num = ""

            # We aren't a number; so handle the token
            # '(' start brackets are simply markers of what point to return to when
            # a ')' close bracket is encountered.
            if (token == "("):
                self.s.push(token)

            # Special case; we look for this first -> it means we have to pop all
            # previous values off stack into the RPN queue until we find the '('
            elif (token == ")"):
                # pop up until the bracket
                while (self.s.peek() != "("):
                    self.q.enqueue(self.s.pop())

                # pop the bracket / throw it away (it was just a marker, we're done with it)
                self.s.pop()

            # Casee: operator handling
            # we are done handling brackets, check for a valid operator
            elif (token in self.precedence):
                while self.s.size() != 0 and (self.precedence[token] <=
                                              self.precedence[self.s.peek()]):
                    self.q.enqueue(self.s.pop())
                self.s.push(token)

            # Case: character is numeric.
        # Append to accumulator and continue parsing
            elif (token.isnumeric()):
                num += token

        # Did token end on a number? If so store accumulated number in RPN queue
        if (num != ""):
            self.q.enqueue(num)

        # Now pop items from stack to the queue to cleanup
        while (self.s.size() != 0):
            self.q.enqueue(self.s.pop())

        # At this point, we have a valid RPN in the 'q' queue
        # (if the infix expression was valid)
        # Let's return a string version:
        q_cp = self.q.copy()

        rpn = ""
        for c in q_cp:
            rpn += c + " "
        return (rpn)

    # Routine to calculate a factorial
    def factorial(self, value):
        if (value < 0):
            return (0)
        elif (value == 0 or value == 1):
            return (1)
        elif (value == 2):
            return (2)

        product = value

        for x in range(2, value):
            product = product * x
        return (product)

    # Routine to calculate "choose" (combinatorics)
    # Formula:
    # nCr (n Choose r) = n! / r!(n-r)!
    def choose(self, n, r):
        numerator = self.factorial(n)
        denominator = self.factorial(r) * self.factorial(n - r)

        # Sanity
        if (denominator == 0):
            return (0)

        # Compute
        # NOTE: Should always be an integer result, but cast
        # it anyways to be safe
        return (int(numerator / denominator))

    # Given left value, right value, and an operator, calculate.
    def calculate(self, left, right, op):
        if (op == "+"):
            return (left + right)

        elif (op == "-"):
            return (left - right)

        elif (op == "*"):
            return (left * right)

        elif (op == "/"):
            return (int(left / right))

        elif (op == "^"):
            return (left**right)

        elif (op == "%"):
            return (left % right)

        elif (op == "!"):
            return (self.factorial(left))

        elif (op == "c" or op == "C"):
            return (self.choose(left, right))

        # dice roll; handled with 'random'
        # NOTE: expressions without 'd' are deterministic;
        # expressions with 'd' are non-deterministic (variable
        # outcomes).
        elif (op == "d"):
            sum = 0

            # Left value is number of rolls; right value is die
            # IE 3d6 = 3 rolls of a 6 sided die, summed.
            for i in range(left):
                sum += random.randint(1, right)
            return (sum)

        # whoops shouldn't have happened try to be graceful
        return (0)

    # Nifty little stack and queue algorithm for evaluating
    # the RPN.  Expects a valid RPN expression.
    def evaluateRPN(self):
        workstack = Stack()

        # As we pull tokens from the queue, we validate them and if neither a number
        # nor an operator, we abort with an error.
        for t in self.q.copy():
            if (t in self.precedence):
                # As we work backwards, right value is first; validate
                right = workstack.pop()
                if (not str(right).isnumeric()
                        and not right in self.precedence):
                    self.error = True
                    break

                # Now get left value, validate
                # Special case: ! only takes one argument. Make them identical
                if (t == "!"):
                    left = right
                else:
                    left = workstack.pop()
                    if (not str(left).isnumeric()
                            and not left in self.precedence):
                        self.error = True
                        break

                # Both valid, so calculate
                workstack.push(self.calculate(left, right, t))
            else:
                workstack.push(int(t))

        # answer is now on the stack
        if (not self.error):
            return (workstack.pop())
        else:
            return (0)

    # One function to handle it all. How Pythonic.
    def resolve(self, expression, repeat=False):
        if not repeat:
            self.error = False
            self.q.clear()
            self.s.clear()
            self.infixToRPN(expression)
            return (self.evaluateRPN())
        else:
            # Repeat=True
            # This allows repeat dice rolls / calculations, without rebuilding
            # the RPN queue each time.
            return (self.evaluateRPN())

    # Heuristic to calculate expression distribution, ment to be
    # used with dice rolls (ie, 2d6).  This is done by repeating rolls
    # to a cap of n trials, then assessing the results.
    # Returns a histogram report with trial results, mean and mode.
    def getHistogram(self, expression, trials):
        # Validate min/max boundaries
        if (trials < 0):
            trials = 1
        elif (trials > 1000000):
            trials = 1000000

        # Initialize
        sb = ""
        rolls = dict()
        sum = 0
        pct = 1.0
        max = dict()
        result = dict()

        # Build
        for i in range(trials):
            if i == 0:
                roll = self.resolve(expression)
            else:
                # We already built the RPN, don't waste cycles
                # reuilding it on every iteration, set repeat=True.
                roll = self.resolve(expression, repeat=True)

            # Track the recurrences of roll values
            if (roll in rolls.keys()):
                rolls[roll] += 1
            else:
                rolls[roll] = 1

        # Nifty way to build a key sorted report
        keys = list(rolls.keys())
        keys.sort()

        # Report
        sb = f"DISTRIBUTION HISTORGRAM ({trials:,} trials):\n"
        max[0] = max[1] = 0
        for key in keys:
            result[key] = rolls[key]
            sum += key * rolls[key]
            pct = float(rolls[key]) / float(trials, ) * 100.0
            if (pct > max[0]):
                max[0] = pct
                max[1] = key

            sb += f"[{key:3}] ==> {rolls[key]:,} ({pct:.2f}%)\n"

            # Stash pct for later
            rolls[key] = pct

        mean = float(sum) / float(trials) + 0.5
        sb += f"Mean: {mean:.2f}\n"
        mode = max[1]
        sb += f"Mode: {mode}\n\n"

        # Scaling calculation uses a lambda function
        scale = lambda x, y: int(float(x / 100) * float(y) + 0.5)

        # Build histogram pictogragh
        pic = "PICTORIAL HISTOGRAM:\n"
        for key in keys:
            pic += f"[{key:3}] "
            for i in range(scale(rolls[key], 160)):
                pic += "*"
            pic += "\n"

        # Send back the report
        return (sb + pic)
def test_primitives():
    stack = Stack()
    with pytest.raises(NotImplementedError):
        stack.push('a')
    with pytest.raises(NotImplementedError):
        stack.pop()