import pandas as pd
import numpy as np
import regex as re

# the usual import horror in python
# https://stackoverflow.com/questions/35166821/valueerror-attempted-relative-import-beyond-top-level-package
from ...config.config import Config

class ExperimentalPlan:
    '''
    Class for creating an experimental Plan based on DoE.

    planType [str]: type of doe plan (e.g. plackett-burrmann, lhs, ...)
    planType [str]: configuration params for the DoE plan (e.g.
                     number of factors, number of levels, ...). Depends
                     on pyDOE2 specs.
    rawPlan [np.array]: raw plan with abstract values (usually -1,0,
                        1 but depends on type)
    factorPlan [pd.DataFrame]: plan with real factor values
    nrTests [int]: number of tests runs of the plan
    '''

    def __init__(self, config: Config):
        self.factorFile = 'factors.csv'
        self.planType = config.planType

        self.rawPlan = np.array(0)
        self.factorPlan = pd.DataFrame()

        self.factorList = []
        self.nrTests = 0

        print('\t\tExperimental Plan created: plan_%s.csv'%config.planType )

    def setNrTests(self):
        self.nrTests = len(self.rawPlan)

    def setFactorList(self):
        self.factorList = list(pd.read_csv(self.factorFile, index_col='name').index)

    def convertPlanToRangeZeroOne(self):
        rawPlanRangeZeroOne = np.zeros((len(self.rawPlan[:, 0]), len(self.rawPlan[0, :])))

        # loop through columns of rawPlan
        for j in range(len(self.rawPlan[0, :])):
            factorCol = self.rawPlan[:, j]
            mini = min(factorCol)
            maxi = max(factorCol)

            # loop through rows of rawPlan
            for i in range(len(factorCol)):
                currentCell = float(self.rawPlan[i, j])
                rawPlanRangeZeroOne[i, j] = 0 + (1 - 0) * (currentCell - mini) / (maxi - mini)

        self.rawPlan = rawPlanRangeZeroOne

    def printFactorPlanToFile(self, pathToPlanFile):
        self.factorPlan.to_csv(pathToPlanFile)

    def printRawPlanToFile(self, pathToRawPlanFile):
        pd.DataFrame(self.rawPlan).to_csv(pathToRawPlanFile,
                                          header=self.factorList)

    def getFactorValuesOfTestRun(self, testNr):
        return dict(self.factorPlan.iloc[testNr])

    def checkFactorMatchingToRawPlan(self):
        # checking that numbers of factors in factors.csv matches the
        # configuration parameters from *.conf

        nrFactorsCSV = len(self.factorList)
        nrFactorsRawPlan = len(self.rawPlan[0, :])
        if nrFactorsCSV != nrFactorsRawPlan:
            raise ValueError(
                'The number of factors in factors.csv does not match to the plan created with config.conf.')

    def convertRawPlanToFactorPlan(self, pathToFactorFile):
        dfFactors = pd.read_csv(pathToFactorFile, index_col='name')

        self.factorPlan = pd.DataFrame(self.rawPlan.copy())
        self.factorPlan.columns = self.factorList

        # loop through all factors (columns of rawPlan)
        j = 0
        factorsWithExprList = []
        posOfFactorWithExpr = []
        for factor in self.factorList:
            factorCol = self.rawPlan[:, j].copy()
            factorMin = str(dfFactors.loc[factor].at['min'])
            factorMax = str(dfFactors.loc[factor].at['max'])

            # check if factor min is a number (float or int)
            if re.match('[\-|\+]?[0-9]+[\.]?[0-9]*', factorMin) is None \
                    or re.match('[\-|\+]?[0-9]+[\.]?[0-9]*', factorMax) is None:
                # if true factorMin/Max should be a math expression like 'a+b/2'
                # it is necessary to save these columns for later because they
                # depend on other factors values which need to be calculated first
                factorsWithExprList.append(factor)
                posOfFactorWithExpr.append(j)
                # these are dummy values that no error occurs
                factorMin = 0
                factorMax = 0

            factorMin = float(factorMin)
            factorMax = float(factorMax)

            factorCol *= factorMax - factorMin
            factorCol += factorMin

            # overwrite column of factorPlan
            self.factorPlan[factor] = factorCol

            j += 1

        # loop through the previous saved factor with expression in factorMin/Max
        factorRegex = '|'.join(self.factorList)
        j = 0
        for factorWithExpr in factorsWithExprList:
            factorCol = self.rawPlan[:, posOfFactorWithExpr[j]]

            factorMin = str(dfFactors.loc[factorWithExpr].at['min'])
            factorMax = str(dfFactors.loc[factorWithExpr].at['max'])

            if re.match('[\-|\+]?[0-9]+[\.]?[0-9]*', factorMin) is None:
                factorMin = self.__calcMinMaxForStrExpression(factorCol, factorRegex, factorMin)
            else:
                factorMin = float(factorMin)
            if re.match('[\-|\+]?[0-9]+[\.]?[0-9]*', factorMax) is None:
                factorMax = self.__calcMinMaxForStrExpression(factorCol, factorRegex, factorMax)
            else:
                factorMax = float(factorMax)

            factorCol *= factorMax - factorMin
            factorCol += factorMin

            # overwrite column of factorPlan
            self.factorPlan[factorWithExpr] = factorCol

            j += 1

    def __calcMinMaxForStrExpression(self, factorCol, factorRegex, minMax):
        minMaxCol = np.zeros(len(factorCol))

        # loop through all tests
        i = 0
        for testNr in range(len(factorCol)):
            # get all factors, operators (+-*/) and number (float or int)
            expressionList = re.findall('%s|[+|\-|\*|/]|[[0-9]+[\.]?[0-9]*' % (factorRegex), minMax)

            # extract factors from expressionlist
            factorsInExprList = list(set(expressionList) & set(self.factorList))
            # calculate values for factor with min or max
            for factorInExpr in factorsInExprList:
                factorValue = self.factorPlan.loc[i].at[factorInExpr]
                factorExpr = minMax.replace(factorInExpr, str(factorValue))

            # calculate expression
            minMaxCol[i] = eval(factorExpr)
            i += 1
        return minMaxCol


from django.contrib.auth import get_user_model
from django.db.models import F, Sum
from django.http.response import HttpResponse
from django_filters.rest_framework import DjangoFilterBackend
from djoser.views import UserViewSet as DjoserUserViewSet
from recipes.models import (AmountIngredientRecipe, Favorite, Follow,
                            Ingredient, Recipe, ShoppingCart, Tag)
from rest_framework import permissions, status, viewsets
from rest_framework.decorators import action
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response

from .filters import IngredientFilter, RecipeFilter
from .mixins import FavoriteShoppingcartMixin
from .permissions import IsOwnerAdminOrReadOnly
from .serializers import (FollowSerializer, IngredientSerializer,
                          RecipesSerializer, TagSerializer)

User = get_user_model()

class UsersViewSet(DjoserUserViewSet):
    """Вьюсет для пользователей"""

    @action(
        methods=['post', 'delete'],
        detail=True,
        permission_classes=[permissions.IsAuthenticated],
    )
    def subscribe(self, request, id):
        """
        Создаётся или удаляется подписка на пользователя.

        :param request: данные запроса.
        :param pk: id пользователя на которого нужно подписаться(отписаться).
        :return:
        """

        user = request.user
        author = get_object_or_404(User, id=id)
        is_subscribed = Follow.objects.filter(
            user=user,
            author=author
        ).exists()

        if request.method == 'DELETE':
            if not is_subscribed:
                return Response(
                    {'errors': 'Вы не были подписаны на этого автора'},
                    status=status.HTTP_400_BAD_REQUEST
                )

            Follow.objects.get(user=user, author=author).delete()
            return Response(status=status.HTTP_204_NO_CONTENT)

        if user == author:
            return Response(
                {'errors': 'Подписка самого на себя невозможна'},
                status=status.HTTP_400_BAD_REQUEST
            )

        if is_subscribed:
            return Response(
                {'errors': 'Вы уже подписаны на этого пользователя'},
                status=status.HTTP_400_BAD_REQUEST
            )

        Follow.objects.create(user=user, author=author)

        serializer = FollowSerializer(
            author,
            context={'request': request}
        )
        return Response(serializer.data, status=status.HTTP_201_CREATED)

    @action(
        methods=['get'],
        detail=False,
        permission_classes=[permissions.IsAuthenticated],
    )
    def subscriptions(self, request):
        """
        Получаем всех пользователей на которых подписан.

        :param request: данные запроса.
        :return: Возвращает сериализованные данные через FollowSerializer
                 с пагинацией.
        """

        queryset = User.objects.filter(following__user_id=request.user.id)
        page = self.paginate_queryset(queryset)
        serializer = FollowSerializer(
            page,
            many=True,
            context={'request': request}
        )
        return self.get_paginated_response(serializer.data)

class TagViewSet(viewsets.ReadOnlyModelViewSet):
    """
    Вьюсет для тегов.

    Теги доступны только для чтения.
    """

    queryset = Tag.objects.all()
    serializer_class = TagSerializer
    pagination_class = None

class IngredientViewSet(viewsets.ReadOnlyModelViewSet):
    """
    Вьюсет для ингредиентов.

    Теги доступны только для чтения.
    """

    queryset = Ingredient.objects.all()

    serializer_class = IngredientSerializer
    pagination_class = None
    filter_backends = (DjangoFilterBackend,)
    filterset_class = IngredientFilter

class RecipesViewSet(viewsets.ModelViewSet, FavoriteShoppingcartMixin):
    """Вьюсет для рецептов"""

    queryset = Recipe.objects.all()
    serializer_class = RecipesSerializer
    permission_classes = (IsOwnerAdminOrReadOnly,)
    filter_backends = (DjangoFilterBackend,)
    filterset_class = RecipeFilter

    @action(
        methods=['post', 'delete'],
        detail=True,
        permission_classes=[IsOwnerAdminOrReadOnly],
    )
    def favorite(self, request, pk):
        """
        Добавляет(удаляет) рецепт в избранное пользователя.

        :param pk: id добавляемого рецепта.
        :param request: данные запроса.
        :return: Возвращает сериализованный рецепт, который добавили
                 или удалили из избранного.
        """

        return self.add_del_to_db(
            request=request,
            pk=pk,
            related_model=Favorite
        )

    @action(
        methods=['post', 'delete'],
        detail=True,
        permission_classes=[IsOwnerAdminOrReadOnly],
    )
    def shopping_cart(self, request, pk):
        """
        Добавляет(удаляет) рецепт в корзину для покупок.

        :param pk: id добавляемого рецепта.
        :param request: данные запроса.
        :return: Возвращает сериализованный рецепт, который добавили
                 или удалили в корзину для покупок.
        """

        return self.add_del_to_db(
            request=request,
            pk=pk,
            related_model=ShoppingCart
        )

    @action(
        methods=['get'],
        detail=False,
        permission_classes=[IsOwnerAdminOrReadOnly],
    )
    def download_shopping_cart(self, request):
        """
        Формирует файл списка продуктов из рецептов в списке покупок.

        :param request:
        :return:
        """

        user = request.user

        if user.is_anonymous:
            raise permissions.exceptions.AuthenticationFailed

        if not user.shoppingcart.all().exists():
            return Response(status=status.HTTP_400_BAD_REQUEST)

        recipes_of_user = user.shoppingcart.values('recipe')
        ingredients_in_recipes = AmountIngredientRecipe.objects.filter(
            recipe__in=recipes_of_user
        )
        sum_ingredients = ingredients_in_recipes.values(
            ingredient_name=F('ingredient__name'),
            measurement_unit=F('ingredient__measurement_unit')
        ).annotate(amount=Sum('amount'))

        list_ingredients = (f'Список продуктов для пользователя с именем: '
                            f'{user.get_full_name()}\n\n')

        for ingredient in sum_ingredients:
            ingredient_str = (f'{ingredient["ingredient_name"]} '
                              f'({ingredient["measurement_unit"]}) - '
                              f'{ingredient["amount"]}\n')
            list_ingredients += ingredient_str

        file_name = f'shopping_cart_{user.username}.txt'
        response = HttpResponse(
            content=list_ingredients,
            content_type='text/plain; charset=utf-8',
            status=status.HTTP_200_OK,
        )
        response['Content-Disposition'] = f'attachment; filename={file_name}'
        return response


class BonusCardType:
    BONUS_CARD = 'BonusCard'
    UNIVERSAL_CARD = 'UniversalCard'
    RZHD_BINNUS_DISCOUNT = 'RzdBonusDiscount'

