prometheus800

ВИ Лаб 1: Snake (Uninformed)

Apr 28th, 2021 (edited)
273
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 24.64 KB | None | 0 0
  1. import bisect
  2. import time
  3.  
  4. """
  5. Дефинирање на класа за структурата на проблемот кој ќе го решаваме со пребарување.
  6. Класата Problem е апстрактна класа од која правиме наследување за дефинирање на основните
  7. карактеристики на секој проблем што сакаме да го решиме
  8. """
  9.  
  10.  
  11. class Problem:
  12.     def __init__(self, initial, goal=None):
  13.         self.initial = initial
  14.         self.goal = goal
  15.  
  16.     def successor(self, state):
  17.         """За дадена состојба, врати речник од парови {акција : состојба}
  18.        достапни од оваа состојба. Ако има многу следбеници, употребете
  19.        итератор кој би ги генерирал следбениците еден по еден, наместо да
  20.        ги генерирате сите одеднаш.
  21.        :param state: дадена состојба
  22.        :return:  речник од парови {акција : состојба} достапни од оваа
  23.                  состојба
  24.        :rtype: dict
  25.        """
  26.         raise NotImplementedError
  27.  
  28.     def actions(self, state):
  29.         """За дадена состојба state, врати листа од сите акции што може да
  30.        се применат над таа состојба
  31.        :param state: дадена состојба
  32.        :return: листа на акции
  33.        :rtype: list
  34.        """
  35.         raise NotImplementedError
  36.  
  37.     def result(self, state, action):
  38.         """За дадена состојба state и акција action, врати ја состојбата
  39.        што се добива со примена на акцијата над состојбата
  40.        :param state: дадена состојба
  41.        :param action: дадена акција
  42.        :return: резултантна состојба
  43.        """
  44.         raise NotImplementedError
  45.  
  46.     def goal_test(self, state):
  47.         """Врати True ако state е целна состојба. Даденава имплементација
  48.        на методот директно ја споредува state со self.goal, како што е
  49.        специфицирана во конструкторот. Имплементирајте го овој метод ако
  50.        проверката со една целна состојба self.goal не е доволна.
  51.        :param state: дадена состојба
  52.        :return: дали дадената состојба е целна состојба
  53.        :rtype: bool
  54.        """
  55.         return state == self.goal
  56.  
  57.     def path_cost(self, c, state1, action, state2):
  58.         """Врати ја цената на решавачкиот пат кој пристигнува во состојбата
  59.        state2 од состојбата state1 преку акцијата action, претпоставувајќи
  60.        дека цената на патот до состојбата state1 е c. Ако проблемот е таков
  61.        што патот не е важен, оваа функција ќе ја разгледува само состојбата
  62.        state2. Ако патот е важен, ќе ја разгледува цената c и можеби и
  63.        state1 и action. Даденава имплементација му доделува цена 1 на секој
  64.        чекор од патот.
  65.        :param c: цена на патот до состојбата state1
  66.        :param state1: дадена моментална состојба
  67.        :param action: акција која треба да се изврши
  68.        :param state2: состојба во која треба да се стигне
  69.        :return: цена на патот по извршување на акцијата
  70.        :rtype: float
  71.        """
  72.         return c + 1
  73.  
  74.     def value(self):
  75.         """За проблеми на оптимизација, секоја состојба си има вредност.
  76.        Hill-climbing и сличните алгоритми се обидуваат да ја максимизираат
  77.        оваа вредност.
  78.        :return: вредност на состојба
  79.        :rtype: float
  80.        """
  81.         raise NotImplementedError
  82.  
  83.  
  84. """
  85. Дефинирање на класата за структурата на јазел од пребарување.
  86. Класата Node не се наследува
  87. """
  88.  
  89.  
  90. class Node:
  91.     def __init__(self, state, parent=None, action=None, path_cost=0):
  92.         """Креирај јазол од пребарувачкото дрво, добиен од parent со примена
  93.        на акцијата action
  94.        :param state: моментална состојба (current state)
  95.        :param parent: родителска состојба (parent state)
  96.        :param action: акција (action)
  97.        :param path_cost: цена на патот (path cost)
  98.        """
  99.         self.state = state
  100.         self.parent = parent
  101.         self.action = action
  102.         self.path_cost = path_cost
  103.         self.depth = 0  # search depth
  104.         if parent:
  105.             self.depth = parent.depth + 1
  106.  
  107.     def __repr__(self):
  108.         return "<Node %s>" % (self.state,)
  109.  
  110.     def __lt__(self, node):
  111.         return self.state < node.state
  112.  
  113.     def expand(self, problem):
  114.         """Излистај ги јазлите достапни во еден чекор од овој јазол.
  115.        :param problem: даден проблем
  116.        :return: листа на достапни јазли во еден чекор
  117.        :rtype: list(Node)
  118.        """
  119.  
  120.         return [self.child_node(problem, action)
  121.                 for action in problem.actions(self.state)]
  122.  
  123.     def child_node(self, problem, action):
  124.         """Дете јазел
  125.        :param problem: даден проблем
  126.        :param action: дадена акција
  127.        :return: достапен јазел според дадената акција
  128.        :rtype: Node
  129.        """
  130.         next_state = problem.result(self.state, action)
  131.         return Node(next_state, self, action,
  132.                     problem.path_cost(self.path_cost, self.state,
  133.                                       action, next_state))
  134.  
  135.     def solution(self):
  136.         """Врати ја секвенцата од акции за да се стигне од коренот до овој јазол.
  137.        :return: секвенцата од акции
  138.        :rtype: list
  139.        """
  140.         return [node.action for node in self.path()[1:]]
  141.  
  142.     def solve(self):
  143.         """Врати ја секвенцата од состојби за да се стигне од коренот до овој јазол.
  144.        :return: листа од состојби
  145.        :rtype: list
  146.        """
  147.         return [node.state for node in self.path()[0:]]
  148.  
  149.     def path(self):
  150.         """Врати ја листата од јазли што го формираат патот од коренот до овој јазол.
  151.        :return: листа од јазли од патот
  152.        :rtype: list(Node)
  153.        """
  154.         x, result = self, []
  155.         while x:
  156.             result.append(x)
  157.             x = x.parent
  158.         result.reverse()
  159.         return result
  160.  
  161.     """Сакаме редицата од јазли кај breadth_first_search или
  162.    astar_search да не содржи состојби - дупликати, па јазлите што
  163.    содржат иста состојба ги третираме како исти. [Проблем: ова може
  164.    да не биде пожелно во други ситуации.]"""
  165.  
  166.     def __eq__(self, other):
  167.         return isinstance(other, Node) and self.state == other.state
  168.  
  169.     def __hash__(self):
  170.         return hash(self.state)
  171.  
  172.  
  173. """
  174. Дефинирање на помошни структури за чување на листата на генерирани, но непроверени јазли
  175. """
  176.  
  177.  
  178. class Queue:
  179.     """Queue е апстрактна класа / интерфејс. Постојат 3 типа:
  180.        Stack(): Last In First Out Queue (стек).
  181.        FIFOQueue(): First In First Out Queue (редица).
  182.        PriorityQueue(order, f): Queue во сортиран редослед (подразбирливо,од најмалиот кон
  183.                                 најголемиот јазол).
  184.    """
  185.  
  186.     def __init__(self):
  187.         raise NotImplementedError
  188.  
  189.     def append(self, item):
  190.         """Додади го елементот item во редицата
  191.        :param item: даден елемент
  192.        :return: None
  193.        """
  194.         raise NotImplementedError
  195.  
  196.     def extend(self, items):
  197.         """Додади ги елементите items во редицата
  198.        :param items: дадени елементи
  199.        :return: None
  200.        """
  201.         raise NotImplementedError
  202.  
  203.     def pop(self):
  204.         """Врати го првиот елемент од редицата
  205.        :return: прв елемент
  206.        """
  207.         raise NotImplementedError
  208.  
  209.     def __len__(self):
  210.         """Врати го бројот на елементи во редицата
  211.        :return: број на елементи во редицата
  212.        :rtype: int
  213.        """
  214.         raise NotImplementedError
  215.  
  216.     def __contains__(self, item):
  217.         """Проверка дали редицата го содржи елементот item
  218.        :param item: даден елемент
  219.        :return: дали queue го содржи item
  220.        :rtype: bool
  221.        """
  222.         raise NotImplementedError
  223.  
  224.  
  225. class Stack(Queue):
  226.     """Last-In-First-Out Queue."""
  227.  
  228.     def __init__(self):
  229.         self.data = []
  230.  
  231.     def append(self, item):
  232.         self.data.append(item)
  233.  
  234.     def extend(self, items):
  235.         self.data.extend(items)
  236.  
  237.     def pop(self):
  238.         return self.data.pop()
  239.  
  240.     def __len__(self):
  241.         return len(self.data)
  242.  
  243.     def __contains__(self, item):
  244.         return item in self.data
  245.  
  246.  
  247. class FIFOQueue(Queue):
  248.     """First-In-First-Out Queue."""
  249.  
  250.     def __init__(self):
  251.         self.data = []
  252.  
  253.     def append(self, item):
  254.         self.data.append(item)
  255.  
  256.     def extend(self, items):
  257.         self.data.extend(items)
  258.  
  259.     def pop(self):
  260.         return self.data.pop(0)
  261.  
  262.     def __len__(self):
  263.         return len(self.data)
  264.  
  265.     def __contains__(self, item):
  266.         return item in self.data
  267.  
  268.  
  269. class PriorityQueue(Queue):
  270.     """Редица во која прво се враќа минималниот (или максималниот) елемент
  271.    (како што е определено со f и order). Оваа структура се користи кај
  272.    информирано пребарување"""
  273.     """"""
  274.  
  275.     def __init__(self, order=min, f=lambda x: x):
  276.         """
  277.        :param order: функција за подредување, ако order е min, се враќа елементот
  278.                      со минимална f(x); ако order е max, тогаш се враќа елементот
  279.                      со максимална f(x).
  280.        :param f: функција f(x)
  281.        """
  282.         assert order in [min, max]
  283.         self.data = []
  284.         self.order = order
  285.         self.f = f
  286.  
  287.     def append(self, item):
  288.         bisect.insort_right(self.data, (self.f(item), item))
  289.  
  290.     def extend(self, items):
  291.         for item in items:
  292.             bisect.insort_right(self.data, (self.f(item), item))
  293.  
  294.     def pop(self):
  295.         if self.order == min:
  296.             return self.data.pop(0)[1]
  297.         return self.data.pop()[1]
  298.  
  299.     def __len__(self):
  300.         return len(self.data)
  301.  
  302.     def __contains__(self, item):
  303.         return any(item == pair[1] for pair in self.data)
  304.  
  305.     def __getitem__(self, key):
  306.         for _, item in self.data:
  307.             if item == key:
  308.                 return item
  309.  
  310.     def __delitem__(self, key):
  311.         for i, (value, item) in enumerate(self.data):
  312.             if item == key:
  313.                 self.data.pop(i)
  314.  
  315.  
  316. import sys
  317.  
  318. """
  319. Неинформирано пребарување во рамки на дрво.
  320. Во рамки на дрвото не разрешуваме јамки.
  321. """
  322.  
  323.  
  324. def tree_search(problem, fringe):
  325.     """ Пребарувај низ следбениците на даден проблем за да најдеш цел.
  326.    :param problem: даден проблем
  327.    :type problem: Problem
  328.    :param fringe:  празна редица (queue)
  329.    :type fringe: FIFOQueue or Stack or PriorityQueue
  330.    :return: Node or None
  331.    :rtype: Node
  332.    """
  333.     fringe.append(Node(problem.initial))
  334.     while fringe:
  335.         node = fringe.pop()
  336.         print(node.state)
  337.         if problem.goal_test(node.state):
  338.             return node
  339.         fringe.extend(node.expand(problem))
  340.     return None
  341.  
  342.  
  343. def breadth_first_tree_search(problem):
  344.     """Експандирај го прво најплиткиот јазол во пребарувачкото дрво.
  345.    :param problem: даден проблем
  346.    :type problem: Problem
  347.    :return: Node or None
  348.    :rtype: Node
  349.    """
  350.     return tree_search(problem, FIFOQueue())
  351.  
  352.  
  353. def depth_first_tree_search(problem):
  354.     """Експандирај го прво најдлабокиот јазол во пребарувачкото дрво.
  355.    :param problem: даден проблем
  356.    :type problem: Problem
  357.    :return: Node or None
  358.    :rtype: Node
  359.    """
  360.     return tree_search(problem, Stack())
  361.  
  362.  
  363. """
  364. Неинформирано пребарување во рамки на граф
  365. Основната разлика е во тоа што овде не дозволуваме јамки,
  366. т.е. повторување на состојби
  367. """
  368.  
  369.  
  370. def graph_search(problem, fringe):
  371.     """Пребарувај низ следбениците на даден проблем за да најдеш цел.
  372.     Ако до дадена состојба стигнат два пата, употреби го најдобриот пат.
  373.    :param problem: даден проблем
  374.    :type problem: Problem
  375.    :param fringe:  празна редица (queue)
  376.    :type fringe: FIFOQueue or Stack or PriorityQueue
  377.    :return: Node or None
  378.    :rtype: Node
  379.    """
  380.     closed = set()
  381.     fringe.append(Node(problem.initial))
  382.     while fringe:
  383.         node = fringe.pop()
  384.         if problem.goal_test(node.state):
  385.             return node
  386.         if node.state not in closed:
  387.             closed.add(node.state)
  388.             fringe.extend(node.expand(problem))
  389.     return None
  390.  
  391.  
  392. def breadth_first_graph_search(problem):
  393.     """Експандирај го прво најплиткиот јазол во пребарувачкиот граф.
  394.    :param problem: даден проблем
  395.    :type problem: Problem
  396.    :return: Node or None
  397.    :rtype: Node
  398.    """
  399.     return graph_search(problem, FIFOQueue())
  400.  
  401.  
  402. def depth_first_graph_search(problem):
  403.     """Експандирај го прво најдлабокиот јазол во пребарувачкиот граф.
  404.    :param problem: даден проблем
  405.    :type problem: Problem
  406.    :return: Node or None
  407.    :rtype: Node
  408.    """
  409.     return graph_search(problem, Stack())
  410.  
  411.  
  412. def depth_limited_search(problem, limit=50):
  413.     """Експандирај го прво најдлабокиот јазол во пребарувачкиот граф
  414.    со ограничена длабочина.
  415.    :param problem: даден проблем
  416.    :type problem: Problem
  417.    :param limit: лимит за длабочината
  418.    :type limit: int
  419.    :return: Node or None
  420.    :rtype: Node
  421.    """
  422.  
  423.     def recursive_dls(node, problem, limit):
  424.         """Помошна функција за depth limited"""
  425.         cutoff_occurred = False
  426.         if problem.goal_test(node.state):
  427.             return node
  428.         elif node.depth == limit:
  429.             return 'cutoff'
  430.         else:
  431.             for successor in node.expand(problem):
  432.                 result = recursive_dls(successor, problem, limit)
  433.                 if result == 'cutoff':
  434.                     cutoff_occurred = True
  435.                 elif result is not None:
  436.                     return result
  437.         if cutoff_occurred:
  438.             return 'cutoff'
  439.         return None
  440.  
  441.     return recursive_dls(Node(problem.initial), problem, limit)
  442.  
  443.  
  444. def iterative_deepening_search(problem):
  445.     """Експандирај го прво најдлабокиот јазол во пребарувачкиот граф
  446.    со ограничена длабочина, со итеративно зголемување на длабочината.
  447.    :param problem: даден проблем
  448.    :type problem: Problem
  449.    :return: Node or None
  450.    :rtype: Node
  451.    """
  452.     for depth in range(sys.maxsize):
  453.         result = depth_limited_search(problem, depth)
  454.         if result is not 'cutoff':
  455.             return result
  456.  
  457.  
  458. def uniform_cost_search(problem):
  459.     """Експандирај го прво јазолот со најниска цена во пребарувачкиот граф.
  460.    :param problem: даден проблем
  461.    :type problem: Problem
  462.    :return: Node or None
  463.    :rtype: Node
  464.    """
  465.     return graph_search(problem, PriorityQueue(min, lambda a: a.path_cost))
  466.  
  467.  
  468. def valid_pos(snake, snake_head, red_apples, max_x, max_y):
  469.     if 0 <= snake_head[0] < max_x and \
  470.             0 <= snake_head[1] < max_y and \
  471.             snake_head not in red_apples and \
  472.             snake_head not in snake:
  473.         return True
  474.     else:
  475.         return False
  476.  
  477.  
  478. def do_the_thing(green_apples, s_pos, s_head):
  479.     # Insert all the parts of the snake, except the head
  480.     new_snake = s_pos[1:]
  481.  
  482.     # Insert the new snake head position
  483.     new_snake.insert(0, (s_head[0], s_head[1]))
  484.  
  485.     # Move all parts except the head to their new position
  486.     for part in range(1, len(new_snake)):
  487.         new_snake[part] = (s_pos[part - 1])
  488.    
  489.     # If there is a green apple on the same position
  490.     # as the snake's head, extend the snake's tail
  491.     new_green_apples = green_apples[0:]
  492.     if new_snake[0] in new_green_apples:
  493.         last_tail_piece = s_pos[-1]
  494.         new_snake.insert(-1, last_tail_piece)
  495.         new_green_apples.remove(new_snake[0])
  496.  
  497.     return new_green_apples, new_snake
  498.  
  499.  
  500. class Snake(Problem):
  501.     def __init__(self, g_size, initial, goal=None):
  502.         super().__init__(initial, goal)
  503.         self.grid_size = g_size
  504.  
  505.     def successor(self, state):
  506.         successors = dict()
  507.  
  508.         # Snake direction = ['E', 'W', 'N', 'S']
  509.  
  510.         max_x = self.grid_size[0]
  511.         max_y = self.grid_size[1]
  512.  
  513.         s_pos = list(state[0])
  514.         snake_dir = state[1]
  515.         green_apples = list(state[2])
  516.         red_apples = state[3]
  517.  
  518.         if snake_dir == 'E':
  519.             # Forward move
  520.             if valid_pos(s_pos, (s_pos[0][0] + 1, s_pos[0][1]), red_apples, max_x, max_y):
  521.                 new_green_apples, new_snake = do_the_thing(green_apples, s_pos, (s_pos[0][0] + 1, s_pos[0][1]))
  522.                 successors['ProdolzhiPravo'] = (
  523.                     tuple(new_snake), 'E', tuple(new_green_apples), red_apples)
  524.  
  525.             # Turn right
  526.             if valid_pos(s_pos, (s_pos[0][0], s_pos[0][1] - 1), red_apples, max_x, max_y):
  527.                 new_green_apples, new_snake = do_the_thing(green_apples, s_pos, (s_pos[0][0], s_pos[0][1] - 1))
  528.                 successors['SvrtiDesno'] = (
  529.                     tuple(new_snake), 'S', tuple(new_green_apples), red_apples)
  530.  
  531.             # Turn left
  532.             if valid_pos(s_pos, (s_pos[0][0], s_pos[0][1] + 1), red_apples, max_x, max_y):
  533.                 new_green_apples, new_snake = do_the_thing(green_apples, s_pos, (s_pos[0][0], s_pos[0][1] + 1))
  534.  
  535.                 successors['SvrtiLevo'] = (
  536.                     tuple(new_snake), 'N', tuple(new_green_apples), red_apples)
  537.  
  538.         elif snake_dir == 'W':
  539.             # Forward move
  540.             if valid_pos(s_pos, (s_pos[0][0] - 1, s_pos[0][1]), red_apples, max_x, max_y):
  541.                 new_green_apples, new_snake = do_the_thing(green_apples, s_pos, (s_pos[0][0] - 1, s_pos[0][1]))
  542.                 successors['ProdolzhiPravo'] = (
  543.                     tuple(new_snake), 'W', tuple(new_green_apples), red_apples)
  544.  
  545.             # Turn right
  546.             if valid_pos(s_pos, (s_pos[0][0], s_pos[0][1] + 1), red_apples, max_x, max_y):
  547.                 new_green_apples, new_snake = do_the_thing(green_apples, s_pos, (s_pos[0][0], s_pos[0][1] + 1))
  548.                 successors['SvrtiDesno'] = (
  549.                     tuple(new_snake), 'N', tuple(new_green_apples), red_apples)
  550.  
  551.             # Turn left
  552.             if valid_pos(s_pos, (s_pos[0][0], s_pos[0][1] - 1), red_apples, max_x, max_y):
  553.                 new_green_apples, new_snake = do_the_thing(green_apples, s_pos, (s_pos[0][0], s_pos[0][1] - 1))
  554.                 successors['SvrtiLevo'] = (
  555.                     tuple(new_snake), 'S', tuple(new_green_apples), red_apples)
  556.  
  557.         elif snake_dir == 'N':
  558.             # Forward move
  559.             if valid_pos(s_pos, (s_pos[0][0], s_pos[0][1] + 1), red_apples, max_x, max_y):
  560.                 new_green_apples, new_snake = do_the_thing(green_apples, s_pos, (s_pos[0][0], s_pos[0][1] + 1))
  561.                 successors['ProdolzhiPravo'] = (
  562.                     tuple(new_snake), 'N', tuple(new_green_apples), red_apples)
  563.  
  564.             # Turn right
  565.             if valid_pos(s_pos, (s_pos[0][0] + 1, s_pos[0][1]), red_apples, max_x, max_y):
  566.                 new_green_apples, new_snake = do_the_thing(green_apples, s_pos, (s_pos[0][0] + 1, s_pos[0][1]))
  567.                 successors['SvrtiDesno'] = (
  568.                     tuple(new_snake), 'E', tuple(new_green_apples), red_apples)
  569.  
  570.             # Turn left
  571.             if valid_pos(s_pos, (s_pos[0][0] - 1, s_pos[0][1]), red_apples, max_x, max_y):
  572.                 new_green_apples, new_snake = do_the_thing(green_apples, s_pos, (s_pos[0][0] - 1, s_pos[0][1]))
  573.                 successors['SvrtiLevo'] = (
  574.                     tuple(new_snake), 'W', tuple(new_green_apples), red_apples)
  575.  
  576.         elif snake_dir == 'S':
  577.             # Forward move
  578.             if valid_pos(s_pos, (s_pos[0][0], s_pos[0][1] - 1), red_apples, max_x, max_y):
  579.                 new_green_apples, new_snake = do_the_thing(green_apples, s_pos, (s_pos[0][0], s_pos[0][1] - 1))
  580.                 successors['ProdolzhiPravo'] = (
  581.                     tuple(new_snake), 'S', tuple(new_green_apples), red_apples)
  582.  
  583.             # Turn right
  584.             if valid_pos(s_pos, (s_pos[0][0] - 1, s_pos[0][1]), red_apples, max_x, max_y):
  585.                 new_green_apples, new_snake = do_the_thing(green_apples, s_pos, (s_pos[0][0] - 1, s_pos[0][1]))
  586.                 successors['SvrtiDesno'] = (
  587.                     tuple(new_snake), 'W', tuple(new_green_apples), red_apples)
  588.  
  589.             # Turn left
  590.             if valid_pos(s_pos, (s_pos[0][0] + 1, s_pos[0][1]), red_apples, max_x, max_y):
  591.                 new_green_apples, new_snake = do_the_thing(green_apples, s_pos, (s_pos[0][0] + 1, s_pos[0][1]))
  592.                 successors['SvrtiLevo'] = (
  593.                     tuple(new_snake), 'E', tuple(new_green_apples), red_apples)
  594.  
  595.         return successors
  596.  
  597.     def actions(self, state):
  598.         return self.successor(state).keys()
  599.  
  600.     def result(self, state, action):
  601.         return self.successor(state)[action]
  602.  
  603.     def goal_test(self, state):
  604.         return len(state[2]) == 0
  605.  
  606.  
  607. if __name__ == '__main__':
  608.     grid_size = (10, 10)
  609.     green_apples_pos = []
  610.     red_apples_pos = []
  611.     snake_pos = [(0, 7), (0, 8), (0, 9)]
  612.     snake_direction = 'S'
  613.  
  614.     num_green_apples = int(input())
  615.     for i in range(num_green_apples):
  616.         x, y = input().split(',')
  617.         green_apples_pos.append((int(x), int(y)))
  618.  
  619.     num_red_apples = int(input())
  620.     for i in range(num_red_apples):
  621.         x, y = input().split(',')
  622.         red_apples_pos.append((int(x), int(y)))
  623.  
  624.     start_time = time.time()
  625.  
  626.     game = Snake(grid_size, (tuple(snake_pos), snake_direction, tuple(green_apples_pos), tuple(red_apples_pos)))
  627.  
  628.     answer = breadth_first_graph_search(game)
  629.  
  630.     print(answer.solution())
  631.  
  632.     print(f"Steps: {len(answer.solution())}")
  633.  
  634.     print(f"--- {time.time() - start_time:5f} seconds --- ")
  635.  
Add Comment
Please, Sign In to add comment