Esempio n. 1
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. 2
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. 3
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. 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()
Esempio n. 5
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. 6
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. 7
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. 8
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. 9
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
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()
Esempio n. 11
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. 12
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()
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. 15
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
Esempio n. 16
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
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
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
Esempio n. 19
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 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. 21
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
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
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
                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()



#       room, neighbs = rooms_neighbs.pop(0)
   
#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:
Esempio n. 26
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

Esempio n. 27
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. 28
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()