class SegmentType:
    # Неопределенный
    UNKNOWN = 'Unknow'
    # Одиночный ЖД сегмент
    RAILWAY = 'Railway'
    # Паромный сегмент
    FERRY = 'Ferry'
    # ЖД сегмент, но в заказе еще есть паромный сегмент
    RAILWAY_FERRY = 'Railwaywithferry'
    # Автобусный сегмент
    BUS = 'Bus'
    # ЖД сегмент, но в заказе есть еще сегмент с автобусом
    RAILWAY_BUS = 'Railwaywithbus'
    # ЖД сегмент, но в заказе есть еще паромный сегмент и сегмент с автобусом
    RAILWAY_BUS_FERRY = 'Railwaywithbusandferry'
    # Направление туда
    THERE = 'There'
    # Направление обратно
    BACK = 'Back'
    # ЖД сегмент, направление туда
    RAILWAY_THERE = 'Railwaythere'
    # ЖД сегмент, направление обратно
    RAILWAY_BACK = 'Railwayback'

class DocType:
    PASSPORT = 'ПН'
    BIRTH_CERTIFICATE = 'СР'
    RUSSIAN_FOREIGN_PASSPORT = 'ЗП'
    FOREIGN_PASSPORT = 'ЗЗ'
    PASSPORT_SEAMAN = 'ПМ'
    MILITARY_ID = 'ВБ'
    USSR_PASSPORT = 'ПС'
    MILITARY_OFFICER_CARD = 'УВ'
    STATELESS_PERSON_ODENTITY_CARD = 'БГ'
    RESIDENCE_PERMIT = 'ВЖ'
    RUSSIAN_TEMPORARY_IDENTITY_CARD = 'СУ'

class TimeSw:
    # Временной интервал не учитывается
    NO_SW = 0
    # Временной интервал применяется к моменту отправки поезда
    # (параметры Time_from, Time_to в таком случае принимаются целочисленным типом в интервале [0;24])
    TRAIN_DISPATCH = 1
    # Временной интервал применяется к моменту прибытия поезда
    # (параметры Time_from, Time_to в таком случае принимаются целочисленным типом в интервале [0;23])
    TRAIN_ARRIVAL = 2

class DirectionGroup:
    # Внутригосударственное сообщение, а также международное сообщение
    # со странами-участниками СНГ, Литовской, Латвийской, Эстонской, республиками, Республикой Абхазия
    INTERNAL = 0
    # Международное сообщение в дальнем зарубежье по глобальным ценам. Направление Россия-Финляндия
    RUSSIAN_FINLAND = 1
    # Международное сообщение в дальнем зарубежье по глобальным ценам. Направление Восток-Запад
    EAST_WEST = 2

class Lang:
    RU = 'Ru'
    EN = 'En'
    DE = 'De'

class TrainWithSeat:
    # Получение информации о поездах со свободными местами
    FREE_PLACE = 1
    # Получение информации обо всех поездах
    ALL = 0

class GrouppingType:
    # Группировка поездов по типу вагона
    TYPE = 0
    # Группировка поездов по типу вагона и по классу обслуживания
    TYPE_AND_SERVICE = 1
    # Группировка по типу вагона и по признакам QM/DM
    TYPE_AND_QM_DM = 2
    # Группировка по типу мест и цене
    PLACE_TYPE_AND_PRICE = 3
    # Группировка по номеру вагона и цене
    CAR_NUMBER_AND_PRICE = 4
    # Без группировки
    NONE = 5

class JoinTrains:
    # Получение списка поездов со склейкой
    WITH_BONDING = 0
    # Получение списка поездов без склеивания. Используется в случае, если в одном и том же поезде разные вагоны имеют разное время прибытия
    WITHOUT_BONDING = 1

class SearchOption:
    # Обычный поиск
    DEFAULT = 0
    # Включить в поиск маршруты в Крымский федеральный округ
    CRIMEA = 1
    # Включить в поиск мультимодальные перевозки
    MULTIMODAL = 2
    # Включить в поиск маршруты в Крымский федеральный округ и мультимодальные перевозки
    CRIMEA_AND_MULTIMODAL = 3
    # Включить в поиск маршруты с пересадкой
    TRANSFER = 4
    # Включить в поиск маршруты в Крымский федеральный округ и маршруты с пересадкой
    CRIMEA_AND_TRANSFER = 5
    # Включить в поиск мультимодальные перевозки и маршруты спересадкой
    MULTIMODAL_AND_TRANSFER = 6
    # Включить в поиск маршруты в Крымский федеральный округ, маршруты с пересадкой и мультимодалные перевозки
    ALL = 7

class AllowedDocType:
    PASSPORT = 'ПН'
    BIRTH_CERTIFICATE = 'СР'
    MILITARY_ID = 'ВБ'
    PASSPORT_SEAMAN = 'ПМ'
    RUSSIAN_FOREIGN_PASSPORT = 'ЗП'
    FOREIGN_PASSPORT = 'ЗЗ'

class Services:
    EAT = 'EAT'
    PAP = 'PAP'
    TV = 'TV'
    TRAN = 'TRAN'
    COND = 'COND'
    BED = 'BED'
    SAN = 'SAN'
    WIFI = 'WIFI'

class LoyaltyCards:
    # Начисление баллов "РЖД Бонус"
    RZHD_BONUS_SCORING = 'RzhdB'
    # Дорожная карта
    RZHD_MAP = 'RzhdU'
    # Скидка по карте "РЖД Бонус"
    RZHD_BONUS_DISCOUNT = 'RzhdSU'

class CarCategories:
    # Купейный вагон РИЦ (KV = КУПЕ)
    KUPE = 'РИЦ'
    # Мягкий вагон РИЦ (KV=МЯГК)
    SOFT = 'РИЦ'
    # Мягкий вагон Вагон Люкс (KV=МЯГК)
    SOFT_LUX = 'ЛЮКС'
    # Вагон класса люкс Вагон СВ (KV = ЛЮКС)
    LUX = 'СВ'

class AvailableTariffs:
    # Полный
    FULL = 1
    # Детский (до 10 лет)
    CHILD_TO_10 = 2
    # Детский без места (до 5 лет)
    CHILD_WITHOUT_PLACE_TO_5 = 3
    # SENIOR (60+) в Сапсан
    SENIOR_SAPSAN = 4
    # SENIOR (от 60 лет) ГЕНИАЛЬНО
    SENIOR = 5
    # JUNIOR (от 12 до 26 лет)
    JUNIOR = 6
    # Детский (до 12 лет)
    CHILD_TO_12 = 7
    # Детский без места (до 4 лет)
    CHILD_WITHOUT_PLACE_TO_4 = 8
    # Детский (до 17 лет)
    CHILD_TO_17 = 9
    # Детский без места (до 6 лет)
    CHILD_WITHOUT_PLACE_TO_6 = 10
    # Детский (до 7 лет)
    CHILD_TO_7 = 11
    # Детский без места(до 10 лет)
    CHILD_WITHOUT_PLACE_TO_10 = 12
    # Детский (от 10 до 17 лет)
    CHILD_FROM_10_TO_17 = 13
    # Школьник (для учащихся от 10 лет)
    SCHOOLBOY_FROM_10 = 14
    # Детский без места (для детей до 12 лет)
    CHILD_WITHOUT_PLACE_TO_12 = 15
    # Детский без места (для детей до 6 лет) ЧТО ? В ЧЁМ ТУТ РАЗНИЦА С ПУНКТОМ ВЫШЕ ? УФСМАТЬВАШУ
    CHILD_2_WITHOUT_PLACE_TO_6 = 16
    # Молодежный ЮНИОР (для лиц от 10 до 21 года)
    JUNIOR_FROM_10_TO_21 = 17
    # Праздничный
    FESTIVE = 18
    # Свадебный
    WEDDING = 19
    # Семейный
    FAMILY = 20

class PlaceTypeNumber:
    # Нижний ярус
    TOP = 'Н'
    # Верхний ярус
    BOTTOM = 'В'
    # Средний ярус
    MIDDLE = 'С'
    # (для сидячих вагонов) Для пассажира с животным
    ANIMALS = 'Ж'
    # (для сидячих вагонов) Для матери и ребенка
    MOTHER_CHILD = 'М'
    # (для сидячих вагонов) Для пассажира с детьми
    CHILDREN = 'Р'
    # (для сидячих вагонов) Для инвалидов
    INVALID = 'И'
    # (для сидячих вагонов) Переговорная
    MEETING_ROOM = 'Г'
    # (для сидячих вагонов) Не у стола
    NOT_TABLE = 'Н'
    # (для сидячих вагонов) У стола
    TABLE = 'П'
    # (для сидячих вагонов) У детской площадки
    PLAYGROUND = 'В'
    # (для сидячих вагонов) У стола рядом с детской площадкой
    TABLE_PLAYGROUND = 'Б'
    # (для сидячих вагонов) Рядом с местами для пассажиров с животными
    BESIDE_ANIMALS = 'Д'
    # (для сидячих) Откидное
    FOLDING = 'О'
    # (для сидячих вагонов) Отсек (купе) в поезде Ласточка
    SITTING_KUPE = '7'

class Confirm:
    # Отказ от зарезервированного заказа
    REFUSE = 0
    # Подтверждение зарезервированного заказа
    CONFIRM = 1
    # Выбрано трехчасовое резервирование заказа
    THREE_HOURS = 2

class ElectronicRegistration:
    # Отказ от электронной регистрации
    REFUSE = 0
    # Согласие на электронную регистрацию
    CONFIRM = 1
    # Установка ЭР не возможна
    IMPOSSIBLE = 3
    # Промежуточный статус, устанавливается до отмены ЭР
    PRE_REFUSE= 4
    # Промежуточный статус, устанавливается до подтверждения заказа.
    PRE_CONFIRM = 5
    # Статус ЭР не известен
    NO_DATA = 6
    # Бланк с ЭР распечатан
    PRINTED = 7
    # Статусы ЭР в бланках одного заказа различаются
    DIFFERENT_DATA = 8
    # Ошибочный статус ЭР
    ERROR_STATUS = 20

class Test:
    # Покупка настоящая (после её подтверждения деньги будут списаны)
    REAL = 0
    # Покупка тестовая, обращения на резервирование мест в АСУ «Экспресс-3» не было
    TEST = 1

class PrintFlag:
    # Не распечатан
    NOT_PRINTED = 0
    # Распечатан
    PRINTED = 1

class RzhdStatus:
    # Без электронной регистрации
    WITHOUT_ELECTRONIC_REGISTRATION = 0
    # Электронная регистрация
    ELECTRONIC_REGISTRATION = 1
    # Оплата не подтверждена
    FAILED_PAYMENT = 2
    # Аннулирован
    CANCELED = 3
    # Возвращен
    RETURNED = 4
    # Возвращены места
    RETURNED_PLACES= 5
    # Выдан посадочный купон (проездной документ) на бланке строгой отчетности
    TICKET_ISSUED = 6
    # Отложенная отплата (статус возвращается после создания бронирования)
    DEFERRED_PAYMENT = 7

class Registration:
    REFUSE = 0
    CONFIRM = 1

class ReferenceCode:
    CO_SERVICES = 'CO_SERVICES'
    LOYALTY_CARDS = 'LOYALTY_CARDS'

class InOneKupe:
    # Требование не задано
    NONE = 0
    # В одном купе/в одном ряду (для вагонов с местами для сидения)
    IN_ONE_KUPE_OR_ROW = 1
    # Не боковые места
    NOT_SIDE = 2
    # Места в одном отсеке
    IN_ONE_COMPARTMENT = 3

class Bedding:
    # В стоимость билета не включено постельное белье
    NO_LINENS = 0
    # В стоимость билета включено постельное белье
    LINENS = 1

class FullKupe:
    # Выкуп купе целиком
    ALL_KUPE = 1
    # Выкуп СВ целиком
    ALL_SV = 2

class RemoteCheckIn:
    # Без электронной регистрации
    WITHOUT_ELECTRONIC_REGISTRATION = 0
    # Электронная регистрация
    ELECTRONIC_REGISTRATION = 1
    # Если ЭР возможна, то при подтверждении ЭР устанавливается автоматически
    TRY_AUTO_ER = 2

class PayType:
    # Оплата наличными
    CASH = 'Cash'
    # Credit card. Оплата банковской картой. Данный тип оплаты доступен не всем партнерам.
    CREDIT_CARD = 'CC'

class Storey:
    # Требования к этажу не заданы
    NONE = 0
    # Первый этаж вагона
    FIRST_STOREY = 1
    # Второй этаж вагона
    SECOND_STOREY = 2

class PlaceDemands:
    # Места не у стола
    NO_TABLE = 'Н'
    # Места у стола (обычные места)
    TABLE = 'П'
    # Места рядом с детской площадкой и не у стола
    PLAYGROUND_NO_TABLE = 'В'
    # Места рядом с детской площадкой и у стола
    PLAYGROUND_TABLE = 'Б'
    # Места рядом с местами для пассажиров с животными
    BESIDE_ANIMALS = 'Д'
    # Любое место у стола (включая рядом с местами для пассажиров с животными, детской площадкой и т.д)
    ANY_TABLE = 'СТОЛ'
    # Места не у стола, включая места рядом с детской площадкой и рядом с местами для пассажиров с животными.
    # Обобщающее требование. Рекомендуется использовать в случае, если не выбран номер вагона
    PLAYGROUND_ANIMALS_NO_TABLE = 'НСТ'
    # Места рядом с детской площадкой
    PLAYGROUND = 'ДЕТ'
    # Места для инвалидов
    INVALID = 'И'
    # Места для пассажиров с животными
    FOR_ANIMALS = 'Ж'
    # Отсек (купе)
    KUPE = '7'
    # Откидное место
    HINGED_SEAT = 'О'
    # Место матери и ребенка до 1 года
    MOTHER_AND_CHILDREN_TO_1 = 'М'
    # Места для пассажиров с детьми
    MOTHER_AND_CHILDREN = 'Р'

class TicketFormat:
    HTML = 'html'
    PDF = 'pdf'

class Gender:
    MALE = 'мужское'
    FEMALE = 'Женское'
    MIXED = 'Смешаное'

class AllLanguages:
    # Ответ на всех доступных языках;
    ALL = 1
    # Ответ только на одном конкретном языке, который указан в элементе Lang
    AS_LANG = 0


import sys

board = [[0]*100 for _ in range(100)]                           # x, y의 값이 1이상 100이하이므로 2차원 배열 최대 크기가 100 * 100
answer = 0                                                      # 4개 사각형의 면적

for _ in range(4):
    x1, y1, x2, y2 = map(int, sys.stdin.readline().split())

    for i in range(x1, x2):
        for j in range(y1, y2):
            board[i-1][j-1] = 1                                 # 사각형 영역의 값을 1로 설정

for row in board:                                               # 행별로 값이 1인 부분의 갯수 카운트
    answer += row.count(1)

print(answer)


import sys

input=sys.stdin.readline

s=int(input())

n=1
while(1):
    s1=(n*(n+1))/2
    s2=((n+1)*(n+2))/2

    if(s1 <= s < s2):
        print(n)
        break

    n+=1
    continue


from collections import deque

# BFS 함수 정의
def bfs(sx, sy, ex, ey):
    # 시작 지점이 목표 지점과 같은 경우, 함수 종료
    if sx == ex and sy == ey:
        return
    queue = deque([(sx, sy)])

    # 나이트가 움직일 수 있는 방향 벡터 정의
    dx = [-2, -1, 1, 2, 2, 1, -1, -2]
    dy = [1, 2, 2, 1, -1, -2, -2, -1]

    while queue:
        x, y = queue.popleft()
        for d in range(8):
            nx = x + dx[d]
            ny = y + dy[d]
            # 범위를 벗어나는 경우, 무시
            if nx < 0 or nx >= i or ny < 0 or ny >= i:
                continue
            # 처음 도달하는 칸인 경우, 이동 횟수를 기록하고 해당 칸의 위치를 큐에 삽입
            if graph[nx][ny] == 0:
                queue.append((nx, ny))
                graph[nx][ny] = graph[x][y] + 1

t = int(input())
# 테스트 케이스 수 만큼 반복
for _ in range(t):
    i = int(input())
    sx, sy = map(int, input().split())  # 나이트가 현재 있는 칸 입력 받기
    ex, ey = map(int, input().split())  # 나이트가 이동하려고 하는 칸 입력 받기

    graph = [[0] * i for _ in range(i)]  # i X i 크기의 체스판 생성

    # BFS 수행
    bfs(sx, sy, ex, ey)
    # 결과 출력
    print(graph[ex][ey])


from moviepy.editor import *
import os
from natsort import natsorted

L =[]

for root, dirs, files in os.walk("D:\\Sujay\\German\\Best Way to Learn German Language-Full Beginner Course-A1.1\\New folder"):

    #files.sort()
    files = natsorted(files)
    for file in files:
        if os.path.splitext(file)[1] == '.mp4':
            filePath = os.path.join(root, file)
            video = VideoFileClip(filePath)
            L.append(video)

final_clip = concatenate_videoclips(L)
final_clip.to_videofile("output.mp4", fps=24, remove_temp=False)


from tkinter import *
from tkinter import messagebox, Entry
index=0
w=Tk()
w.title("Restaurant Management System")
count=0
def ok():
        print("OK")
        s1.set("OK")
def addrec():
        f=open("mydata.txt","a")
        n=s1.get()
        a=s2.get()
        b=s3.get()
        c=s4.get()
        d=s5.get()
        for i in range(len(n),20):
                n=n+" "
        for i in range(len(a),20):
                a=a+" "

        for i in range(len(b),20):
                b=b+" "

        for i in range(len(c),20):
                c=c+" "
        for i in range(len(d),20):
                d=d+" "
        f.writelines(n+" "+a+" "+b+" "+c+" "+d+"\n")
        f.close()
def nextrec():
        file=open('mydata.txt','r')
        global index
        index=index+1
        file.seek(index)
        try:
                c=file.readlines()
                xyz = c[index]
                l=list(xyz.split())
                t1.delete(0,'end');t2.delete(0,'end');t3.delete(0,'end');t4.delete(0,'end');t5.delete(0,'end');
                t1.insert(0,l[0]);t2.insert(0,l[1]);t3.insert(0,l[2]);t4.insert(0,l[3]);t5.insert(0,l[4]);
        except:
                messagebox.showinfo("Title", "")
        file.close()

def prevrec():
        file=open('mydata.txt','r')
        global index
        index=index-1
        try:
                file.seek(index)
                c=file.readlines()
                xyz = c[index]
                l=list(xyz.split())
                t1.delete(0,'end');t2.delete(0,'end');t3.delete(0,'end');t4.delete(0,'end');t5.delete(0,'end');
                t1.insert(0,l[0]);t2.insert(0,l[1]);t3.insert(0,l[2]);t4.insert(0,l[3]);t5.insert(0,l[4]);
        except:
                messagebox.showinfo("Title", "no more records")
        file.close()
def firstrec():
        file=open('mydata.txt','r')
        t1.delete(0,'end');t2.delete(0,'end');t3.delete(0,'end');t4.delete(0,'end');t5.delete(0,'end');
        file.seek(0)
        k=file.readline()
        k=k.split()
        t1.insert(0,k[0]);t2.insert(0,k[1]);t3.insert(0,k[2]);t4.insert(0,k[3]);t5.insert(0,k[4]);
        file.close()
def updaterec():
        file=open('mydata.txt','r')
        lines=file.readlines()
        file.close()
        file=open('mydata.txt','w')
        for line in lines:
                com=line.split()
                if com[0]==t1.get():
                        file.write(t1.get()+'\t'+t2.get()+'\t'+t3.get()+'\t'+t4.get()+'\t'+t5.get()+'\n')
                        messagebox.showinfo('Title','updated')
                else:
                        file.write(line)
        t1.delete(0,'end');t2.delete(0,'end');t3.delete(0,'end');t4.delete(0,'end');t5.delete(0,'end');
        file.close()
def exitt():
        w.destroy()
def deleterec():
        f=open("mydata.txt","r")
        lines = f.readlines()
        f.close()
        f = open("mydata.txt","w")
        for line in lines:
                com = line.split()
                if com[0] != s4.get():
                        f.write(line)
        messagebox.showinfo("Alert","Record Deleted!")
        s5.delete(0,"end")
        s4.delete(0,"end")
        s3.delete(0,"end")
        s2.delete(0,"end")
        s1.delete(0,"end")
        f.close()
def lastrec():
        file=open('mydata.txt','r')
        t1.delete(0,'end');t2.delete(0,'end');t3.delete(0,'end');t4.delete(0,'end');t5.delete(0,'end');
        for i in file:
                k=i
        k=k.split()
        t1.insert(0,k[0]);t2.insert(0,k[1]);t3.insert(0,k[2]);t4.insert(0,k[3]);t5.insert(0,k[4]);
        file.close()
l1=Label(w,text="Food")
l2=Label(w,text="Drinks")
l3=Label(w,text="Total")
l4=Label(w,text="Reference")
l5=Label(w,text="Type")
s1=StringVar()
s2=StringVar()
s3=StringVar()
s4=StringVar()
s5=StringVar()
t1=Entry(w,textvariable=s1)
t2=Entry(w,textvariable=s2)
t3=Entry(w,textvariable=s3)
t4=Entry(w,textvariable=s4)
t5=Entry(w,textvariable=s5)
l1.grid(row=0,column=1)
l2.grid(row=1,column=1)
l3.grid(row=2,column=1)
l4.grid(row=3,column=1)
l5.grid(row=4,column=1)
t1.grid(row=0,column=2)
t2.grid(row=1,column=2)
t3.grid(row=2,column=2)
t4.grid(row=3,column=2)
t5.grid(row=4,column=2)
b1=Button(w,text="ADD",command=addrec)
b2=Button(w,text=">",command=nextrec)
b3=Button(w,text="EXIT",command=exitt)
b4=Button(w,text="LAST",command=lastrec)
b5=Button(w,text="<",command=prevrec)
b6=Button(w,text="FIRST",command=firstrec)
b7=Button(w,text="UPDATE",command=updaterec)
b8=Button(w,text="DELETE",command=deleterec)
b1.grid(row=5,column=1)
b2.grid(row=5,column=2)
b3.grid(row=5,column=3)
b4.grid(row=5,column=4)
b5.grid(row=6,column=1)
b6.grid(row=6,column=2)
b7.grid(row=6,column=3)
b8.grid(row=6,column=4)
w.mainloop()


import requests
from bs4 import BeautifulSoup
import json

tokens = []

for x in range(1, 12):
    result = requests.get("https://etherscan.io/tokens?p=" + str(x))
    c = result.content

    soup = BeautifulSoup(c, "html.parser")

    samples = soup.find_all("tr")

    for sample in samples:
        try:
            if ("token" in sample.find_all("td")[1].find("a")['href']):
                tokens.append({"address": sample.find_all("td")[1].find("a")['href'].replace("/token/", ""), "image": sample.find_all("td")[1].find("img")['src'], "name": sample.find_all("td")[2].find("a").text})

        except Exception as e:
            continue

print(json.dumps(tokens))


import numpy as np
from ..patch import Patch
from ..parcel import Parcel

class PropertyDeveloper:
    def __init__(self, world, agent_type, view_radius=5, memory=100):
        self.world = world
        self.view_radius = view_radius
        self.memory = memory
        self.position = np.random.choice(world.patches.flatten()) # Starts in a random patch of the map
        self.dev_sites = [] # TO DO: initialize dev_sites using starting position
        self.dev_patches = []
        self.considered_patches = []
        self.agent_type = agent_type

        # Weights for each type of developer
        ##        eh  ev  epv  dw   dr  di  dpk dpr dm   x
        self.W = {
            "r": [.1, .2,  0,  .1,  .4,  0,  0,  .2,  0,  0],
            "c": [ 0, .2,  0, .15, .15,  0,  0,  0, .4,  0],
            "i": [ 0, .5,  0,  .3,   0, .1,  0, .1,  0,  0],
            "p": [ 0,  0, .2,  .1,  .1,  0, .4,  0,  0, .2]
        }

    def getRegion(self, i, j):
        region = self.world.patches[max(1,i-self.view_radius):i+self.view_radius, # Adding padding of 1 patch
                         max(1, j-self.view_radius):j+self.view_radius].flatten()
        region = [p for p in region if p.developable] # Selecting only developable patches (excludes water, etc)

        region_parcels = []
        region_patches = []
        for patch in region:
            if (patch.parcel == None):
                region_patches.append(patch)
            elif ( patch.parcel not in region_parcels and patch.parcel.development_type != self.agent_type): # Excludes parcels of same development type as the agent (TO DO: provisory)
                region_parcels.append(patch.parcel)

        return region_patches, region_parcels

    # Searches for suitable locations in its surroundings
    def prospect(self, dev_sites):
        i, j = [self.position.i, self.position.j]
        region_patches, region_parcels = self.getRegion(i, j)
        patch_values, parcel_values = [self.world.patch_values, self.world.parcel_values]
        values = patch_values | parcel_values
        combined_region = region_patches + region_parcels

        if (len(dev_sites)!=0 and (self.last_commit>0 or self.last_relocate>0)): # Move to a seen location
            combined_region = combined_region + dev_sites # Searching in union of region and memory
            region_values = [values[p][self.agent_type] for p in combined_region]
            next_location = combined_region[np.argmax(region_values)]
        else: # Relocate globlally
            self.last_relocate = 5 # Resets relocation counter

            # Selecting all empty patches from the world
            world_patches = [p for p in self.world.patches.flatten() if p.undeveloped and p.developable]
            world_parcels = [p for p in self.world.parcels if p.development_type != self.agent_type] # TO DO: check if conversion to agent_type is possible

            world_sites = world_patches + world_parcels
            sorted_idx = np.argsort([values[p][self.agent_type] for p in world_sites])[::-1]
            world_sites = np.array(world_sites)[sorted_idx]

            # Move to a random site in the top 5 best for the agent
            try:
                next_location = np.random.choice(world_sites[:5])
            except: # No more areas to develop
                return []
            self.dev_patches = []
        self.position = next_location

        #region_patches, region_parcels = self.getRegion() # NOTE: maybe have to be updated after relocation

        # TO DO / NOTE : Check this implementation better later

        dev_parcels = region_parcels

        dev_patches = []
        for patch in self.dev_patches + region_patches:
            if patch not in dev_patches and patch.undeveloped:
                dev_patches.append(patch)
        dev_patches_sorted_idx = np.argsort([values[p][self.agent_type] for p in dev_patches])[::-1]
        dev_patches = np.array(dev_patches)[dev_patches_sorted_idx]
        self.dev_patches = list(dev_patches[:int(0.9*len(dev_patches))]) # Selecting only the 90% best patches

        dev_sites = list(dev_patches) + dev_parcels
        return dev_sites

    # Returns True if as build successfully and False otherwise
    def build(self, site):
        if (isinstance(site, Patch)): # Building in patch is direct
            self.considered_patches.append(site)
            new_parcel = self.world.createParcel(site, development_type=self.agent_type) # TO DO: expand to create parcels of multiple patches
            if (new_parcel == None):
                return False

            for patch in new_parcel.patches:
                # Preventing roads to be built on top of this patch
                self.world.addBlockedPatch(patch)

            return True
        return False

    def getScore(self, patch):
        i, j = [patch.i, patch.j]
        region_patches, region_parcels = self.getRegion(i, j)

        eh = patch.get_eh(self.world.patches)
        ev, epv = patch.get_ev(self.world.patches)
        dpr = patch.get_dpr()
        dw = patch.get_dw()
        dr = patch.get_dr(region_patches, region_parcels)
        dc = patch.get_dc(region_patches, region_parcels)
        di = patch.get_di(region_patches, region_parcels)
        dm = patch.get_dm(self.world.parcels)
        dpk = patch.get_dm(self.world.parcels)

        A = [eh, ev, epv, dw, dr, di, dpk, dpr, dm, 0]
        W = [self.W['r'], self.W['i'], self.W['i'], self.W['p']]

        Vr, Vc, Vi, _= np.dot(W, A)
        Vp = (1/Vr + 1/Vc + 1/Vi) * self.W['p'][-1] # Anti-worth

        return {'Vr': Vr, 'Vc': Vc, 'Vi': Vi, 'Vp': Vp}

    def prospectNew(self):
        i, j = [self.position.i, self.position.j]
        region_patches, region_parcels = self.getRegion(i, j)

        avaliable_patches = self.dev_patches + region_patches # Memory + new
        avaliable_patches = [p for p in avaliable_patches if p not in self.considered_patches and self.world.isAccessible(p)]

        # No more avaliable patches, relocate globaly
        while len(avaliable_patches) == 0:
            self.position = np.random.choice(self.world.patches.flatten())
            i, j = [self.position.i, self.position.j]
            region_patches, region_parcels = self.getRegion(i, j)
            avaliable_patches = [p for p in region_patches if p not in self.considered_patches and self.world.isAccessible(p)]

        scores = [self.getScore(patch)[self.agent_type] for patch in avaliable_patches]

        idx_best = np.argmax(scores)
        best_patch = avaliable_patches[idx_best]

        #print(f"Selected patch: ({best_patch.i}, {best_patch.j}), undeveloped={best_patch.undeveloped}")

        if (self.position == best_patch):
            self.build(self.position)
            print("Building")
        else:
            self.position = best_patch
            print("Relocating")

        return avaliable_patches

    def buildNew(self):
        avaliable_patches = [p for p in self.world.patches.flatten() if p.developable and p.undeveloped and p not in self.considered_patches]

        # Scoring all patches and sorting them by score
        scores = [self.getScore(p)[self.agent_type] for p in avaliable_patches]
        sorted_idx = np.argsort(scores)[::-1]
        sorted_patches = np.array(avaliable_patches)[sorted_idx]

        # Triyng to build in the best score avaliable
        built = False
        for patch in sorted_patches:
            # Checks if a patch is accessible
            if(self.build(patch)):
                break

    # Interacts with the environment
    def interact(self):
        self.dev_patches = self.prospectNew()
        self.dev_patches = self.dev_patches[:min(len(self.dev_patches), self.memory)]

        # Removing already developed patched from the list
        self.dev_patches = [p for p in self.dev_patches if p.developable]

        '''
        self.build(self.position) # My version

        #for site in self.dev_sites: # Paper implementation
        #    self.build(site)

        # Decreases counters
        self.last_commit -= 1
        self.last_relocate -= 1
        '''

        #print(f"Current position: {self.position.i}, {self.position.j}")


import time
import random

def main():
    rows = 20
    cols = 50
    max = rows * cols
    c=220
    d=700
    m = [0] * max
    n = [0] * max

    print("\033[2J")      # Clear screen

    # Initialize arrays.
    # for j in range(0, i):
    #     m[j] = 0
    #     n[j] = 0

    # drop cells at random locations.
    for j in range(0, d):
        m[random.randint(0, max-1)] = 1

    # While c generations...
    while c > 0:
        c -= 1

        for i in range(51, 949):
            # Count neighbors.
            d = m[i-1] + m[i+1] + m[i-51] + m[i-50] + m[i-49] + m[i+49] + m[i+50] + m[i+51]

            # Compute new state.
            n[i] = m[i]
            if m[i] == 0 and d == 3:
                n[i] = 1
            elif m[i] == 1 and d < 2:
                n[i] = 0
            elif m[i] == 1 and d > 3:
                n[i] = 0

        # Display results.
        print("\033[1;1H")   # Home cursor
        print(c)     # Countdown
        for i in range(0, max): # Gridsize 50x20
            if n[i] > 0:
                print("O", end='')
            else:
                print(".", end='')

            if (i+1) % 50 == 0:
                print('')       # Print new line.

            # And new state to main array.
            m[i] = n[i]

        time.sleep(0.5)

main()


from enum import Enum
import random
import copy

random.seed(None)

def setSeed(s):
    random.seed(s)

"""
Basic enumerated class to specify colors when needed.
"""
class Color(Enum):
    WHITE = 0
    BLACK = 1
    GREEN = 2
    RED = 3
    BLUE = 4
    GOLD = 5

    @classmethod
    def mapToColor(self, color):
        if (color == "W"):
            return Color.WHITE
        elif (color == "K"):
            return Color.BLACK
        elif (color == "E"):
            return Color.GREEN
        elif (color == "R"):
            return Color.RED
        elif (color == "B"):
            return Color.BLUE
        elif (color == "G"):
            return Color.GOLD
        else:
            return None

    def __str__(self):
        s = ""
        if self.value == 1:
            s = "W"
        elif self.value == 2:
            s = "K"
        elif self.value == 3:
            s = "E"
        elif self.value == 4:
            s = "R"
        elif self.value == 5:
            s = "B"
        elif self.value == 6:
            s = "G"
        return s

"""
Basic enumerated class to specify whether a player is a human or AI.
"""
class PlayerType(Enum):
    HUMAN = 1
    AI = 2

"""
A GemDict is a dictionary with Color keys and int values. Dictionary can be accessed through the getter.
"""
class GemDict:
    def __init__(self, gem_lst):
        self.data = {
            Color.WHITE : gem_lst[0],
            Color.BLACK : gem_lst[1],
            Color.GREEN : gem_lst[2],
            Color.RED   : gem_lst[3],
            Color.BLUE  : gem_lst[4]}

    def add(self, color, num):
        if color == Color.WHITE:
            self.data[Color.WHITE] += num

        elif color == Color.BLACK:
            self.data[Color.BLACK] += num

        elif color == Color.GREEN:
            self.data[Color.GREEN] += num

        elif color == Color.RED:
            self.data[Color.RED]   += num

        elif color == Color.BLUE:
            self.data[Color.BLUE]  += num

        else:
            raise ValueError

    def remove(self, color, num):
        if color == Color.WHITE:
            self.data[Color.WHITE] -= num

        elif color == Color.BLACK:
            self.data[Color.BLACK] -= num

        elif color == Color.GREEN:
            self.data[Color.GREEN] -= num

        elif color == Color.RED:
            self.data[Color.RED]   -= num

        elif color == Color.BLUE:
            self.data[Color.BLUE]  -= num

        else:
            raise ValueError

    def total_gems(self):
        return (
            self.data[Color.WHITE]  +
            self.data[Color.BLACK]  +
            self.data[Color.GREEN]  +
            self.data[Color.RED]    +
            self.data[Color.BLUE])

    def get_data(self):
        return self.data

    def data_gui(self):
        return [
            self.data[Color.WHITE],
            self.data[Color.BLACK],
            self.data[Color.GREEN],
            self.data[Color.RED],
            self.data[Color.BLUE]]

    def addGD(self, gd):
        w = self.data_gui()[0] + gd.data_gui()[0]
        k = self.data_gui()[1] + gd.data_gui()[1]
        e = self.data_gui()[2] + gd.data_gui()[2]
        r = self.data_gui()[3] + gd.data_gui()[3]
        b = self.data_gui()[4] + gd.data_gui()[4]
        return self.gdString(w,k,e,r,b)

    def gdString(self,w,k,e,r,b):
        return (str(w)+"|"+str(k)+"|"+str(e)+"|"+str(r)+"|"+str(b))

    def __str__(self):
        w = self.data_gui()[0]
        k = self.data_gui()[1]
        e = self.data_gui()[2]
        r = self.data_gui()[3]
        b = self.data_gui()[4]
        return self.gdString(w,k,e,r,b)

"""
    A State will contain:

    current_player: A Player object of the player who's turn it currently is
    players: A list of Player objects, in turn order, corresponding to each player in the game
    tier1_deck: a list of Card objects that make up the remaining cards in the tier 1 deck
    tier2_deck: a list of Card objects that make up the remaining cards in the tier 2 deck
    tier3_deck: a list of Card objects that make up the remaining cards in the tier 3 deck
    tier1: a list of Card objects that represent the tier 1 cards available for purchase
    tier2: a list of Card objects that represent the tier 2 cards available for purchase
    tier3: a list of Card objects that represent the tier 3 cards available for purchase
    available_gems: a GemDict representing the number of gems available to take
    gold: an int, the number of gold gems available to take
    nobles: a list of GemDicts representing the nobles available for purchase (each noble is worth 3 points)
"""
class State:
    """
    Sets up the initial state of the game with a randomized board and gems for the correct number of players.
    """
    def __init__(self, num_human_players, num_AI_players):
        self._players = []
        for i in range(num_human_players):
            self._players.append(Player(PlayerType.HUMAN, "HUMAN " + str(i)))
        for j in range(num_AI_players):
            self._players.append(Player(PlayerType.AI, "AI " + str(j)))
        random.shuffle(self._players) #put players in random order

        self._current_player = self._players[0] #set current player to first player in list

        if num_AI_players+num_human_players == 2:
            self._available_gems = GemDict([4,4,4,4,4])
        elif num_human_players + num_human_players == 3:
            self._available_gems = GemDict([5,5,5,5,5])
        else:
            self._available_gems = GemDict([7,7,7,7,7])

        self._gold = 5

        self._tier1_deck = self.gen_tier1()
        self._tier2_deck = self.gen_tier2()
        self._tier3_deck = self.gen_tier3()
        random.shuffle(self._tier1_deck)
        random.shuffle(self._tier2_deck)
        random.shuffle(self._tier3_deck)

        self._tier1 = self._tier1_deck[:4]
        self._tier2 = self._tier2_deck[:4]
        self._tier3 = self._tier3_deck[:4]

        self._tier1_deck = self._tier1_deck[4:]
        self._tier2_deck = self._tier2_deck[4:]
        self._tier3_deck = self._tier3_deck[4:]

        self._nobles = self.gen_nobles()
        random.shuffle(self._nobles)
        self._nobles = self._nobles[:(num_human_players+num_AI_players+1)] #num nobles available = num players + 1
        self._turnCount = 1
        self._discarding = False
        self._firstWinner = None
        self._winners = []
        self._running = True
        self._interactions = 0

    """Generates a shuffled deck of tier 1 cards."""
    def gen_tier1(self):
        tier1_deck = []
        tier1_deck.append(Card(Color.BLACK, 0, [1, 0, 1, 1, 1], 1))
        tier1_deck.append(Card(Color.BLACK, 0, [0, 0, 2, 1, 0], 1))
        tier1_deck.append(Card(Color.BLACK, 0, [2, 0, 2, 0, 0], 1))
        tier1_deck.append(Card(Color.BLACK, 0, [0, 1, 1, 3, 0], 1))
        tier1_deck.append(Card(Color.BLACK, 0, [0, 0, 3, 0, 0], 1))
        tier1_deck.append(Card(Color.BLACK, 0, [1, 0, 1, 1, 2], 1))
        tier1_deck.append(Card(Color.BLACK, 0, [2, 0, 0, 1, 2], 1))
        tier1_deck.append(Card(Color.BLACK, 1, [0, 0, 0, 0, 4], 1))

        tier1_deck.append(Card(Color.BLUE, 0, [1, 2, 0, 0, 0], 1))
        tier1_deck.append(Card(Color.BLUE, 0, [1, 1, 1, 2, 0], 1))
        tier1_deck.append(Card(Color.BLUE, 0, [1, 1, 1, 1, 0], 1))
        tier1_deck.append(Card(Color.BLUE, 0, [0, 0, 3, 1, 1], 1))
        tier1_deck.append(Card(Color.BLUE, 0, [0, 3, 0, 0, 0], 1))
        tier1_deck.append(Card(Color.BLUE, 0, [1, 0, 2, 2, 0], 1))
        tier1_deck.append(Card(Color.BLUE, 0, [0, 2, 2, 0, 0], 1))
        tier1_deck.append(Card(Color.BLUE, 1, [0, 0, 0, 4, 0], 1))

        tier1_deck.append(Card(Color.GREEN, 0, [2, 0, 0, 0, 1], 1))
        tier1_deck.append(Card(Color.GREEN, 0, [0, 0, 0, 2, 2], 1))
        tier1_deck.append(Card(Color.GREEN, 0, [1, 0, 1, 0, 3], 1))
        tier1_deck.append(Card(Color.GREEN, 0, [1, 1, 0, 1, 1], 1))
        tier1_deck.append(Card(Color.GREEN, 0, [1, 1, 0, 1, 2], 1))
        tier1_deck.append(Card(Color.GREEN, 0, [0, 2, 0, 2, 1], 1))
        tier1_deck.append(Card(Color.GREEN, 0, [0, 0, 0, 3, 0], 1))
        tier1_deck.append(Card(Color.GREEN, 1, [0, 4, 0, 0, 0], 1))

        tier1_deck.append(Card(Color.RED, 0, [3, 0, 0, 0, 0], 1))
        tier1_deck.append(Card(Color.RED, 0, [1, 3, 0, 1, 0], 1))
        tier1_deck.append(Card(Color.RED, 0, [0, 0, 1, 0, 2], 1))
        tier1_deck.append(Card(Color.RED, 0, [2, 2, 1, 0, 0], 1))
        tier1_deck.append(Card(Color.RED, 0, [2, 1, 1, 0, 1], 1))
        tier1_deck.append(Card(Color.RED, 0, [1, 1, 1, 0, 1], 1))
        tier1_deck.append(Card(Color.RED, 0, [2, 0, 0, 2, 0], 1))
        tier1_deck.append(Card(Color.RED, 1, [4, 0, 0, 0, 0], 1))

        tier1_deck.append(Card(Color.WHITE, 0, [0, 1, 2, 0, 2], 1))
        tier1_deck.append(Card(Color.WHITE, 0, [0, 1, 0, 2, 0], 1))
        tier1_deck.append(Card(Color.WHITE, 0, [0, 1, 1, 1, 1], 1))
        tier1_deck.append(Card(Color.WHITE, 0, [0, 0, 0, 0, 3], 1))
        tier1_deck.append(Card(Color.WHITE, 0, [0, 0, 2, 0, 2], 1))
        tier1_deck.append(Card(Color.WHITE, 0, [0, 1, 2, 1, 1], 1))
        tier1_deck.append(Card(Color.WHITE, 0, [3, 1, 0, 0, 1], 1))
        tier1_deck.append(Card(Color.WHITE, 1, [0, 0, 4, 0, 0], 1))
        return tier1_deck

    """Generates a shuffled deck of tier 2 cards."""
    def gen_tier2(self):
        tier2_deck = []
        tier2_deck.append(Card(Color.BLACK, 1, [3, 0, 2, 0, 2], 2))
        tier2_deck.append(Card(Color.BLACK, 1, [3, 2, 3, 0, 0], 2))
        tier2_deck.append(Card(Color.BLACK, 2, [0, 0, 4, 2, 1], 2))
        tier2_deck.append(Card(Color.BLACK, 2, [5, 0, 0, 0, 0], 2))
        tier2_deck.append(Card(Color.BLACK, 2, [0, 0, 5, 3, 0], 2))
        tier2_deck.append(Card(Color.BLACK, 3, [0, 6, 0, 0, 0], 2))

        tier2_deck.append(Card(Color.BLUE, 1, [0, 0, 2, 3, 2], 2))
        tier2_deck.append(Card(Color.BLUE, 1, [0, 3, 3, 0, 2], 2))
        tier2_deck.append(Card(Color.BLUE, 2, [5, 0, 0, 0, 3], 2))
        tier2_deck.append(Card(Color.BLUE, 2, [0, 0, 0, 0, 5], 2))
        tier2_deck.append(Card(Color.BLUE, 2, [2, 4, 0, 1, 0], 2))
        tier2_deck.append(Card(Color.BLUE, 3, [0, 0, 0, 0, 6], 2))

        tier2_deck.append(Card(Color.GREEN, 1, [3, 0, 2, 3, 0], 2))
        tier2_deck.append(Card(Color.GREEN, 1, [2, 2, 0, 0, 3], 2))
        tier2_deck.append(Card(Color.GREEN, 2, [4, 1, 0, 0, 2], 2))
        tier2_deck.append(Card(Color.GREEN, 2, [0, 0, 5, 0, 0], 2))
        tier2_deck.append(Card(Color.GREEN, 2, [0, 0, 3, 0, 5], 2))
        tier2_deck.append(Card(Color.GREEN, 3, [0, 0, 6, 0, 0], 2))

        tier2_deck.append(Card(Color.RED, 1, [0, 3, 0, 2, 3], 2))
        tier2_deck.append(Card(Color.RED, 1, [2, 3, 0, 2, 0], 2))
        tier2_deck.append(Card(Color.RED, 2, [1, 0, 2, 0, 4], 2))
        tier2_deck.append(Card(Color.RED, 2, [3, 5, 0, 0, 0], 2))
        tier2_deck.append(Card(Color.RED, 2, [0, 5, 0, 0, 0], 2))
        tier2_deck.append(Card(Color.RED, 3, [0, 0, 0, 6, 0], 2))

        tier2_deck.append(Card(Color.WHITE, 1, [0, 2, 3, 2, 0], 2))
        tier2_deck.append(Card(Color.WHITE, 1, [2, 0, 0, 3, 3], 2))
        tier2_deck.append(Card(Color.WHITE, 2, [0, 2, 1, 4, 0], 2))
        tier2_deck.append(Card(Color.WHITE, 2, [0, 0, 0, 5, 0], 2))
        tier2_deck.append(Card(Color.WHITE, 2, [0, 3, 0, 5, 0], 2))
        tier2_deck.append(Card(Color.WHITE, 3, [6, 0, 0, 0, 0], 2))

        return tier2_deck
    """Generates a shuffled deck of tier 3 cards."""
    def gen_tier3(self):
        tier3_deck = []
        tier3_deck.append(Card(Color.BLACK, 3, [3, 0, 5, 3, 3], 3))
        tier3_deck.append(Card(Color.BLACK, 4, [0, 0, 0, 7, 0], 3))
        tier3_deck.append(Card(Color.BLACK, 4, [0, 3, 3, 6, 0], 3))
        tier3_deck.append(Card(Color.BLACK, 5, [0, 3, 0, 7, 0], 3))

        tier3_deck.append(Card(Color.BLUE, 3, [3, 5, 3, 3, 0], 3))
        tier3_deck.append(Card(Color.BLUE, 4, [7, 0, 0, 0, 0], 3))
        tier3_deck.append(Card(Color.BLUE, 4, [6, 3, 0, 0, 3], 3))
        tier3_deck.append(Card(Color.BLUE, 5, [7, 0, 0, 0, 3], 3))

        tier3_deck.append(Card(Color.GREEN, 3, [5, 3, 0, 3, 3], 3))
        tier3_deck.append(Card(Color.GREEN, 4, [3, 0, 3, 0, 6], 3))
        tier3_deck.append(Card(Color.GREEN, 4, [0, 0, 0, 0, 7], 3))
        tier3_deck.append(Card(Color.GREEN, 5, [0, 0, 3, 0, 7], 3))

        tier3_deck.append(Card(Color.RED, 3, [3, 3, 3, 0, 5], 3))
        tier3_deck.append(Card(Color.RED, 4, [0, 0, 7, 0, 0], 3))
        tier3_deck.append(Card(Color.RED, 4, [0, 0, 6, 3, 3], 3))
        tier3_deck.append(Card(Color.RED, 5, [0, 0, 7, 3, 0], 3))

        tier3_deck.append(Card(Color.WHITE, 3, [0, 3, 3, 5, 3], 3))
        tier3_deck.append(Card(Color.WHITE, 4, [0, 7, 0, 0, 0], 3))
        tier3_deck.append(Card(Color.WHITE, 4, [3, 6, 0, 3, 0], 3))
        tier3_deck.append(Card(Color.WHITE, 5, [3, 7, 0, 0, 0], 3))
        return tier3_deck

    def gen_nobles(self):
        nobles = []
        nobles.append(GemDict([0,0,4,4,0]))
        nobles.append(GemDict([3,3,0,3,0]))
        nobles.append(GemDict([4,0,0,0,4]))
        nobles.append(GemDict([4,4,0,0,0]))
        nobles.append(GemDict([0,0,4,0,4]))
        nobles.append(GemDict([0,0,3,3,3]))
        nobles.append(GemDict([3,0,3,0,3]))
        nobles.append(GemDict([0,4,0,4,0]))
        nobles.append(GemDict([3,3,0,0,3]))
        nobles.append(GemDict([0,3,3,3,0]))
        return nobles
    """
    Returns whether the game has finished.
    """
    def running(self):
        return self._running
    """
    Set _running to False.
    """
    def endGame(self):
        self._running = False
    """
    Returns the turn count for the game.
    """
    def get_turn_count(self):
        return self._turnCount
    """
    Returns a list of Player objects, in turn order, corresponding to each player in the game.
    """
    def get_players(self):
        return self._players
    """
    Returns a Player object of the current player.
    """
    def get_current_player(self):
        return self._current_player

    """
    Returns the remaining tier1 cards left in the deck as a list of Card objects.
    """
    def get_tier1_deck(self):
        return self._tier1_deck

    """
    Returns the remaining tier2 cards left in the deck as a list of Card objects.
    """
    def get_tier2_deck(self):
        return self._tier2_deck

    """
    Returns the remaining tier3 cards left in the deck as a list of Card objects.
    """
    def get_tier3_deck(self):
        return self._tier3_deck

    """
    Returns the remaining tier1 cards currently on the board as a list of Card objects.
    """
    def get_tier1(self):
        return self._tier1

    """
    Returns the remaining tier2 cards currently on the board as a list of Card objects.
    """
    def get_tier2(self):
        return self._tier2

    """
    Returns the tier3 cards currently on the board as a list of Card objects.
    """
    def get_tier3(self):
        return self._tier3

    """
    Returns the gems available to take as a GemDict object.
    """
    def get_avail_gems(self):
        return self._available_gems

    """
    Returns int of the number of gold gems still available.
    """
    def get_num_gold(self):
        return self._gold

    """
    Returns the nobles remaining on the board.
    """
    def get_nobles(self):
        return self._nobles

    def getNoble(self, noble):
        return self._nobles[noble]

    def getPlayerReserved(self, player, card):
        self._players[player].get_reserved()[card].reserve(card)
        return self._players[player].get_reserved()[card]

    """
    Returns the index of the current player in the order the players are in.
    """
    def current_player_index(self):
        return self._players.index(self._current_player)

    """
    Changes the current player to the next player in the player list.
    """
    def next_player(self):
        current = self.current_player_index()
        self._current_player = self._players[current+1-len(self._players)]
        self._turnCount += 1

    """
    Removes one card from the desired tier deck and adds it to the tier cards on the board.

    tier:   Int. The tier number whose deck a card should be removed from. Also the tier number
            on the board that the card will be added to.
            If tier = 1, remove from tier 1 deck. If tier = 2, remove from tier 2 deck.
            If tier = 3, remove from tier 3 deck. Otherwise the method raises ValueError.

    This method is called after a player purchases a card to replenish the cards on the board.
    If there are no cards remaining in the desired tier deck, no card is added to the board.
    """
    def draw_from_deck(self, tier):
        if tier == 1:
            if len(self._tier1_deck)!=0:
                new_card = self._tier1_deck.pop()
                self._tier1.append(new_card)
        elif tier == 2:
            if len(self._tier2_deck)!=0:
                new_card = self._tier2_deck.pop()
                self._tier2.append(new_card)
        elif tier == 3:
            if len(self._tier3_deck)!=0:
                new_card = self._tier3_deck.pop()
                self._tier3.append(new_card)
        else:
            raise ValueError

    """
    Helper function that removes and returns the top card ot a specified tier deck.
    """
    def reserve_from_deck(self, tier):
        if tier == 1:
            if len(self._tier1_deck)!= 0:
               new_card = self._tier1_deck.pop()
               return new_card
            else:
               return None
        elif tier == 2:
            if len(self._tier2_deck)!= 0:
                new_card = self._tier2_deck.pop()
                return new_card
            else:
                return None
        elif tier == 3:
            if len(self._tier3_deck)!= 0:
                new_card = self._tier3_deck.pop()
                return new_card
            else:
                return None
        else:
            raise ValueError

    """
    Removes a card from the cards available to purchase from a specific tier.

    tier:   Int. The tier from which the card should be removed.
            If tier = 1, remove from tier 1. If tier = 2, remove from tier 2. If tier = 3,
            remove from tier 3. Otherwise the method raises ValueError.

    card:   Card object. The card that should be removed from the board.

    """
    def remove_tier_card(self, tier, card):
        if tier == 1:
            del self._tier1[card]
        elif tier == 2:
            del self._tier2[card]
        elif tier == 3:
            del self._tier3[card]
        else:
            raise ValueError

    """
    Removes gems from the game's available gems.

    gem_lst:    list of ints representing the number of gems of each color to remove. The order of the list is
                [red, blue, green, white, black]. For example, [0, 1, 3, 0, 2] would representing removing
                0 red gems, 1 blue gem, 3 green gems, 0 white gems, and 2 black gems.
    """
    def remove_gems(self, gem_lst):
        self._available_gems.remove(Color.WHITE, gem_lst[0])
        self._available_gems.remove(Color.BLACK, gem_lst[1])
        self._available_gems.remove(Color.GREEN, gem_lst[2])
        self._available_gems.remove(Color.RED, gem_lst[3])
        self._available_gems.remove(Color.BLUE, gem_lst[4])

    """
    Adds gems to the game's available gems.

    gem_lst:    list of ints representing the number of gems of each color to add. The order of the list is
                [red, blue, green, white, black]. For example, [0, 1, 3, 0, 2] would representing adding
                0 red gems, 1 blue gem, 3 green gems, 0 white gems, and 2 black gems.
    """
    def add_gems(self, gem_lst):
        self._available_gems.add(Color.WHITE, gem_lst[0])
        self._available_gems.add(Color.BLACK, gem_lst[1])
        self._available_gems.add(Color.GREEN, gem_lst[2])
        self._available_gems.add(Color.RED, gem_lst[3])
        self._available_gems.add(Color.BLUE, gem_lst[4])

    """
    Removes the given noble from the game's available nobles.

    noble: GemDict object. The noble to be removed from the board.
    """
    def remove_noble(self, noble):
        self._nobles.remove(noble)

    """
    Returns the card for the tier and card index specified, or None if it DNE.
    """
    def getTierCard(self, tier, card):
        c = None
        if (tier == 1):
            c = self._tier1[card]
        elif (tier == 2):
            c = self._tier2[card]
        elif (tier == 3):
            c = self._tier3[card]
        return c

    """
    Decrements the game's total number of available gold gems by 1.
    """
    def decr_gold(self):
        self._gold -= 1

    """
    Increments the game's total number of available gold gems by 1.
    """
    def incr_gold(self):
        self._gold += 1

    """
    Returns True if the current player is discarding cards.
    """
    def get_discarding(self):
        return self._discarding

    """
    Sets _discarding to dis.
    """
    def set_discarding(self, dis):
        self._discarding = dis

    """
    Returns None if nobody has won; else, a player's name.
    """
    def get_firstWinner(self):
        return self._firstWinner

    """
    Sets _firstWinner to fw.
    """
    def set_firstWinner(self, fw):
        self._firstWinner = fw

    """
    Sets _winners to empty list of winners.
    """
    def reset_winners(self):
        self._winners = []

    """
    Returns list of winners.
    """
    def get_winners(self):
        return self._winners

    """
    Returns winners message.
    """
    def get_winners_text(self):
        text = ""
        if (len(self._winners) == 0):
            return "Nobody won... [1000 Turn Limit]"
        if (len(self._winners) == 1):
            text = "" + self._winners[0] + " Wins!"
        elif (len(self._winners) > 1):
            for w in self._winners[:-2]:
                text += w + ", "
            text += self._winners[-2] + " and " + self._winners[-1] + " Win!"
        else:
            return ""
        return text

    """
    Add w to _winners.
    """
    def add_winner(self, w):
        self._winners.append(w)

    """
    Adds a player to the game's player list.

    player:     Player object. The player to be added to the player list. The player is added to
                the end of the list (will have the last turn).
    """
    def add_player(self, player):
        self._players.append(player)

    def can_buy_card(self, t, c, r):
        card = None
        if (r):
            card = self._players[t].get_reserved()[c]
        else:
            if (t == 1):
                card = self._tier1[c]
            elif (t == 2):
                card = self._tier2[c]
            elif (t == 3):
                card = self._tier3[c]

        difference = 0
        player = self._current_player
        keys = card.get_cost().get_data()
        discounts = player.get_discounts().get_data()
        adjusted_cost = [0] * 5

        #Check if user has enough gems to buy card (adjusted with discounts)
        for color in keys:
            card_cost = max(keys[color] - discounts[color], 0)
            adjusted_cost[color.value] = card_cost
            if card_cost < 0:
                card_cost = 0
            player_cash = player.get_gems().get_data()[color]
            if card_cost > player_cash:
                difference += card_cost - player_cash
        if difference > player.get_gold():
            return False
        return True

    def toList(self):
        p = []
        i = 0
        for pl in self._players:
            rsv = []
            k = 0
            for r in pl.get_reserved():
                res = [0] * 5
                res[r.get_color().value] = 1
                rsv += [r.get_points()] + res + r.get_cost().data_gui()
                k += 1
            for j in range(3-k):
                rsv += [0] * 11
            p += rsv + [22-pl.get_points()] + pl.get_colors() + pl.get_discounts().data_gui()
            i += 1
        for j in range(4-i):
            p += [0] * 45

        g = [self._gold] + self._available_gems.data_gui()

        td = [len(self._tier1_deck)] + [len(self._tier2_deck)] + [len(self._tier3_deck)]

        nb = []
        i = 0
        for n in self._nobles:
            nb += n.data_gui()
            i += 1
        for j in range(5-i):
            nb += [0] * 5

        t1 = []
        i = 0
        for t in self._tier1:
            res = [0] * 5
            res[t.get_color().value] = 1
            t1 += [t.get_points()] + res + t.get_cost().data_gui()
            i += 1
        for j in range(4-i):
            t1 += [0] * 11

        t2 = []
        i = 0
        for t in self._tier2:
            res = [0] * 5
            res[t.get_color().value] = 1
            t2 += [t.get_points()] + res + t.get_cost().data_gui()
            i += 1
        for j in range(4-i):
            t2 += [0] * 11

        t3 = []
        i = 0
        for t in self._tier3:
            res = [0] * 5
            res[t.get_color().value] = 1
            t3 += [t.get_points()] + res + t.get_cost().data_gui()
            i += 1
        for j in range(4-i):
            t3 += [0] * 11

        return (p + g + td + nb + t1 + t2 + t3)

    def incr_interactions(self):
        self._interactions += 1

    def get_interactions(self):
        return self._interactions

    def input_info(self, stepsLeft):
        pturn = [0] * 4
        pind = self.current_player_index()
        pturn[pind] = 1
        return (self.toList() + self.possible_moves(pind) +
            pturn + [stepsLeft])

    """
    Returns all the possible moves in this state.
    Indexes [0...4] for whether it's possible to take at least one of the gem.
    Indexes [5...9] for whether it's possible to take at two of the gem.
    Indexes [10...24] for whether a card can be reserved; R-to-L, Tier 1 -> 3.
    Indexes [25...27] for whether a reserved card can be bought.
    Indexes [28...39] for whether a tier card can be bought; Tier 1 -> 3.
    Indexes [40...45] for whether the player can discard one of their colors.
    """
    def possible_moves(self, cpind):

        gm = self.get_avail_gems().data_gui()
        cp = self.get_players()[cpind]

        canTake1 = [0] * 5
        canTake2 = [0] * 5
        canReserve = [0] * 15
        canBuy = [0] * 15
        canDiscard = [0] * 6
        if (not self.get_discarding()):
            canTake1 = list(map(lambda n : int(n>0), gm))

            canTake2 = list(map(lambda n : int(n>3), gm))

            if (len(cp.get_reserved()) < 3):
                canReserve[0] = int(len(self.get_tier1_deck()) > 0)
                canReserve[5] = int(len(self.get_tier2_deck()) > 0)
                canReserve[10] = int(len(self.get_tier3_deck()) > 0)
                for i in range(len(self.get_tier1())):
                    canReserve[i+1] = 1
                for i in range(len(self.get_tier2())):
                    canReserve[i+6] = 1
                for i in range(len(self.get_tier3())):
                    canReserve[i+11] = 1

            for r in range(len(cp.get_reserved())):
                canBuy[r] = int(self.can_buy_card(cpind, r, True))
            for t1 in range(len(self.get_tier1())):
                canBuy[t1+3] = int(self.can_buy_card(1, t1, False))
            for t2 in range(len(self.get_tier2())):
                canBuy[t2+7] = int(self.can_buy_card(2, t2, False))
            for t3 in range(len(self.get_tier3())):
                canBuy[t3+11] = int(self.can_buy_card(3, t3, False))

        else:
            canDiscard = list(map(lambda n : int(n>0), cp.get_colors()))

        return (canTake1 + canTake2 + canReserve + canBuy + canDiscard)

"""
    A Card will contain:

    color: a Color indicating the discount the card gives
    points: an int indicating the number of points the card gives
    cost: A GemDict indicating the cost of the card
"""
class Card:
    """
    cost_lst: a list of the number of each color of gem that the card costs [red, blue, green, white, black].
    For example, [0, 1, 3, 0, 2] would representing the card costing
    0 red gems, 1 blue gem, 3 green gems, 0 white gems, and 2 black gems.
    """
    def __init__(self, color, points, cost_lst, tier):
        self._color = color
        self._points = points
        self._cost = GemDict(cost_lst)
        self._tier = tier
        self._reserved = [False, None]

    """Returns the Color indicated the discount the card gives."""
    def get_color(self):
        return self._color

    """Returns the point value of the card."""
    def get_points(self):
        return self._points

    """Returns the cost of the card as a Gem Dict."""
    def get_cost(self):
        return self._cost

    """Returns the tier of the card. One of [1,2,3]."""
    def get_tier(self):
        return self._tier

    """Returns boolean for whether the card is reserved."""
    def reserved(self):
        return self._reserved

    """Returns [True, index], where index is the index in its reserve pile."""
    def reserve(self, index):
        self._reserved = [True, index]

    def __str__(self):
        return ( str(self._points) + "       " + str(self._color) +
            "\n\n\n" + "W|K|E|R|B\n" +
            str(self.get_cost()) )

"""
    A Player will contain:

    name: a string representing the name of the player. ex. HUMAN 0 for human player, AI 0 for AI player.
    discounts: A GemDict with Color keys and int values. Represents the number of cards of each color a player has.
    gems: A GemDict with with Color keys and int values. Represents the number of gems of each color a player has.
    gold: an int representing the number of gold gems a player has
    reserved: A list of Card objects that the player has reserved
    points: The number of points a player has
    player_type: A PlayerType indicating whether the player is AI or human
    num_cards: An int indicating the number of cards a player has bought (used for end-game tiebreaker).
    num_moves: Number of moves the player has taken
    gems_taken: list of gem colors in the order that the player took them (only used for AI)
"""
class Player:
    """
    Sets up an empty Player object with each of the attributes described above.
    """
    def __init__(self, player_type, name):
        self._name = name
        self._discounts = GemDict([0,0,0,0,0])
        self._gems = GemDict([0,0,0,0,0])
        self._gold = 0
        self._reserved = []
        self._points = 0
        self._player_type = player_type
        self._num_cards = 0
        self._num_moves = 0
        self._gems_taken = []
        self._move_dict = {'take_two' : 0, 'take_three' : 0, 'buy' : 0, 'buy_noble' : 0, 'reserve' : 0, 'reserve_top': 0, 'discard' : 0}

    def get_move_dict(self):
        return self._move_dict

    def add_move_dict(self, move):
        self._move_dict[move] += 1

    """
    Returns the type of this player.
    """
    def get_player_type(self):
        return self._player_type

    """
    Returns the name of the current player.
    """
    def get_name(self):
        return self._name

    """
    Returns the player's discounts as a GemDict which represents the number of cards of each color a player has.
    """
    def get_discounts(self):
        return self._discounts

    """
    Returns the number of gems of each color the player has.
    """
    def get_gems(self):
        return self._gems

    """
    Returns the number of gold gems a player has.
    """
    def get_gold(self):
        return self._gold

    """
    Returns a list of Card objects the player has reserved.
    """
    def get_reserved(self):
        return self._reserved

    """
    Returns the number of points the player has.
    """
    def get_points(self):
        return self._points

    """
    Returns number of cards the player has bought.
    """
    def get_purchased(self):
        return self._num_cards

    """
    Returns number of cards the player has bought.
    """
    def gemGoldAmt(self):
        return self._gems.total_gems() + self._gold

    """
    Returns the list of all the colors the player owns.
    """
    def get_colors(self):
        return [self._gold] + self._gems.data_gui()

    """
    Increments the player's number of gold gems by 1.
    """
    def incr_gold(self):
        self._gold += 1

    """
    Decrements the player's number of gold gems by 1.
    """
    def decr_gold(self):
        self._gold -= 1

    """
    Adds card to the player's list of reserved cards.
    """
    def add_reserved(self, card):
        self._reserved.append(card)
        card.reserve(self._reserved.index(card))

    """
    Removes card from the player's list of reserved cards.
    """
    def remove_reserved(self, card):
        del self._reserved[card]

    """
    Adds the color to the player's discounts.
    """
    def set_discount(self, color):
        self._discounts.add(color, 1)

    """
    Adds the given number of gems in color_lst to the player's total gems. color_lst is a list
    of ints representing the number of gems of each color to be added. The order of color_lst is
    [red, blue, green, white, black]. For example, [0, 1, 3, 0, 2] would representing adding
    0 red gems, 1 blue gem, 3 green gems, 0 white gems, and 2 black gems.
    """
    def add_gems(self, color_lst):
        self._gems.add(Color.WHITE, color_lst[0])
        self._gems.add(Color.BLACK, color_lst[1])
        self._gems.add(Color.GREEN, color_lst[2])
        self._gems.add(Color.RED, color_lst[3])
        self._gems.add(Color.BLUE, color_lst[4])

    """
    Removes the given number of gems in color_lst from the player's total gems. color_lst is a list
    of ints representing the number of gems of each color to be added. The order of color_lst is
    [red, blue, green, white, black]. For example, [0, 1, 3, 0, 2] would representing removing
    0 red gems, 1 blue gem, 3 green gems, 0 white gems, and 2 black gems.
    """
    def remove_gems(self, color_lst):
        self._gems.remove(Color.WHITE, color_lst[0])
        self._gems.remove(Color.BLACK, color_lst[1])
        self._gems.remove(Color.GREEN, color_lst[2])
        self._gems.remove(Color.RED, color_lst[3])
        self._gems.remove(Color.BLUE, color_lst[4])

    def ai_remove_gems(self, color_lst):
        if self._player_type == 2: #use only for AI
            for i in range(len(color_lst)):
                for j in range(color_lst[i]):
                    if i == 0:
                        self._gems_taken.remove(Color.WHITE)
                    elif i == 1:
                        self._gems_taken.remove(Color.BLACK)
                    elif i == 2:
                        self._gems_taken.remove(Color.GREEN)
                    elif i == 3:
                        self._gems_taken.remove(Color.RED)
                    elif i == 4:
                        self._gems_taken.remove(Color.BLUE)

    """
    Returns list of gems that the player took in the order they took them
    """
    def get_gems_ordered(self):
        return self._gems_taken

    """
    Takes a list of Colors and adds them to the front of the list of gems that have
    already been taken
    """
    def ai_add_gems(self, color_lst):
        color_lst + self._gems_taken

    """
    Adds the given number of points to the player's point total.
    """
    def set_points(self, num_points):
        self._points += num_points

    """
    Increments the player's number of purchased cards by 1.
    """
    def incr_card_total(self):
        self._num_cards += 1

    """
    Returns the player's number of moves so far, not including this current one.
    """
    def get_num_moves(self):
        return self._num_moves

    """
    Increments the player's number of moves by 1.
    """
    def incr_num_moves(self):
        self._num_moves += 1

    def __str__(self):
        gd = self._gold
        gm = self._gems
        dc = self._discounts
        return (
            str(self._points) + "               " + self._name + "\n\n\n" +
            "          G   " + "W|K|E|R|B\n" +
            "GEMS      " + str(gd) + "   " + str(gm) + "\n" +
            "CARDS         " + str(dc) + "\n" +
            "_______________________\n" +
            "TOTAL     " + str(gd) + "   " + gm.addGD(dc)
        )


import time

"""
By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13.
What is the 10 001st prime number?
"""

# Check if a number is prime

def is_prime(n):
    if n <= 1:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False
    for i in range(3, int(n / 2) + 1, 2):
        if n % i == 0:
            return False
    return True

def main():
    start = time.time()

    n = 10001

    prime_number = 13
    count = 6

    while count < n:
        prime_number += 2
        if is_prime(prime_number):
            count += 1

    print("10001st prime number: " + str(prime_number))

    finish = time.time()

    print("Execution time : " + str(finish - start))

if __name__ == "__main__":
    main()


#!/usr/bin/env python

import csv
import gzip
import json
import os
import re
import sys
import pathlib
import sqlite3

from shapely.geometry import Polygon
from sqlite3 import Error

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(SCRIPT_DIR, os.path.join('..', '..', 'outputs', 'butte'))
GEOJSON_FILE = os.path.join(DATA_DIR, 'butte_parcels.geojson')

sql_create_parcel_table = """
CREATE TABLE IF NOT EXISTS parcels (
    apn TEXT PRIMARY KEY,                        -- county specific id for parcel lookup
    location TEXT NOT NULL,                      -- the address or geographical description of parcel
    zipcode TEXT NOT NULL,                       -- the zipcode of the parcel
    geo_lat NUMERIC,                             -- the latitude of the property centroid
    geo_lon NUMERIC,                             -- the longitude of the property centroid
    use_code TEXT,                               -- property use code
    lot_size_sqft NUMERIC,                       -- the lot size in square feet
    building_size_sqft NUMERIC,                  -- the building size in square feet
    building_bed_count NUMERIC,                  -- the number of bedrooms in building
    building_bath_count NUMERIC,                 -- the number of bathrooms in building
    building_stories_count NUMERIC,              -- the number of stories in building
    building_units_count NUMERIC,                -- the number of units in building
    building_age NUMERIC,                        -- the year building is built
    tax_value NUMERIC                            -- the appicable assessed tax
);
"""
sql_select_apn_from_parsed = """
SELECT EXISTS(SELECT 1 FROM parcels WHERE apn = ?);
"""
sql_insert_parcel_from_parsed = """
INSERT INTO parcels (apn, location, zipcode, geo_lat, geo_lon, use_code, lot_size_sqft, building_size_sqft, building_bed_count, building_bath_count, building_stories_count, building_units_count, building_age)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
"""

flatten=lambda l: sum(map(flatten, l),[]) if isinstance(l,list) else [l]

def run():
    conn = None
    try:
        db_file = os.path.join(DATA_DIR, 'butte_parcel.db')
        print('Opening {}'.format(db_file))

        conn = sqlite3.connect(db_file)
        c = conn.cursor()
        c.execute(sql_create_parcel_table)

        with open(GEOJSON_FILE) as f_in:
            count = 0
            for line in f_in:
                count += 1
                if count < 6:
                    # Skip geojson cruft left by conversion
                    continue

                try:
                    json_to_parse = line.strip()
                    if json_to_parse.endswith(','):
                        json_to_parse = json_to_parse[:-1]
                    record = json.loads(json_to_parse)
                except:
                    print('-> could not parse JSON on line %d' % (count,))
                    continue

                props = record['properties']
                formatted_apn = props['SiteAPN']
                if not formatted_apn:
                    continue
                if not record['geometry'] or not record['geometry']['coordinates']:
                    print('-> skip')
                    continue

                # There is definitely a more correct way to do this.
                flat_coords = [[xyz[0], xyz[1]] for coords in record['geometry']['coordinates'] for xyz in coords]
                flat_coords = flatten(flat_coords)
                coords = zip(flat_coords[0::2], flat_coords[1::2])

                try:
                    centroid = list(Polygon(coords).centroid.coords)[0]
                except:
                    print('-> could not find centroid')
                    continue

                # check if id already exists
                c.execute(sql_select_apn_from_parsed, (formatted_apn,))
                (existsCheck,) = c.fetchone()
                if existsCheck > 0:
                    continue
                if not props['SiteZip']:
                    continue

                insert_record = (
                    formatted_apn,
                    '{}\n{}'.format(props['SiteAddr'], props['SiteCity']),
                    props['SiteZip'],
                    centroid[1],
                    centroid[0],
                    props['UseCode'],
                    props['LotSizeSF'],
                    props['BuildingSF'],
                    props['Bedrooms'],
                    props['Bathrooms'],
                    props['Stories'],
                    props['Units'],
                    props['YrBuilt']
                )
                c.execute(sql_insert_parcel_from_parsed, insert_record)
            conn.commit()
            print("inserts: {}".format(c.lastrowid))
    except Error as e:
        print(e)
    finally:
        if conn:
            conn.close()

if __name__ == '__main__':
    run()


from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals

import numpy as np
import sklearn.metrics as metrics
import seaborn as sns

from mosaic import features
from mosaic import contexts
from mosaic import image_io
from mosaic import plots
from mosaic import data_utils
from mosaic.grid.image_grid import images_to_grid

__all__ = ['scatter_grid']

def images_to_scatter_grid(images, x_var, y_var, padding=None, **kwargs):
    """Creates a grid plot from a scatter plot.

    Parameters
    ----------
    images : list of length [n_samples,]
        A List of PIL Image objects. All images must be
        the same shape NxWx3.

    x_var : np.array of shape [n_samples,]
        The x-coordinate in euclidean space.

    y_var : np.array of shape [n_samples,]
        The y-coordinate in euclidean space.

    padding : int, optional
        The padding between images in the grid.

    Returns
    -------
    A properly shaped width x height x 3 PIL Image.
    """
    # scale the variables between 0-1 (subtract off min?)
    features.minmax_scale(x_var)
    features.minmax_scale(y_var)
    xy = np.c_[x_var, y_var]

    # make a grid of evenly spaced points on the grid.
    # The grid is of size sqrt(n_samples) x sqrt(n_samples)
    grid_size = int(np.ceil(np.sqrt(len(images))))
    grid_1d = np.linspace(0, 1, grid_size)
    grid_2d = np.dstack(np.meshgrid(grid_1d, grid_1d)).reshape(-1, 2)

    # distances between the evenly spaced grid and the points
    dist = metrics.euclidean_distances(grid_2d, xy)

    # determine order based on nearest neighbors
    image_order = []
    for i in range(grid_2d.shape[0]):
        index = np.argmin(dist[i, :])
        image_order.append(index)
        dist[:, index] = np.inf  # set to inf so we don't pick this point again
    images = [images[index] for index in image_order]

    grid = images_to_grid(images, padding=padding)
    return plots.pillow_to_matplotlib(grid, **kwargs)

def scatter_grid(x, y,
                  images=None,
                  data=None,
                  hue=None,
                  image_dir='',
                  image_size=None,
                  padding=None,
                  n_jobs=1,
                  **kwargs):
    """Draw a plot ordering images in a regularly spaced 2-d grid
    based on their distance in the x-y plane. The distance between
    points is assumed to be euclidean.

    Parameters
    ----------
    x, y : str or array-like
        Data or names of variables in `data`.
        These variables correspond to the x-y coordinates
        in the euclidean space.

    images : str or array-like
        Image arrays or names of the column pointing to the
        image paths within `data`.

    data : pd.DataFrame
        Pandas dataframe holding the dataset.

    hue : str or array-like
        Data or the name of the variable to use to color
        the individual images on the grid.

    image_dir : str (default='')
        The location of the image files on disk.

    image_size : int
        The size of each image in the scatter plot.

    padding : int, optional
        The padding between images in the grid.

    n_jobs : int (default=1)
        The number of parallel workers to use for loading
        the image files.

    Returns
    -------
    A properly shaped NxWx3 image with any necessary padding.

    Examples
    --------

    Create a grid plot with hue labels.

    .. plot:: ../examples/scatter_grid.py
    """
    x_var = data_utils.get_variable(data, x)
    y_var = data_utils.get_variable(data, y)

    # TODO (seaborn is only required for a color palette. Remove this)
    if hue is not None:
        images = data_utils.get_images(
            data, images,
            image_dir=image_dir,
            as_image=False,
            image_size=image_size,
            n_jobs=n_jobs)

        hue = data_utils.get_variable(data, hue)
        values, value_map = np.unique(hue, return_inverse=True)
        palette = sns.husl_palette(len(values))
        images = [features.color_image(img, hue=palette[val]) for
                  img, val in zip(images, value_map)]
        images = [image_io.to_pillow_image(img) for img in images]
    else:
        # load images
        images = data_utils.get_images(
            data, images,
            image_dir=image_dir,
            as_image=True,
            image_size=image_size,
            n_jobs=n_jobs)

    return images_to_scatter_grid(images, x_var, y_var, padding=padding, **kwargs)


import pandas as pd
import numpy as np
# from sklearn.linear_model import LogisticRegression
# from omegaconf import DictConfig, OmegaConf
from loguru import logger
import joblib
import click

# from dataclasses import dataclass
# from hydra.core.config_store import ConfigStore

# from sklearn.pipeline import Pipeline
# from src.features.transformers import SqrTransformer
# from omegaconf import DictConfig, OmegaConf, MISSING
# from src.models.train_model import Config, RF, LogReg, ModelType

@click.command()
@click.option(
    "--model",
    help="Pretrained model path.",
    type=click.Path(exists=True),
    required=True,
)
@click.option(
    "--dataset",
    help="Input dataset in csv format.",
    type=click.Path(exists=True),
    required=True,
)
@click.option(
    "--output",
    help="Output file with predicted labels.",
    type=click.Path(),
    required=True,
)
def main(model: str, dataset: str, output: str) -> None:
    logger.info('Reading data')

    df = pd.read_csv(dataset)

    if 'condition' in df.columns:
        df = df.drop(['condition'], axis=1)

    #     with open(model, 'rb') as file:
    #         model = pickle.load(file)
    #         logger.info('Model loaded')

    model = joblib.load(model)

    y_pred = model.predict(df)

    logger.info('Saving results')

    np.savetxt(output, y_pred, delimiter=",")

if __name__ == "__main__":
    main()


from elasticsearch import Elasticsearch
from search import search_user_query

class ESClient:
  def __init__(self):
    self.es = Elasticsearch("http://localhost:9200")

  def extract_songs(self, resp):
    songs = []
    hits = resp["hits"]["hits"]
    for i in range(len(hits)):
      songs.append(hits[i]["_source"])
    return songs

  def get_all_songs(self):
    resp = self.es.search(index="sinhala-songs-corpus", body={"query": {"match_all": {}}})
    return self.extract_songs(resp)

  def advanced_search(self, req_body):
    filled_keys = {k: v for k, v in req_body.items() if v}
    must_list = []
    for k in filled_keys.keys():
      must_list.append({ "match" : { k+".case_insensitive_and_inflections" : req_body[k] } })
    resp = self.es.search(index="sinhala-songs-corpus",body={"query": {"bool": {"must": must_list}}})
    return self.extract_songs(resp)

  def get_logical_combinations(self, req_body):
    resp = None
    if req_body["operation"] == "and":
      resp = self.es.search(index="sinhala-songs-corpus",body={
        "query": {
          "bool": {
            "must": [
              { "match" : { req_body["key1"]+".case_insensitive_and_inflections" : req_body["value1"] } },
              { "match" : { req_body["key2"]+".case_insensitive_and_inflections" : req_body["value2"] } }
            ]
          }
        }
      })
    elif req_body["operation"] == "or":
      resp = self.es.search(index="sinhala-songs-corpus",body={
        "query": {
          "bool": {
            "should": [
              { "match" : { req_body["key1"]+".case_insensitive_and_inflections" : req_body["value1"] } },
              { "match" : { req_body["key2"]+".case_insensitive_and_inflections" : req_body["value2"] } }
            ]
          }
        }
      })
    elif req_body["operation"] == "not":
      resp = self.es.search(index="sinhala-songs-corpus",body={
        "query": {
          "bool": {
            "must" : {
              "match" : { req_body["key1"]+".case_insensitive_and_inflections" : req_body["value1"] }
            },
            "must_not" : {
              "match" : { req_body["key2"]+".case_insensitive_and_inflections" : req_body["value2"] }
            }
          }
        }
      })
    return self.extract_songs(resp)

  def regular_search(self, req_body):
    resp = search_user_query(req_body["query"], self.es)
    return self.extract_songs(resp)


import datetime as dt

f = open("def.dat", "r")

CLAIM_NUM = int(f.readline())
HST_RATE = float(f.readline)
CURR_DATE = dt.datetime.now()

f.close()

while True:
    emp_name = input("Employee name: ")
    emp_num = input("Employee number: ")
    location = input("Location: ")
    start_date = "2023-11-06"
    end_date = "2023-11-09"
    num_days = 3
    car_status = "O"

    num_km = 0
    if car_status == "O":
        num_km = 1400

    if num_days <= 3:
        per_diem = num_days * 85.00
    else:
        per_diem = num_days * 100.00

    if car_status == "O":
        mileage = num_km * 0.10
    else:
        mileage = num_days * 56.00

    claim_amt = per_diem + mileage
    taxes = claim_amt * HST_RATE
    claim_total = claim_amt + taxes

    print(f"Claim num: {CLAIM_NUM}")
    print(f"Employee num: {emp_num}")
    print(f"Employee name: {emp_name}")
    print(f"Location: {location}")
    print(f"Claim total: ${claim_total}")

    # write data to a file called claims.dat
    f = open("claims.dat", "a")  # a for append

    f.write(f"{CLAIM_NUM}, ")
    f.write(f"{str(CURR_DATE)}, ")
    f.write(f"{num_days}\n")

    CLAIM_NUM += 1
    print("Claim data written.")

    cont = input("Continue?: ").upper()

    if cont == "N":
        break

f = open("def.dat", "w")
f.write(f"{CLAIM_NUM}\n")
f.write(f"{HST_RATE}\n")
f.close()

print("Thank you for using the claim processing program.")


# -*- coding: utf-8 -*-
from __future__ import absolute_import
import warnings
import unittest
from collections import OrderedDict
from w3lib.form import encode_multipart

class EncodeMultipartTest(unittest.TestCase):

    def test_encode_multipart(self):
        data = {'key': 'value'}
        with warnings.catch_warnings(record=True):
            body, boundary = encode_multipart(data)
        expected_body = (
            '\r\n--{boundary}'
            '\r\nContent-Disposition: form-data; name="key"\r\n'
            '\r\nvalue'
            '\r\n--{boundary}--'
            '\r\n'.format(boundary=boundary).encode('utf8')
        )
        self.assertEqual(body, expected_body)

    def test_encode_multipart_unicode(self):
        data = OrderedDict([
            (u'ключ1', u'значение1'.encode('utf8')),
            (u'ключ2', u'значение2'),
        ])
        with warnings.catch_warnings(record=True):
            body, boundary = encode_multipart(data)
        expected_body = (
            u'\r\n--{boundary}'
            u'\r\nContent-Disposition: form-data; name="ключ1"\r\n'
            u'\r\nзначение1'
            u'\r\n--{boundary}'
            u'\r\nContent-Disposition: form-data; name="ключ2"\r\n'
            u'\r\nзначение2'
            u'\r\n--{boundary}--'
            u'\r\n'.format(boundary=boundary).encode('utf8')
        )
        self.assertEqual(body, expected_body)

    def test_encode_multipart_file(self):
        # this data is not decodable using utf8
        data = {'key': ('file/name', b'\xa1\xa2\xa3\xa4\r\n\r')}
        with warnings.catch_warnings(record=True):
            body, boundary = encode_multipart(data)
        body_lines = [
            b'\r\n--' + boundary.encode('ascii'),
            b'\r\nContent-Disposition: form-data; name="key"; filename="file/name"\r\n',
            b'\r\n\xa1\xa2\xa3\xa4\r\n\r',
            b'\r\n--' + boundary.encode('ascii') + b'--\r\n',
        ]
        expected_body = b''.join(body_lines)
        self.assertEqual(body, expected_body)

    #def test_encode_multipart_int(self):
    #    data = {'key': 123}
    #    body, boundary = encode_multipart2(data)
    #    expected_body = (
    #        '\n--{boundary}'
    #        '\nContent-Disposition: form-data; name="key"\n'
    #        '\n123'
    #        '\n--{boundary}--'
    #        '\n'.format(boundary=boundary)
    #    )
    #    self.assertEqual(body, expected_body)


class Solution(object):
    def majorityElement(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        length = int(len(nums) / 2)
        data = {}
        for ele in nums:
            data[ele] = data.get(ele, 0) + 1
            if data[ele] > length :
                return ele

if __name__ == '__main__':
    nums = [1, 2, 3, 2, 2, 2, 5, 4, 2]
    print( Solution().majorityElement(nums) )

com/study/algorithm/offer/剑指 Offer 39. 数组中出现次数超过一半的数字.py

剑指 Offer 39. 数组中出现次数超过一半的数字.py


from rest_framework import serializers
from core.models import Tag,Ingredient

class TagSerializers(serializers.ModelSerializer):
    '''serializer for the object'''

    class Meta:
        model = Tag
        fields = ('id', 'name')
        read_only_fields = ('id',)

class IngredientSerializer(serializers.ModelSerializer):
    '''serializers for ingredient objects'''

    class Meta:
        model = Ingredient
        fields = ('id','name')
        read_only_fields = ('id',)


prompt="enter your pizza toppings: "
#exercise 7-4 & 7-6 P1 :
#x=""
#while x != "quit":
    #x=input(prompt)
    #if x == "quit":
        #print("thank you for ordering")
    #else:
        #print(x)
#exercise 7-6 P2:
#active = True
#while active:
    #x=input(prompt)
    #if (x == "quit"):
        #print("thank you for ordering")
        #active = False
    #else:
        #print(x)

#exercise 7-6 P3:
while True:
    x=input(prompt)
    if (x=="quit"):
        print("thank you for ordering")
        break
    else:
        print(x)