"""
Objects with validity checking for Google service account registration.
"""
from collections import Mapping
from fence.errors import NotFound

from fence.resources.google.utils import (
    get_registered_service_accounts_with_access,
    get_project_access_from_service_accounts,
    get_users_from_google_members,
    get_service_account_ids_from_google_members,
    is_google_managed_service_account,
)
from fence.resources.google.access_utils import (
    is_valid_service_account_type,
    service_account_has_external_access,
    is_service_account_from_google_project,
    get_google_project_membership,
    get_google_project_parent_org,
    get_google_project_valid_users_and_service_accounts,
    do_all_users_have_access_to_project,
    get_project_from_auth_id,
    get_google_project_number,
    get_service_account_policy,
    remove_white_listed_service_account_ids,
    is_org_whitelisted,
    is_user_member_of_google_project,
    is_user_member_of_all_google_projects,
)
from cirrus.google_cloud import GoogleCloudManager

from cdislogging import get_logger

logger = get_logger(__name__)

class ValidityInfo(Mapping):
    """
    A representation of validity of an item along with
    information about the validity.

    It's a dict-like object which can be evaulated as a boolean value.
    If the info is false-y, the validity of this object will evaluate to False.

    CAVEAT: None is allowed and will not make the validity False, this
            represents a validity check that was NOT run
            (e.g. we have no information)

    This means that you can nest ValidityInfo objects and
    the "valid" status of the parent object will always be updated when adding
    new validity information.
    """

    def __init__(self, default_validity=True):
        # innocent until proven guilty, default validity is True
        self._valid = default_validity
        self._info = {}

    def get(self, key, *args):
        return self._info.get(key, *args)

    def set(self, key, value):
        if not value and value is not None:
            self._valid = False
        self._info.__setitem__(key, value)

    def __setitem__(self, key, value):
        if not value and value is not None:
            self._valid = False
        self._info.__setitem__(key, value)

    def __contains__(self, key):
        return key in self._info

    def __iter__(self):
        for key, value in self._info.items():
            yield key, value

    def __getitem__(self, key):
        return self._info[key]

    def __delitem__(self, key):
        del self._info[key]

    def __len__(self):
        return len(self._info)

    def __bool__(self):
        return self._valid

    def __repr__(self):
        return str(self._info)

    def __str__(self):
        return str(self._info)

    def get_info(self):
        return self._info

class GoogleProjectValidity(ValidityInfo):
    """
    A representation of a Google Project's validity regarding service account
    registration for access to data.

    Example Usage and Information:

        project_validity = (
            GoogleProjectValidity(
                google_project_id='abc123',
                new_service_account='someaccount@something.com',
                new_service_account_access=['projectA', 'ProjectB']
            )
        )
        project_validity.check_validity(early_return=False)

        NOTE: project_validity can be evaluated as if it's a boolean and will
        be False if ANY of the values in the dict-like structure are False.
        In other words, a single invalid item will make the whole project
        invalid.

        An example of the dict-like structure with validity info is:

        {
            'monitor_has_access': True,
            'valid_parent_org': True,
            'valid_membership': True,
            'new_service_account': {
                'account@something.com': {
                    'valid_type': True,
                    'no_external_access': True,
                    'owned_by_project': True,
                    'exists': True
                },
            }
            'service_accounts': {
                'someaccount@something.com': {
                    'valid_type': None,
                    'no_external_access': True,
                    'owned_by_project': True,
                    'exists': True
                },
                'other_service_account_in_project@something.com': {
                    'valid_type': None,
                    'no_external_access': True,
                    'owned_by_project': True,
                    'exists': True
                }
            },
            'access': {
                'ProjectA': {
                    'exists': True,
                    'all_users_have_access': True
                },
                'ProjectB': {
                    'exists': True,
                    'all_users_have_access': True
                },
            }
        }
    """

    def __init__(
        self,
        google_project_id,
        new_service_account=None,
        new_service_account_access=None,
        user_id=None,
        google_cloud_manager=None,
        *args,
        **kwargs
    ):
        """
        Initialize

        Args:
            google_project_id (str): Google project identifier
            new_service_account (str, optional): an additional service account
                identifier (ex: email) to include when checking access. You can
                provide this without actually giving it access to check if
                access will be valid
            new_service_account_access (List(str), optional): List of
                Project.auth_ids to attempt to provide the new service account
                access to
            user_id (None, optional): User requesting validation. ONLY pass this if you
                want to check if the user is a member of this project.
        """
        self.google_project_id = google_project_id
        self.new_service_account = new_service_account
        self.new_service_account_access = new_service_account_access or []
        self.user_id = user_id
        self.google_cloud_manager = google_cloud_manager or GoogleCloudManager(
            google_project_id
        )

        super(GoogleProjectValidity, self).__init__(*args, **kwargs)

        # setup default values for error information, will get updated in
        # check_validity
        self._info["user_has_access"] = None
        self._info["monitor_has_access"] = None
        self._info["valid_parent_org"] = None
        self._info["valid_member_types"] = None
        self._info["members_exist_in_fence"] = None
        self._info["new_service_account"] = {}
        self._info["service_accounts"] = {}
        self._info["access"] = {}

    def check_validity(self, early_return=True, db=None):
        """
        Determine whether or not project is valid for registration. If
        early_return is False, this object will store information about the
        failure.

        Args:
            early_return (bool, optional): Description
        """

        self.google_cloud_manager.open()

        logger.debug(
            "Google Project with id: {}, "
            "new service account requested: {}, project access requested: {}, "
            "user requesting: {}".format(
                self.google_project_id,
                self.new_service_account,
                self.new_service_account_access,
                self.user_id,
            )
        )

        logger.debug(
            "Attempting to get project number "
            "for project id {}".format(self.google_project_id)
        )
        google_project_number = get_google_project_number(
            self.google_project_id, self.google_cloud_manager
        )
        has_access = bool(google_project_number)

        self.set("monitor_has_access", has_access)
        # always early return if we can't access the project
        if not has_access:
            logger.warning(
                "INVALID Fence's Monitoring service account does "
                "NOT have access in project id {}. Monitor needs access to continue "
                "checking project validity. Exiting early and determining invalid.".format(
                    self.google_project_id
                )
            )
            return

        logger.debug(
            "Retrieving project membership "
            "for project id {}".format(self.google_project_id)
        )
        membership = get_google_project_membership(
            self.google_project_id, self.google_cloud_manager
        )
        logger.debug(
            "Project Members: {}".format(
                str(
                    [
                        getattr(member, "email_id", "unknown_member_email")
                        for member in membership
                    ]
                )
            )
        )

        if self.user_id is not None:
            logger.debug(
                "Checking that user requesting, {}, is part of "
                "the Google Project with id {}".format(
                    self.user_id, self.google_project_id
                )
            )
            user_has_access = is_user_member_of_google_project(
                self.user_id, self.google_cloud_manager, membership=membership, db=db
            )
            self.set("user_has_access", user_has_access)
            if not user_has_access:
                # always early return if user isn't a member on the project
                logger.warning(
                    "INVALID User {} "
                    "for project id {}. User is not a member of the project so does not "
                    "have permission on the project.".format(
                        self.user_id, self.google_project_id
                    )
                )
                return

        logger.debug(
            "Retrieving Parent Organization "
            "for project id {} to make sure it's valid".format(self.google_project_id)
        )
        parent_org = get_google_project_parent_org(self.google_cloud_manager)
        valid_parent_org = True

        if parent_org:
            valid_parent_org = is_org_whitelisted(parent_org)

        self.set("valid_parent_org", valid_parent_org)

        if not valid_parent_org:
            logger.warning(
                "INVALID Parent Organization {} "
                "for project id {}. No parent org is allowed unless it's explicitly "
                "whitelisted in cfg.".format(parent_org, self.google_project_id)
            )
            if early_return:
                return

        logger.debug(
            "Determining if other users and service accounts on "
            "project id {} are valid.".format(self.google_project_id)
        )
        user_members = None
        service_account_members = []
        try:
            (
                user_members,
                service_account_members,
            ) = get_google_project_valid_users_and_service_accounts(
                self.google_project_id, self.google_cloud_manager, membership=membership
            )
            self.set("valid_member_types", True)
        except Exception:
            self.set("valid_member_types", False)
            logger.warning(
                "INVALID users and/or service accounts (SAs) on "
                "project id {}.".format(self.google_project_id)
            )
            if early_return:
                return

        logger.debug("Determining if valid users exist in fence.")
        # if we have valid members, we can check if they exist in fence
        users_in_project = None
        if user_members is not None:
            try:
                users_in_project = get_users_from_google_members(user_members, db=db)
                self.set("members_exist_in_fence", True)
            except Exception as e:
                self.set("members_exist_in_fence", False)
                logger.warning(
                    "INVALID user(s) do not exist in fence and thus, "
                    "we cannot determine their authZ info: {}.".format(e)
                )
                if early_return:
                    return

        # use a generic validityinfo object to hold all the service accounts
        # validity. then check all the service accounts. Top level will be
        # invalid if any service accounts are invalid
        new_service_account_validity = ValidityInfo()
        if self.new_service_account:
            service_account_validity_info = GoogleServiceAccountValidity(
                self.new_service_account,
                self.google_project_id,
                google_project_number=google_project_number,
                google_cloud_manager=self.google_cloud_manager,
            )

            service_account_id = str(self.new_service_account)

            logger.debug(
                "Google Project with id: {} and number: {}. "
                "Beginning validation on service account for registration {}".format(
                    self.google_project_id, google_project_number, service_account_id
                )
            )

            logger.debug(
                "Determining if the service account {} is google-managed.".format(
                    service_account_id
                )
            )
            # we do NOT need to check the service account type and external access
            # for google-managed accounts.
            if is_google_managed_service_account(service_account_id):
                logger.debug(
                    "GCP SA Validity -Service account {} IS google-managed. Therefore, "
                    "we do NOT need to check the SA Type or if it has external access.".format(
                        service_account_id
                    )
                )
                service_account_validity_info.check_validity(
                    early_return=early_return,
                    check_type=True,
                    check_policy_accessible=True,
                    check_external_access=False,
                )
            else:
                logger.debug(
                    "GCP SA Validity -Service account {} is NOT google-managed. Therefore, "
                    "we need to run all validation checks against it.".format(
                        service_account_id
                    )
                )
                service_account_validity_info.check_validity(
                    early_return=early_return,
                    check_type=True,
                    check_policy_accessible=True,
                    check_external_access=True,
                )

            # update project with error info from the service accounts
            new_service_account_validity.set(
                service_account_id, service_account_validity_info
            )

            if not service_account_validity_info:
                logger.warning("INVALID service account {}.".format(service_account_id))
                # if we need to return early for invalid SA, make sure to include
                # error details and invalidate the overall validity
                if early_return:
                    self.set("new_service_account", new_service_account_validity)
                    return

        self.set("new_service_account", new_service_account_validity)

        logger.debug(
            "Google Project with id: {} and number: {}. "
            "Beginning validation on project service accounts not requested for "
            "registration.".format(self.google_project_id, google_project_number)
        )

        service_accounts = get_service_account_ids_from_google_members(
            service_account_members
        )

        logger.debug("SAs on the project {}.".format(service_accounts))

        remove_white_listed_service_account_ids(service_accounts)

        # don't double check service account being registered
        if self.new_service_account:
            try:
                service_accounts.remove(self.new_service_account)
            except ValueError:
                logger.debug(
                    "Service Account requested for registration is not a "
                    "member of the Google project."
                )

        # use a generic validityinfo object to hold all the service accounts
        # validity. then check all the service accounts. Top level will be
        # invalid if any service accounts are invalid
        service_accounts_validity = ValidityInfo()
        for service_account in service_accounts:
            service_account_validity_info = self._get_project_sa_validity_info(
                service_account, google_project_number, early_return
            )

            # update project with error info from the service accounts
            service_accounts_validity.set(
                service_account, service_account_validity_info
            )

            if not service_account_validity_info and early_return:
                # if we need to return early for invalid SA, make sure to include
                # error details and invalidate the overall validity
                self.set("service_accounts", service_accounts_validity)
                return

        self.set("service_accounts", service_accounts_validity)

        logger.debug(
            "Checking data access for Google Project {}...".format(
                self.google_project_id
            )
        )

        # get the service accounts for the project to determine all the data
        # the project can access through the service accounts
        service_accounts = get_registered_service_accounts_with_access(
            self.google_project_id, db=db
        )

        # don't double check service account being updated if it was previously registered
        # in other words, this may be an update of existing access (from A&B to just A)
        # so we need to ONLY validate the new access (which happens below when the project
        # access list is extended with new access requested)
        if self.new_service_account:
            logger.debug(
                "Removing new/updated SA {} from list of existing SAs in order "
                "to only validate the newly requested access.".format(
                    self.new_service_account
                )
            )
            service_accounts = [
                sa
                for sa in service_accounts
                if sa.email.lower() != str(self.new_service_account).lower()
            ]

        service_account_project_access = get_project_access_from_service_accounts(
            service_accounts, db=db
        )

        logger.debug(
            "Registered SAs {} current have project access: {}".format(
                [sa.email for sa in service_accounts], service_account_project_access
            )
        )

        # use a generic validityinfo object to hold all the projects validity
        project_access_validities = ValidityInfo()

        # extend list with any provided access to test
        for provided_access in self.new_service_account_access:
            project = get_project_from_auth_id(provided_access)

            # if provided access doesn't exist, set error in project_validity
            if not project:
                logger.warning(
                    "INVALID project access requested. "
                    "Data Access with auth_id {} does not exist.".format(
                        provided_access
                    )
                )
                project_validity = ValidityInfo()
                project_validity.set("exists", False)
                project_validity.set("all_users_have_access", None)
                project_access_validities.set(str(provided_access), project_validity)
            else:
                service_account_project_access.append(project)

        logger.debug(
            "New project access requested (in addition to "
            "previous access): {}.".format(service_account_project_access)
        )

        # make sure all the users of the project actually have access to all
        # the data the service accounts have access to
        for project in service_account_project_access:
            project_validity = ValidityInfo()
            project_validity.set("exists", True)

            # if all the users exist in our db, we can check if they have valid
            # access
            logger.debug(
                "Checking that all users in project have "
                "access to project with id {}".format(
                    getattr(project, "id", "ERROR-could-not-get-project-id")
                )
            )
            valid_access = None
            if users_in_project:
                valid_access = do_all_users_have_access_to_project(
                    users_in_project, project.id, db=db
                )
                if not valid_access:
                    logger.warning(
                        "INVALID Some users do NOT have "
                        "access to project with id {}. users in project: {}".format(
                            getattr(project, "id", "ERROR-could-not-get-project-id"),
                            [
                                getattr(user, "username", "unknown_user")
                                for user in users_in_project
                            ],
                        )
                    )

            project_validity.set("all_users_have_access", valid_access)

            project_access_validities.set(str(project.auth_id), project_validity)

        self.set("access", project_access_validities)
        self.google_cloud_manager.close()
        return

    def _get_project_sa_validity_info(
        self, service_account, google_project_number, early_return
    ):
        service_account_id = str(service_account)

        service_account_validity_info = GoogleServiceAccountValidity(
            service_account,
            self.google_project_id,
            google_project_number=google_project_number,
            google_cloud_manager=self.google_cloud_manager,
        )

        logger.debug(
            "Google Project with id: {} and number: {}. "
            "Beginning validation on project service account {}".format(
                self.google_project_id, google_project_number, service_account_id
            )
        )

        logger.debug(
            "Determining if the service account {} is google-managed.".format(
                service_account_id
            )
        )

        # we do NOT need to check the service account type and external access
        # for google-managed accounts.
        if is_google_managed_service_account(service_account_id):
            logger.debug(
                "Service account {} IS google-managed. Therefore, "
                "we only need to detemine if it belongs.".format(service_account_id)
            )
            service_account_validity_info.check_validity(
                early_return=early_return,
                check_type=False,
                check_policy_accessible=False,
                check_external_access=False,
            )
        else:
            logger.debug(
                "Service account {} is NOT google-managed. Therefore, "
                "we need to run all validation checks against it.".format(
                    service_account_id
                )
            )
            service_account_validity_info.check_validity(
                early_return=early_return,
                check_type=True,
                check_policy_accessible=True,
                check_external_access=True,
            )

        return service_account_validity_info

class GoogleServiceAccountValidity(ValidityInfo):
    """
    A representation of a Google Service Accounts's validity regarding
    registration for access to data.

    Example Usage and Information:

        sa_validity = (
            GoogleServiceAccountValidity(
                account_id='some-service-account@something.com'
                google_project_id='abc123',
                google_project_number='123456789'
            )
        )
        sa_validity.check_validity(early_return=False)

        NOTE: sa_validity can be evaluated as if it's a boolean and will
        be False if ANY of the values in the dict-like structure are False.
        In other words, a single invalid item will make the whole project
        invalid.

        An example of the dict-like structure with validity info is:

        {
            'valid_type': True,
            'no_external_access': True,
            'owned_by_project': True,
            'exists': True
        }
    """

    def __init__(
        self,
        account_id,
        google_project_id,
        google_cloud_manager=None,
        google_project_number=None,
        *args,
        **kwargs
    ):
        self.account_id = account_id
        self.google_project_id = google_project_id

        # default to the given project id if not provided
        self.google_project_number = google_project_number or google_project_id
        self.google_cloud_manager = google_cloud_manager or GoogleCloudManager(
            google_project_id
        )
        super(GoogleServiceAccountValidity, self).__init__(*args, **kwargs)

        # setup default values for error information, will get updated in
        # check_validity
        self._info["owned_by_project"] = None
        self._info["valid_type"] = None
        self._info["no_external_access"] = None
        self._info["policy_accessible"] = None

    def check_validity(
        self,
        early_return=True,
        check_type=True,
        check_external_access=True,
        check_policy_accessible=True,
    ):
        logger.debug(
            "Validating Google Service Account {} for Google Project {}.".format(
                self.account_id, self.google_project_id
            )
        )

        self.google_cloud_manager.open()

        # check ownership
        logger.debug(
            "Determining if {} is owned by the Google Project.".format(self.account_id)
        )

        is_owned_by_google_project = is_service_account_from_google_project(
            self.account_id, self.google_project_id, self.google_project_number
        )
        self.set("owned_by_project", is_owned_by_google_project)
        if not is_owned_by_google_project:
            logger.warning(
                "INVALID SA {}, it is NOT owned by the Google Project {}.".format(
                    self.account_id, self.google_project_id
                )
            )
            if early_return:
                self.google_cloud_manager.close()
                return

        # select the GCM to use for the remainder of the checks
        # if the account is not owned by the google project then
        # it is invalid, however, if Fence has access to the SA's
        # project, we can still check the other conditions

        if is_owned_by_google_project:
            gcm = self.google_cloud_manager
        else:
            self.google_cloud_manager.close()
            try:
                # check to see if we can access the project the SA belongs to
                project_id = self.account_id.split("@")[-1].split(".")[0]
                gcm = GoogleCloudManager(project_id)
                gcm.open()
            except Exception:
                logger.debug(
                    "Could not access the Google Project for Service "
                    "Account {}. Unable to continue validity "
                    "checkingwithout access to the project, "
                    "early exit.".format(self.account_id)
                )
                return

        # check if the SA's policy is accessible
        policy_accessible = None
        sa_policy = None
        if check_policy_accessible:
            try:
                policy_accessible = True
                sa_policy = get_service_account_policy(self.account_id, gcm)
            except Exception:
                policy_accessible = False
                gcm.close()
                return
            finally:
                self.set("policy_accessible", policy_accessible)

        if check_external_access:
            if not policy_accessible:
                logger.warning(
                    "Invalid function use. External Access check requires "
                    "Service Account Policy & may fail if policy is not "
                    "accessible. If you want to check external access, make "
                    "sure you are also checking policy_accessible. "
                )
                gcm.close()
                return

            no_external_access = not (
                service_account_has_external_access(self.account_id, gcm, sa_policy)
            )
            self.set("no_external_access", no_external_access)
            if not no_external_access:
                logger.warning(
                    "INVALID SA {}, it has external access "
                    "(keys generated or roles on it).".format(self.account_id)
                )
                if early_return:
                    gcm.close()
                    return

        # check if the SA is an allowed type
        if check_type:
            if not policy_accessible:
                logger.warning(
                    "Policy access was not checked. If the service account's "
                    "policy is not accessible or the service account does not "
                    "exist, this check may fail."
                )
                # don't return early, we can still check type without checking
                # policy, however, if the SA doesn't exist, this will fail

            valid_type = is_valid_service_account_type(self.account_id, gcm)

            self.set("valid_type", valid_type)
            if not valid_type:
                logger.warning(
                    "INVALID SA {}, it is not a valid SA type.".format(self.account_id)
                )
                if early_return:
                    gcm.close()
                    return

        gcm.close()


from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

ANSIBLE_METADATA = {
    'metadata_version': '1.1',
    'status': ['preview'],
    'supported_by': 'certified'
}

DOCUMENTATION = '''
---
module: aoscx_checkpoint
version_added: "2.8"
short_description: Creates a new checkpoint or copies an existing checkpoint to AOS-CX switch config.
description:
  - This module creates a new checkpoint or copies existing checkpoint
    to the running or startup config of an AOS-CX switch.
author: Aruba Networks (@ArubaNetworks)
options:
  source_config:
    description: Name of the source configuration from which checkpoint needs
      to be created or copied.
    type: str
    required: False
    default: 'running-config'

  destination_config:
    description: Name of the destination configuration or name of checkpoint.
    type: str
    required: False
    default: 'startup-config'
'''

EXAMPLES = '''
- name: Copy running-config to startup-config
  aoscx_checkpoint:
    source_config: 'running-config'
    destination_config: 'startup-config'

- name: Copy startup-config to running-config
  aoscx_checkpoint:
    source_config: 'startup-config'
    destination_config: 'running-config'

- name: Copy running-config to backup checkpoint
  aoscx_checkpoint:
    source_config: 'running-config'
    destination_config: 'checkpoint_20200128'
'''

RETURN = r''' # '''

from ansible.module_utils.aoscx import ArubaAnsibleModule, put

def main():
    module_args = dict(
        source_config=dict(type='str', default='running-config'),
        destination_config=dict(type='str', default='startup-config')
    )

    aruba_ansible_module = ArubaAnsibleModule(module_args=module_args)

    source_config = aruba_ansible_module.module.params['source_config']
    destination_config = \
        aruba_ansible_module.module.params['destination_config']

    url = '/rest/v1/fullconfigs/{dest}?from=/rest/v1/fullconfigs/{src}'.format(
        dest=destination_config, src=source_config)
    put(aruba_ansible_module.module, url)
    result = dict(changed=aruba_ansible_module.changed,
                  warnings=aruba_ansible_module.warnings)
    result["changed"] = True
    aruba_ansible_module.module.exit_json(**result)

if __name__ == '__main__':
    main()


import requests
from bs4 import BeautifulSoup
import re

def getHtmlPage(url):
    html_data = requests.get(url)
    html_data.encoding = html_data.apparent_encoding
    if html_data.status_code == 200:
        return  html_data.text
    else:
        return None

def pasePage(HtmlText):
    soup = BeautifulSoup( HtmlText,'html5lib')
    data = soup.select('div.ranklist-wrapper.clearfix div.bd ul.song-list li')
    pattern1 = re.compile(r'<li.*?
(.*?)
.*?title="(.*?)".*?title="(.*?)".*?
',re.S)
    pattern2 = re.compile(r'(.*?)', re.S)

    dataWant = [];

    for item in data:
        findResult = re.findall(pattern1,str(item))
        if len(findResult) == 1:
            dataWant.append(findResult[0])
        else:
            otherWayData = re.findall(pattern2, str(item))
            dataWant.append(otherWayData[0])
    return dataWant

if __name__ == '__main__':
    url = 'http://music.taihe.com/'
    html = getHtmlPage(url)
    data = pasePage(html)
    for item in data:
        dict = {
            '序号':item[0],
            '歌名':item[1],
            '歌手':item[2]
        }

        print(dict)


from django.contrib import admin
from django.urls import path, re_path, include
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from rest_framework.permissions import AllowAny

schema_view = get_schema_view(
    openapi.Info(
        title='CareerHub API',
        default_version='v1',
        description='CareerHub browsable API =)',
        terms_of_service='Thank you',
        contact=openapi.Contact(email='careerhub@careerhub.com'),
        license=openapi.License(name='Лицензий нет =)'),
    ),
    public=True,
    permission_classes=[AllowAny],
)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('api.v1.urls')),
    re_path(
        r'^swagger(?P<format>\.json|\.yaml)$',
        schema_view.without_ui(cache_timeout=0),
        name='schema-json'
    ),
    path(
        'swagger/',
        schema_view.with_ui('swagger', cache_timeout=0),
        name='schema-swagger-ui'
    ),
    path(
        'redoc/',
        schema_view.with_ui('redoc', cache_timeout=0),
        name='schema-redoc'
    ),
]


"""
example:
	python splitH5.py --iname=./pre-trained-rnn.h5 \
					--dataset=/model_weights/dense_1/dense_1/bias:0 \
					--oname=./dense_1_bias.h5 \
					--new_dataset=dense_1/bias:0
"""

import h5py
import argparse

if __name__ == "__main__":

	parser = argparse.ArgumentParser()
	parser.add_argument('--iname', type=str, default="",
		help="the input H5 file")
	parser.add_argument('--dataset', type=str, default="",
		help="the dataset we want to split")
	parser.add_argument('--oname', type=str, default="",
		help="the output H5 file")
	parser.add_argument('--new_dataset', type=str, default="",
		help="the dataset we want to generate in the new H5 file")
	args = parser.parse_args()
	iname = args.iname
	dname = args.dataset
	oname = args.oname
	ndname = args.new_dataset

	f = h5py.File(iname, 'r')
	dataset = f[dname]
	h5f = h5py.File(oname, 'w')
	h5f.create_dataset(ndname, data=dataset)
	h5f.close()

	# f = h5py.File('./pre-trained-rnn.h5', 'r')
	# dataset = f['model_weights/dense_1/dense_1/bias:0']
	# h5f = h5py.File('dense_1_bias.h5', 'w')
	# h5f.create_dataset('dense_1/bias:0', data=dataset)
	# h5f.close()


import pygame

#kicks off all the modules required for PyGame
pygame.init()

#Launches a window of desired size, return value is a Surface object
screen = pygame.display.set_mode((400, 300))
done = False
is_blue = True
x = 30
y = 30

clock = pygame.time.Clock()

while not done:
    for event in pygame.event.get(): #this empties the event queue, if not used program may become unresponsive
        if event.type == pygame.QUIT: #event triggered when clicking close window button
            done = True
        if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
            is_blue = not is_blue

    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_UP]:
        y-=3
    if pressed[pygame.K_DOWN]:
        y+=3
    if pressed[pygame.K_LEFT]:
        x-=3
    if pressed[pygame.K_RIGHT]:
        x+=3

    screen.fill((0,0,0))

    if is_blue:
        color = (0,128,255)
    else:
        color = (255,100,0)
    #1st arg - the surface object; 2nd arg - color of the rect; 3rd arg - size of the rect drawn
    pygame.draw.rect(screen, color, pygame.Rect(x,y,60,60))
    #swaps the buffers; i.e it is needed so that any updates on the surface object becomes visible
    pygame.display.flip()
    clock.tick(60)


"""Topology for surface tracking (:mod:`fluidimage.topologies.surface_tracking`)
================================================================================

.. autoclass:: TopologySurfaceTracking
   :members:
   :private-members:

.. todo::

   Make this code usable:

   - good unittests
   - tutorial with ipynb
   - example integrated in the documentation

"""
import json

from ..topologies import prepare_path_dir_result
from .base import TopologyBase
from .waiting_queues.base import (
    WaitingQueueMultiprocessing,
    WaitingQueueThreading,
    WaitingQueueLoadImagePath,
    WaitingQueueMakeCouple,
)

from ..data_objects.piv import ArrayCouple
from .. import ParamContainer
from .. import SeriesOfArrays, SerieOfArraysFromFiles
from ..util import logger
from ..works.old.surface_tracking import WorkSurfaceTracking

class TopologySurfaceTracking(TopologyBase):
    """Topology for SurfaceTracking.

    Parameters
    ----------

    params : None

      A ParamContainer containing the parameters for the computation.

    logging_level : str, {'warning', 'info', 'debug', ...}

      Logging level.

    nb_max_workers : None, int

      Maximum numbers of "workers". If None, a number is computed from the
      number of cores detected. If there are memory errors, you can try to
      decrease the number of workers.

    """

    @classmethod
    def create_default_params(cls):
        """Class method returning the default parameters.

        For developers: cf. fluidsim.base.params

        """
        params = ParamContainer(tag="params")

        params._set_child(
            "series",
            attribs={
                "path": "",
                "strcouple": "i:i+2",
                "ind_start": 0,
                "ind_stop": None,
                "ind_step": 1,
            },
        )

        params._set_child(
            "film",
            attribs={
                "fileName": "",
                "path": "",
                "path_ref": "",
                "ind_start": 0,
                "ind_stop": None,
                "ind_step": 1,
            },
        )

        params._set_child(
            "saving",
            attribs={
                "plot": False,
                "how_many": 1000,
                "path": None,
                "how": "hdf5",
                "postfix": "surface_tracking",
            },
        )

        params.saving._set_doc(
            """Saving of the results.

path : None or str

    Path of the directory where the data will be saved. If None, the path is
    obtained from the input path and the parameter `postfix`.

how : str {'ask'}

    'ask', 'new_dir', 'complete' or 'recompute'.

postfix : str

    Postfix from which the output file is computed.
"""
        )

        WorkSurfaceTracking._complete_params_with_default(params)

        params._set_internal_attr(
            "_value_text",
            json.dumps(
                {
                    "program": "fluidimage",
                    "module": "fluidimage.topologies.surface_tracking",
                    "class": "TopologySurfaceTracking",
                }
            ),
        )

        params._set_child("mask", attribs={"strcrop": None})

        params.mask._set_doc(
            """
            Parameters describing how images are masked.

            strcrop : None, str

                Two-dimensional slice (for example '100:600, :'). If None, the
                whole image is used.  """
        )

        return params

    def __init__(self, params=None, logging_level="info", nb_max_workers=None):

        if params is None:
            params = self.__class__.create_default_params()

        self.params = params
        self.path = params.film.path
        self.path_ref = params.film.path_ref
        serie_arrays_ref = SerieOfArraysFromFiles(params.film.path_ref)

        self.surface_tracking_work = WorkSurfaceTracking(params)

        serie_arrays = SerieOfArraysFromFiles(
            params.film.path + "/" + params.film.fileName
        )
        self.series = SeriesOfArrays(
            serie_arrays,
            params.series.strcouple,
            ind_start=params.film.ind_start,
            ind_stop=params.film.ind_stop,
            ind_step=params.film.ind_step,
        )

        path_dir = self.path
        path_dir_result, self.how_saving = prepare_path_dir_result(
            path_dir, params.saving.path, params.saving.postfix, params.saving.how
        )

        self.path_dir_result = params.film.path
        self.results = {}

        def save_surface_tracking_object(o):
            ret = o.save(path_dir_result / o.nameFrame)
            return ret

        self.wq_sf_out = WaitingQueueThreading(
            "save_surface_tracking_object",
            save_surface_tracking_object,
            self.results,
            topology=self,
        )
        self.wq_sf_in = WaitingQueueMultiprocessing(
            "surface_tracking_work",
            self.surface_tracking_work.compute,
            self.wq_sf_out,
            topology=self,
        )

        self.wq_images = WaitingQueueMakeCouple(
            "array image", self.wq_sf_in, topology=self
        )

        self.wq0 = WaitingQueueLoadImagePath(
            destination=self.wq_images, path_dir=path_dir, topology=self
        )

        waiting_queues = [self.wq0, self.wq_images, self.wq_sf_in, self.wq_sf_out]

        super().__init__(
            waiting_queues,
            path_output=path_dir_result,
            logging_level=logging_level,
            nb_max_workers=nb_max_workers,
        )

        self.add_series(self.series)

    def add_series(self, series):
        """
        Fill working queues
        :param series: Series representing a film
        :type SeriesOfArrays
        :return:
        """
        if len(series) == 0:
            logger.warning("add 0 couple. No PIV to compute.")
            return

        if self.how_saving == "complete":
            names = []
            index_series = []
            for i, serie in enumerate(series):
                name_sf = serie.get_name_arrays()
                #                if os.path.exists(os.path.join(self.path_dir_result, name_sf[0])):
                #                    print(os.path.join(self.path_dir_result, name_sf[0]))
                #                    continue

                for name in serie.get_name_arrays():
                    if name not in names:
                        names.append(name)

                index_series.append(i * series.ind_step + series.ind_start)
            if len(index_series) == 0:
                logger.warning(
                    'topology in mode "complete" and work already done.'
                )
                return

            series.set_index_series(index_series)

            logger.debug(repr(names))
            logger.debug(repr([serie.get_name_arrays() for serie in series]))
        else:
            names = series.get_name_all_arrays()

        nb_series = len(series)
        print(f"Add {nb_series} surface fields to compute.")

        for i, serie in enumerate(series):
            if i > 1:
                break

        print("Files of serie {}: {}".format(i, serie.get_name_arrays()))

        self.wq0.add_name_files(names)
        self.wq_images.add_series(series)

        k, o = self.wq0.popitem()
        im = self.wq0.work(o)
        self.wq0.fill_destination(k, im)

        # a little bit strange, to apply mask...
        try:
            params_mask = self.params.mask
        except AttributeError:
            params_mask = None
        couple = ArrayCouple(
            names=("", ""), arrays=(im, im), params_mask=params_mask
        )
        im, _ = couple.get_arrays()

    def print_at_exit(self, time_since_start):
        pass

params = TopologySurfaceTracking.create_default_params()
__doc__ += params._get_formatted_docs()


import sys
from collections import defaultdict

class TrieNode:
    def __init__(self):
        self.children = defaultdict(TrieNode)
        self.end = False

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert_word(self, word: str) -> None:
        node = self.root
        for char in word:
            node = node.children[char]
        node.end = True

    def find_word(self, word: str) -> bool:
        node = self.root
        for char in word:
            if char not in node.children.keys():
                return False
            node = node.children[char]
        return node.end

def main():
    s, m = map(int, sys.stdin.readline().split())

    trie = Trie()

    for _ in range(s):
        trie.insert_word(sys.stdin.readline().strip())

    res = 0
    for _ in range(m):
        if trie.find_word(sys.stdin.readline().strip()):
            res += 1

    print(res)

if __name__ == "__main__":
    main()


from bs4 import BeautifulSoup
from urllib.request import Request, urlopen
import re
import csv
#import sys
#import json

if __name__ == '__main__':
    fieldnames = ['name', 'genre', 'actor', 'director', 'duration', 'date_released', 'score']
    with open('metacritic_data.csv', mode='w') as csv_file:
        writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
        writer.writeheader()
        months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
        root = ["https://www.metacritic.com/browse/movies/genre/date/action?view=condensed&page=", "https://www.metacritic.com/browse/movies/genre/date/documentary?view=condensed&page="]
        for rooti in range(2):
            if rooti == 0:
                limitindex = 64
            else:
                limitindex = 36
            for i in range(limitindex):
                print(rooti, i)
                site= root[rooti] + str(i)
                hdr = {'User-Agent': 'Mozilla/5.0'}
                req = Request(site,headers=hdr)
                html = urlopen(req).read().decode('utf-8')
                soup = BeautifulSoup(html, features='lxml')
                all_h3 = soup.find_all("div")
                for one_h3 in all_h3:
                    #try:
                    if one_h3.get('class') is not None and one_h3.findChildren("h3" , recursive=False) == [] and one_h3.get('class')[0] == 'basic_stat' and one_h3.get('class')[1] == 'product_title':
                        links = one_h3.find_all('a')
                        for link in links:
                            info = {}
                            target = 'https://www.metacritic.com'+link.get('href')
                            #print('Go to target: '+target)
                            req = Request(target,headers=hdr)
                            sub_html = urlopen(req).read().decode('utf-8')

                            #print(sub_html)
                            sub_soup = BeautifulSoup(sub_html, features = 'lxml')
                            director = sub_soup.find('div', {'class': "director"})
                            if director is not None:
                                director = director.get_text().split('\n')[2].replace(" and ", "&")
                            else:
                                director = 'null'
                            #print(director)

                            genres = sub_soup.find('div', {'class': "genres"})
                            if genres is not None:
                                genres = ''.join(genres.get_text().split('\n')[3].split(' ')).replace(",", "&")
                            else:
                                genres = 'null'
                            #print(genres)

                            runtime = sub_soup.find('div', {'class': "runtime"})
                            if runtime is not None:
                                runtime = runtime.get_text().split('\n')[2]
                            else:
                                runtime = 'null'
                            #print(runtime)

                            actors = sub_soup.find('div', {'class': "summary_cast details_section"})
                            if actors is not None:
                                actors = ''.join(re.split("  +", actors.get_text().split('\n')[3])).replace(",", "&")
                            else:
                                actors = 'null'
                            #print(actors)

                            date = sub_soup.find('span', {'class': "release_date"})
                            if date is not None:
                                date_letter = date.get_text().split('\n')[2].replace(", ", " ")
                                date_sp = date_letter.split()
                                if date_sp[0] in months:
                                    date_sp[0] = str(months.index(date_sp[0])+1)
                                    date = '/'.join(date_sp)
                                else:
                                    date = 'null'
                            else:
                                date = 'null'
                            #print(date)

#                            userscore = sub_soup.find('div', {'class': re.compile("metascore_w user larger movie *")})
#                            if userscore is not None:
#                                userscore = userscore.get_text().split('\n')[0]
#                            else:
#                                userscore = 'null'
#                            #print(userscore)

                            metascore = sub_soup.find('div', {'class': re.compile("metascore_w larger movie *")})
                            if metascore is not None:
                                metascore = metascore.get_text().split('\n')[0]
                            else:
                                metascore = 'null'
                            #print(metascore)

                            name = sub_soup.find('div', {'class': "product_page_title oswald"})
                            name = name.get_text().split('\n')[1]
                            print(name)
                            #['name', 'genre', 'actor', 'director', 'duration', 'datePublished', 'score']
                            if runtime.lower() == 'tbd' or runtime.lower() == 'tba':
                                runtime = 'null'
                            if name.lower() == 'tbd' or name.lower() == 'tba':
                                name = 'null'
                            if genres.lower() == 'tbd' or genres.lower() == 'tba':
                                genres = 'null'
                            if actors.lower() == 'tbd' or actors.lower() == 'tba':
                                actors = 'null'
                            if date.lower() == 'tbd' or date.lower() == 'tba':
                                date = 'null'
                            if metascore.lower() == 'tbd' or metascore.lower() == 'tba':
                                userscore = 'null'
                            if director.lower() == 'tbd' or director.lower() == 'tba':
                                director = 'null'
        #                    if name == 'Blazing Samurai':
                            info['duration'] = runtime
                            info['name'] = name
                            info['genre'] = genres
                            info['actor'] = actors
                            info['date_released'] = date
                            info['score'] = metascore
                            info['director'] = director
                            writer.writerow(info)

            #except KeyboardInterrupt:
            #    sys.exit()
            #except:
            #    print("An exception occurred while processing ", one_h3)


import pymongo

##### Uncomment this part, if your running on a gnome-terminal ########
##### It launches mongod in a new terminal.                    ########
# import subprocess
# import time
#
# subprocess.call(['gnome-terminal', '--', 'mongod'])
# print("Wait 2 seconds for mongod to start up.....")
# time.sleep(2)
#######################################################################

myclient = pymongo.MongoClient("mongodb://localhost:27017/")
mydb = myclient["foodDB"]
mycol = mydb["fooditems"]

def deleteOne():
    name = input("Delete product: ")
    myquery = {"name": name}
    if mycol.find_one(myquery):
        mycol.delete_one(myquery)
        emptyLines(1)
        print("Succesfully deleted: " + name)
    else:
        emptyLines(1)
        print(name + " not found.")
    emptyLines()

def findOne():
    name = input("Search product: ")
    food = {"name": name}
    x = mycol.find_one(food)
    if x == None:
        emptyLines(1)
        print(name + " not found")
    else:
        emptyLines(1)
        print(x)
    emptyLines()

def insertOne():
    name = input("Product Name: ")
    while True:
        try:
            kcal = float(input("Kcal :"))
            break
        except ValueError:
            print("Oops! That was not a valid number. Try again...")
            emptyLines(1)
    food = {"name": name, "kcal": kcal}
    x = mycol.insert_one(food)
    emptyLines(1)
    print("Succesfully added :", food)
    emptyLines()

def querySomething():
    myquery = {"kcal": {"$gt": 50}}
    mydoc = mycol.find(myquery)
    for x in mydoc:
        print(x)
    emptyLines()

def showAll(mycol=mycol):
    for x in mycol.find():
        print(x)
    emptyLines()

def showCollections():
    print(mydb.list_collection_names())
    emptyLines()

def sortByName():
    mydoc = mycol.find().sort("name")
    for x in mydoc:
        print(x)
    emptyLines()

def sortByKcal():
    mydoc = mycol.find().sort("kcal", -1)  # , -1  = reverse sort
    for x in mydoc:
        print(x)
    emptyLines()

def updateKcal():
    name = input("Product Name: ")
    myquery = {"name": name}
    if mycol.find_one(myquery):
        while True:
            try:
                kcal = float(input("Update Kcal :"))
                break
            except ValueError:
                print("Oops! That was not a valid number. Try again...")
                emptyLines(1)
        newvalues = {"$set": {"kcal": kcal}}
        mycol.update_one(myquery, newvalues)
        emptyLines(1)
        print("Succefully updated ", name, newvalues["$set"])
    else:
        emptyLines(1)
        print(name + " not found.")
    emptyLines()

def updateName():
    name = input("Product Name: ")
    myquery = {"name": name}
    if mycol.find_one(myquery):
        newName = userChoice = input("New Name: ")
        newvalues = {"$set": {"name": newName}}
        mycol.update_one(myquery, newvalues)
        emptyLines(1)
        print("Succefully updated ", name, newvalues["$set"])
    else:
        emptyLines(1)
        print(name + " not found.")
    emptyLines()

# Non MongoDB

def emptyLines(x=2):
    print('\n' * x)

def printMenu():
    emptyLines()
    print("-" * 22 + "Menu" + "-" * 22)
    print("1. Show all                 5. Update kcal")
    print("2. Add new product          6. Sort by name")
    print("3. Find product             7. Sort by kcal")
    print("4. Update name              8. Delete product")
    print("0. Exit")
    print("-" * 48)
    emptyLines()

# Main Loop

def main():
    userChoice = None
    while True:
        if userChoice in [1, 2, 3, 4, 5, 6, 7, 8, 99]:
            input("Press Enter to continue")

        printMenu()

        while True:
            try:
                userChoice = int(input("Please make a choice: "))
                emptyLines(1)
                break
            except ValueError:
                print("Oops! That was not a valid number. Try again...")
                emptyLines(1)

        if userChoice == 0:
            emptyLines(1)
            print("Goodbye")
            emptyLines(1)
            break

        elif userChoice == 1:
            showAll()
        elif userChoice == 2:
            insertOne()
        elif userChoice == 3:
            findOne()
        elif userChoice == 4:
            updateName()
        elif userChoice == 5:
            updateKcal()
        elif userChoice == 6:
            sortByName()
        elif userChoice == 7:
            sortByKcal()
        elif userChoice == 8:
            deleteOne()
    # test option
        elif userChoice == 99:
            showCollections()
        else:
            print("Please choose a correct number (0-8)")

if __name__ == "__main__":
    main()


"""
GOL: Graphics in One Line

A collection of functions to create matplotlib plots in a single command line.

(This has been written after getting tired of polluting code with matplotlib instructions).
"""

__author__ = "Manuel Sanchez del Rio"
__contact__ = "srio@esrf.eu"
__copyright = "ESRF, 2016"

import numpy as np
try:
    import matplotlib.pylab as plt
except:
    raise ImportError("Please install matplotlib to allow graphics")

def set_qt():
    """
    Sets the 'correct' qt backend: plt.switch_backend("Qt5Agg").
    This is useful in python scripts making graphs inside oasys in some platforms.
    """
    try:
        plt.switch_backend("Qt5Agg")
    except:
        raise Exception("Failed to set matplotlib backend to Qt5Agg")

def plot_show():
    """
    executes matplotlib's plt.show().
    """
    plt.show()

def plot_image(*positional_parameters,title="TITLE",xtitle=r"X",ytitle=r"Y",
               xrange=None, yrange=None,
               cmap=None,aspect=None,show=1,
               add_colorbar=True,figsize=None):
    """
    Plots an image.

    Parameters
    ----------
    *positional_parameters : tuple(s)
        z [, x, y]
    title : str, optional
        The main title.
    xtitle : str, optional
        The X title.
    ytitle : str, optional
        The Y title.
    xrange : 2D tuple or list
        [min, max] values.
    yrange : 2D tuple or list
        [min, max] values.
    cmap : str, optional
        The matplotlin color map.
    aspect : str, optional
        The matplotlib aspect (e..g. 'auto')
    show : int, optional
        if 1 executes show_plot()
    add_colorbar : boolean, optional
        if True adds the color bar.
    figsize : tuple
        The matplotlib figure size.

    Returns
    -------
    tuple
        The matplotlib (fig, ax).

    """
    n_arguments = len(positional_parameters)
    if n_arguments == 1:
        z = positional_parameters[0]
        x = np.arange(0,z.shape[0])
        y = np.arange(0,z.shape[1])
    elif n_arguments == 2:
        z = positional_parameters[0]
        x = positional_parameters[1]
        y = positional_parameters[1]
    elif n_arguments == 3:
        z = positional_parameters[0]
        x = positional_parameters[1]
        y = positional_parameters[2]
    else:
        raise Exception("Bad number of inputs")

    fig = plt.figure(figsize=figsize)

    # cmap = plt.cm.Greys
    plt.imshow(z.T,origin='lower',extent=[x[0],x[-1],y[0],y[-1]],cmap=cmap,aspect=aspect)
    if add_colorbar:
        plt.colorbar()
    ax = fig.gca()
    ax.set_xlabel(xtitle)
    ax.set_ylabel(ytitle)

    plt.title(title)

    plt.xlim( xrange )
    plt.ylim( yrange )

    if show:
        plt.show()

    return fig, ax

def plot_image_with_histograms(*positional_parameters,
                               title="", xtitle=r"X", ytitle=r"Y",
                               xrange=None, yrange=None,
                               cmap=None, aspect='auto', show=True,
                               add_colorbar=False, figsize=(8,8),
                               use_profiles_instead_histograms=False,
                               ):
    """
    Plots an image with an histogram.

    Parameters
    ----------
    *positional_parameters : tuple(s)
        z [, x, y]  Note that here, z array has Y in the first index z(y,x)!!!!
    title : str, optional
        The main title.
    xtitle : str, optional
        The X title.
    ytitle : str, optional
        The Y title.
    xrange : 2D tuple or list
        [min, max] values.
    yrange : 2D tuple or list
        [min, max] values.
    cmap : str, optional
        The matplotlin color map.
    aspect : str, optional
        The matplotlib aspect (e..g. 'auto')
    show : int, optional
        if 1 executes show_plot()
    add_colorbar : boolean, optional
        if True adds the color bar.
    figsize : tuple
        The matplotlib figure size.
    use_profiles_instead_histograms : boolean, optional
        If True, display the profiles at (0,0) instead of the histograms.

    Returns
    -------
    tuple
        The matplotlib objects (figure, axScatter, axHistx, axHisty).

    """

    if aspect is None: aspect == 'auto'

    n_arguments = len(positional_parameters)
    if n_arguments == 1:
        z = positional_parameters[0].T
        x = np.arange(0,z.shape[0])
        y = np.arange(0,z.shape[1])
    elif n_arguments == 2:
        z = positional_parameters[0].T
        x = positional_parameters[1]
        y = positional_parameters[1]
    elif n_arguments == 3:
        z = positional_parameters[0].T
        x = positional_parameters[1]
        y = positional_parameters[2]
    else:
        raise Exception("Bad number of inputs")

    if xrange is None:
        xrange = [x.min(),x.max()]

    if yrange is None:
        yrange = [y.min(),y.max()]

    figure = plt.figure(figsize=figsize)

    hfactor = 1.0
    vfactor = 1.0

    left, width = 0.1, 0.6
    bottom, height = 0.1, 0.6
    bottom_h = left_h = left + width + 0.02
    rect_scatter = [left, bottom, width, height]
    rect_histx = [left, bottom_h, width, 0.2]
    rect_histy = [left_h, bottom, 0.2, height]

    #
    #main plot
    #
    axScatter = figure.add_axes(rect_scatter)

    axScatter.set_xlabel(xtitle)
    axScatter.set_ylabel(ytitle)

    if aspect == 'equal':
        axScatter.set_aspect(aspect)

    axScatter.axis(xmin=hfactor*xrange[0],xmax=xrange[1])
    axScatter.axis(ymin=vfactor*yrange[0],ymax=yrange[1])

    axs = axScatter.pcolormesh(x,y,z,cmap=cmap)

    #
    #histograms
    #

    if aspect == 'equal':
        pos0 = axScatter.get_position()
        mm = np.min((pos0.height, pos0.width)) * 0.6
        axHistx = figure.add_axes([pos0.x0, pos0.y0 +pos0.height, pos0.width, mm], sharex=axScatter)
        axHisty = figure.add_axes([pos0.x0 + pos0.width, pos0.y0, mm * figsize[1] / figsize[0], pos0.height], sharey=axScatter)
    else:
        axHistx = figure.add_axes(rect_histx, sharex=axScatter)
        axHisty = figure.add_axes(rect_histy, sharey=axScatter)

    if use_profiles_instead_histograms:
        hx = z[z.shape[0]//2, :]
        hy = z[:, z.shape[1]//2]
    else:
        hx = z.sum(axis=0)
        hy = z.sum(axis=1)

    axHistx.plot(x,hx)
    axHisty.plot(hy,y)

    # tt = np.where(hx >= hx.max() * 0.5)
    # if hx[tt].size > 1:
    #     binSize = x[1] - x[0]
    #     print("FWHM X: ",binSize * (tt[0][-1] - tt[0][0]))
    #
    #
    # tt = np.where(hy >= hy.max() * 0.5)
    # if hx[tt].size > 1:
    #     binSize = y[1] - y[0]
    #     print("FWHM Y: ",binSize * (tt[0][-1] - tt[0][0]))

    # supress ordinates labels ans ticks
    axHistx.get_yaxis().set_visible(False)
    axHisty.get_xaxis().set_visible(False)
    axHistx.set_ylim(ymin=0, ymax=hy.max()*1.1)
    axHisty.set_xlim(xmin=0, xmax=hx.max() * 1.1)
    # supress abscissas labels (keep ticks)
    for tl in axHistx.get_xticklabels(): tl.set_visible(False)
    for tl in axHisty.get_yticklabels(): tl.set_visible(False)

    if title != "":
        axHistx.set_title(title)

    if add_colorbar:
        plt.colorbar(axs)

    if show:
        plt.show()

    return figure, axScatter, axHistx, axHisty

def plot(*positional_parameters, title="", xtitle="", ytitle="",
         xrange=None, yrange=None, show=1, legend=None, legend_position=None, color=None, marker=None, linestyle=None,
         xlog=False, ylog=False, figsize=None):
    """
    Makes a plot of a dataset (XY plot).

    Parameters
    ----------
    *positional_parameters : numpy arrays
        x1, y1 [, x2, y2, x3, y3, ...]
    title : str, optional
        The main title.
    xtitle : str, optional
        The X title.
    ytitle : str, optional
        The Y title.
    xrange : 2D tuple or list
        [min, max] values.
    yrange : 2D tuple or list
        [min, max] values.
    show : int, optional
        if 1 executes show_plot()
    legend : str or list
        The legend for the different datasets.
    legend_position : list or tuple
        2D list with coordinates of the legend.
    color : str
        The matplotlib color code.
    marker : str or list
        The matplotlib marker or symbol.
    linestyle : str or list
        The matplotlib linestyle code.
    xlog : boolean, optional
        Set to True for logarithmic plot axis.
    ylog : boolean, optional
        Set to True for logarithmic plot axis.
    figsize : tuple
        The matplotlib figure size.

    Returns
    -------
    tuple
        The matplotmib (fig, ax).

    """

    if isinstance(positional_parameters,tuple):
        if len(positional_parameters) == 1: # in the cvase that input is a tuple with all curves
            positional_parameters = positional_parameters[0]

    n_arguments = len(positional_parameters)
    if n_arguments == 0:
        return

    fig = plt.figure(figsize=figsize)
    if n_arguments == 1:
        y = positional_parameters[0]
        x = np.arange(y.size)
        if linestyle == None:
            linestyle = '-'
        plt.plot(x,y,label=legend,marker=marker,color=color,linestyle=linestyle)
    elif n_arguments == 2:
        x = positional_parameters[0]
        y = positional_parameters[1]
        if linestyle == None:
            linestyle = '-'
        plt.plot(x, y, label=legend, color=color, marker=marker, linestyle=linestyle)
    elif n_arguments % 2 == 0:
        if legend is None:
            legend = [None] * (n_arguments // 2)
        if color is None:
            color = [None] * (n_arguments // 2)

        if marker is None:
            marker = [None] * (n_arguments // 2)

        if linestyle is None:
            linestyle = ['-'] * (n_arguments // 2)

        for i in range(n_arguments // 2):
            plt.plot(positional_parameters[2*i],positional_parameters[2*i+1],label=legend[i],marker=marker[i],linestyle=linestyle[i],color=color[i])
    else:
        raise Exception("Incorrect number of arguments: found an odd number of data sets")
        # x = positional_parameters[0]
        # y = positional_parameters[1]
        # plt.plot(x,y,label=legend)

    ax = plt.subplot(111)
    if legend is not None:
        ax.legend(bbox_to_anchor=legend_position)

    if xlog:
        ax.set_xscale("log")

    if ylog:
        ax.set_yscale("log")

    plt.xlim( xrange )
    plt.ylim( yrange )

    plt.title(title)
    plt.xlabel(xtitle)
    plt.ylabel(ytitle)

    if show:
        plt.show()

    return fig, ax

def plot_table(*positional_parameters, errorbars=None, xrange=None, yrange=None,
               title="", xtitle="", ytitle="", show=1,
               legend=None, legend_position=None, color=None,
               xlog=False, ylog=False, figsize=None):
    """
    Makes a plot with data in tabular arrays using one array with abscissas X (Nx values), and another array
    (N, Nx) with ordinates Y for N datasets.

    Parameters
    ----------

    *positional_parameters : numpy arrays
        [X, ] Y
    errorbars : numpy array
        The values for the error bars.
    title : str, optional
        The main title.
    xtitle : str, optional
        The X title.
    ytitle : str, optional
        The Y title.
    xrange : 2D tuple or list
        [min, max] values.
    yrange : 2D tuple or list
        [min, max] values.
    show : int, optional
        if 1 executes show_plot()
    legend : str or list
        The legend for the different datasets.
    legend_position : list or tuple
        2D list with coordinates of the legend.
    color : str
        The matplotlib color code.
    xlog : boolean, optional
        Set to True for logarithmic plot axis.
    ylog : boolean, optional
        Set to True for logarithmic plot axis.
    figsize : tuple
        The matplotlib figure size.

    Returns
    -------
    tuple
       The matplotlib objects (fig, ax).

    """

    n_arguments = len(positional_parameters)
    if n_arguments == 0:
        return

    fig = plt.figure(figsize=figsize)

    if n_arguments == 1:
        y = positional_parameters[0]
        x = np.arange(y.size)
        plt.plot(x,y,label=legend)
    elif n_arguments == 2:
        x = positional_parameters[0]
        y = positional_parameters[1]

        if len(y.shape) == 1:
            y = np.reshape(y,(1,y.size))
            if isinstance(legend,str):
                legend = [legend]
            if isinstance(color,str):
                color = [color]

        for i in range(y.shape[0]):
            if legend is None:
                ilegend = None
            else:
                ilegend = legend[i]

            if color is None:
                icolor = None
            else:
                icolor = color[i]

            if errorbars is None:
                plt.plot(x,y[i],label=ilegend,color=icolor)
            else:
                plt.errorbar(x,y[i],yerr=errorbars[i],label=ilegend,color=icolor)
    else:
        raise Exception("Incorrect number of arguments")

    ax = plt.subplot(111)

    if xlog:
        ax.set_xscale("log")

    if ylog:
        ax.set_yscale("log")

    if legend is not None:
        if legend_position is None:
            legend_position = (1.1, 1.05)
        ax.legend(bbox_to_anchor=legend_position)

    plt.xlim( xrange )
    plt.ylim( yrange )

    plt.title(title)
    plt.xlabel(xtitle)
    plt.ylabel(ytitle)

    if show:
        plt.show()

    return fig, ax

def four_plots(x1, y1, x2, y2, x3, y3, x4, y4, title="", xtitle="", ytitle="", xrange=None, yrange=None, show=True):
    """
    Creates four plots in a window.

    Parameters
    ----------
    x1 : numpy array
        abscissas for plot 1.
    y1 : numpy array
        ordinates for plot 1.
    x2 : numpy array
        abscissas for plot 2.
    y2 : numpy array
        ordinates for plot 2.
    x3 : numpy array
        abscissas for plot 3.
    y3 : numpy array
        ordinates for plot 3.
    x4 : numpy array
        abscissas for plot 4.
    y4 : numpy array
        ordinates for plot 4.
    title : str or list, optional
        a string or list of 4 strings with title.
    xtitle : str or list, optional
        a string or list of 4 strings with title for X.
    ytitle : str or list, optional
        a string or list of 4 strings with title for Y.
    xrange : tuple or list, optional
        the X range for all plots.
    yrange : tuple or list, optional
        the Y range for all plots.
    show : boolean, optional
        if True, execute plot_show() at the end.

    Returns
    -------
    tuple
        The matplotlib objects (f, ax00, ax01, ax10, ax11).

    """

    if isinstance(title,list):
        Title = title
    else:
        Title = [title,title,title,title]

    if isinstance(xtitle,list):
        Xtitle = xtitle
    else:
        Xtitle = [xtitle,xtitle,xtitle,xtitle]

    if isinstance(ytitle,list):
        Ytitle = ytitle
    else:
        Ytitle = [ytitle,ytitle,ytitle,ytitle]

    # Create subplots.
    f, ((ax00, ax01), (ax10, ax11)) = plt.subplots(2, 2, sharex="all", sharey="all")

    ax00.plot(x1,y1, "-")
    ax00.set_title(  Title[0])
    ax00.set_xlabel(Xtitle[0])
    ax00.set_ylabel(Ytitle[0])
    ax00.set_xlim(xrange)
    ax00.set_ylim(yrange)

    ax01.plot(x2,y2, "-")
    ax01.set_title(  Title[1])
    ax01.set_xlabel(Xtitle[1])
    ax01.set_ylabel(Ytitle[1])
    ax01.set_xlim(xrange)
    ax01.set_ylim(yrange)

    ax10.plot(x3,y3, "-")
    ax10.set_title(  Title[2])
    ax10.set_xlabel(Xtitle[2])
    ax10.set_ylabel(Ytitle[2])
    ax10.set_xlim(xrange)
    ax10.set_ylim(yrange)

    ax11.plot(x4,y4, "-")
    ax11.set_title(  Title[3])
    ax11.set_xlabel(Xtitle[3])
    ax11.set_ylabel(Ytitle[3])
    ax11.set_xlim(xrange)
    ax11.set_ylim(yrange)

    if show: plt.show()

    return f, ax00, ax01, ax10, ax11

def plot_surface(mymode, theta, psi, title="TITLE", xtitle="", ytitle="", ztitle="", legend=None, cmap=None,
                 figsize=None,show=1):
    """
    Plots a 2D surface given by an array mymode(theta, phi).

    Parameters
    ----------
    mymode : numpy array
        The 2D array with the surface to plot.
    theta : numpy array
        The array with X (H) values.
    psi : numpy array
        The array with Y (V) values.
    title : str, optional
        The main title.
    xtitle : str, optional
        The X title.
    ytitle : str, optional
        The Y title.
    ztitle : str, optional
        The main title.
    legend : str or list
        The legend for the different datasets.
    cmap : str, optional
        The matplotlib color map.
    figsize : tuple
        The matplotlib figure size.
    show : boolean, optional
        If True, executes plot_show() at the end.

    Returns
    -------
    tuple
        The matplotlib objects (fig, ax).

    """

    from matplotlib import cm
    from matplotlib.ticker import LinearLocator, FormatStrFormatter
    from mpl_toolkits.mplot3d import Axes3D

    ftheta, fpsi = np.meshgrid(theta, psi)
    fig = plt.figure(figsize=figsize)
    ax = fig.gca(projection='3d')

    II0 = mymode.T

    if cmap == None:
        cmap = cm.coolwarm

    print(II0.shape,ftheta.shape,fpsi.shape)
    surf = ax.plot_surface(ftheta, fpsi, II0, rstride=1, cstride=1, cmap=cmap,
                           linewidth=0, antialiased=False)

    ax.set_zlim(II0.min(),II0.max())
    ax.zaxis.set_major_locator(LinearLocator(10))
    ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
    fig.colorbar(surf, shrink=0.5, aspect=5)
    plt.title(title)
    ax.set_xlabel(xtitle)
    ax.set_ylabel(ytitle)
    ax.set_zlabel(ztitle)

    if show:
        plt.show()

    return fig, ax

def plot_scatter(x, y, show=1, nbins=100, xrange=None, yrange=None, plot_histograms=True, title="", xtitle="", ytitle=""):
    """
    makes a scatter plot with histograms.

    Parameters
    ----------
    x : numpy array
        x data array.
    y : numpy array
        y data array.
    show : boolean, optional
        If True, executes plot_show() at the end.
    nbins : int
        The number of bins for the histograms.
    xrange : tuple or list, optional
        the X range for all plots.
    yrange : tuple or list, optional
        the Y range for all plots.
    plot_histograms : boolean, optional
        Flag for plotting histograms: False or 0: plot no histograms;
            True or 1: plot both histograms;
            2: plot histograms vs abscissas only;
            3: plot histogram vs ordinates only.
    title : str, optional
        The main title.
    xtitle : str, optional
        The X title.
    ytitle : str, optional
        The Y title.

    Returns
    -------
    tuple
        The matplotlib objects (fig, axScatter).

    """
    from matplotlib.ticker import NullFormatter

    # the random data

    nullfmt   = NullFormatter()         # no labels

    # definitions for the axes

    if plot_histograms:
        left, width    = 0.1, 0.65
        bottom, height = 0.1, 0.65
        bottom_h = left_h = left+width+0.02
        rect_scatter = [left, bottom, width, height]
        rect_histx   = [left, bottom_h, width, 0.2]
        rect_histy   = [left_h, bottom, 0.2, height]
    else:
        left, width    = 0.1, 0.8
        bottom, height = 0.1, 0.8
        rect_scatter = [left, bottom, width, height]

    # start with a rectangular Figure
    fig = plt.figure(figsize=(8,8))

    axScatter = plt.axes(rect_scatter)
    if plot_histograms:
        if plot_histograms == 2:
            axHistx = plt.axes(rect_histx)
            axHistx.xaxis.set_major_formatter(nullfmt)
        elif plot_histograms == 3:
            axHisty = plt.axes(rect_histy)
            axHisty.yaxis.set_major_formatter(nullfmt)
        else:
            axHistx = plt.axes(rect_histx)
            axHisty = plt.axes(rect_histy)
            # no labels
            axHistx.xaxis.set_major_formatter(nullfmt)
            axHisty.yaxis.set_major_formatter(nullfmt)

    # now determine nice limits by hand:
    binwidth = np.array([x.max() - x.min(), y.max() - y.min()]).max() / nbins

    if xrange == None:
        xrange = np.array((x.min(), x.max()))
    if yrange == None:
        yrange = np.array((y.min(), y.max()))

    # the scatter plot:
    axScatter.scatter(x, y, marker='.', edgecolor='b', s=.1)

    axScatter.set_xlabel(xtitle)
    axScatter.set_ylabel(ytitle)
    if not plot_histograms:
        axScatter.set_title(title)

    axScatter.set_xlim( xrange )
    axScatter.set_ylim( yrange )

    if plot_histograms:
        bins_x = np.arange(xrange[0], xrange[1] + binwidth, binwidth)
        if plot_histograms == 2:
            axHistx.hist(x, bins=nbins)
            axHistx.set_xlim( axScatter.get_xlim() )
            axHistx.set_title(title)
        elif plot_histograms == 3:
            axHisty.hist(y, bins=nbins, orientation='horizontal')
            axHisty.set_ylim( axScatter.get_ylim() )
        else:
            axHistx.hist(x, bins=nbins, range=xrange)
            axHisty.hist(y, bins=nbins, range=yrange, orientation='horizontal')

            axHistx.set_xlim( axScatter.get_xlim() )

            axHistx.set_title(title)
            axHisty.set_ylim( axScatter.get_ylim() )

    if show: plt.show()

    if plot_histograms:
        if plot_histograms == 2:
            return fig,axScatter,axHistx
        elif plot_histograms == 3:
            return fig,axScatter,axHisty
        else:
            return fig,axScatter,axHistx,axHisty
    else:
        return fig, axScatter

def plot_contour(z, x, y, title="TITLE", xtitle="", ytitle="", xrange=None, yrange=None, plot_points=0, contour_levels=20,
                 cmap=None, cbar=True, fill=False, cbar_title="", figsize=None, show=1):
    """
    Plot a contour curves plot from data z(x,y).

    Parameters
    ----------
    z : numpy array
        The 2D data to plot.
    x : numpy array
        The 1D data for X axis.
    y : numpy array
        The 1D data for Y axis.
    title : str, optional
        The main title.
    xtitle : str, optional
        The X title.
    ytitle : str, optional
        The Y title.
    xrange : tuple or list, optional
        the X range for all plots.
    yrange : tuple or list, optional
        the Y range for all plots.
    plot_points : int, optional
        The number of ppoints for the contours.
    contour_levels : int, optional
        The number of contour levels.
    cmap : str, optional
        The matplotlib color map.
    cbar : boolean, optional
        Flag to plot the color table.
    fill : boolean, optional
        Flag to fill the contours.
    cbar_title : str, optional
        The titles for the contours.
    figsize : tuple
        The matplotlib figure size.
    show : boolean, optional
        If True, executes plot_show() at the end.

    Returns
    -------
    instance of plt.contourf
        The matplotlib plt.contourf instance.

    """

    fig = plt.figure(figsize=figsize)

    if fill:
        fig = plt.contourf(x, y, z.T, contour_levels, cmap=cmap, origin='lower')
    else:
        fig = plt.contour( x, y, z.T, contour_levels, cmap=cmap, origin='lower')

    if cbar:
        cbar = plt.colorbar(fig)
        cbar.ax.set_ylabel(cbar_title)

    plt.title(title)
    plt.xlabel(xtitle)
    plt.ylabel(ytitle)

    # the scatter plot:
    if plot_points:
        axScatter = plt.subplot(111)
        axScatter.scatter( np.outer(x,np.ones_like(y)), np.outer(np.ones_like(x),y))

    # set axes range
    plt.xlim(xrange)
    plt.ylim(yrange)

    if show:
        plt.show()

    return fig


import pandas as pd
import plotly.express as px

import dash
import dash_table
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

app = dash.Dash(__name__)
app.title = '2021 PTT Baseball 文章標題時序圖'
server = app.server

df = pd.read_csv('news1year.csv',encoding='utf-8')

def get_daily(title):
  #df = pd.read_csv('news1year.csv',encoding='utf-8')
  # 找出 df 中含有給定標題的那幾列的列數
  # fubon_row = df['title'].str.contains(title)
  # 取出 df 中含有給定標題的那幾列
  df_fubon = df.loc[df['title'].str.contains(title)]
  # 將含有給定標題的文章依 date 分類
  df_fubon_date = df_fubon.groupby("date")
  # 將含有給定標題的文章依 date 分類的結果取出
  fubon_article = {
      'article_n': df_fubon_date.size()
  }
  fubon_article = pd.DataFrame(fubon_article).reset_index()
  # Mr. Chateau is so handsome
  fubon_article['date'] = pd.to_datetime(fubon_article['date'], format="%m/%d").dt.date
  fubon_article['date'] = fubon_article['date'].astype('str').str.replace('1900-', '')
  # 創造一年 365 天的數列
  date0 = pd.date_range('2021-01-01', periods = 365)
  date1 = date0.strftime("%Y-%m-%d").str.replace('2021-', '')
  date = {
      'date': date1
  }
  date = pd.DataFrame(date)
  # # 彙整出每日含有給定標題的文章篇數
  fubon_daily = pd.merge(date, fubon_article, on = 'date', how = 'left').sort_values(by = 'date')
  fubon_daily['article_n'] = fubon_daily['article_n'].fillna(0)
  fubon_daily['date'] = pd.to_datetime(fubon_daily['date'], format="%m-%d").dt.date
  for i in list(range(0, len(fubon_daily['date']))):
    fubon_daily['date'][i] = fubon_daily['date'][i].replace(year = 2021)
  return(fubon_daily)

instruction = ['請於下方文字格輸入欲搜尋之關鍵字', '若欲同時搜尋多個詞，請用 | 符號作為間隔，例如：富邦|悍將|邦邦', '若欲搜尋特定文章分類，請用 \[ 及 \] 將其包住，例如：\[炸裂\]', '將游標移至時序圖線上即可顯示該日期之文章篇數', '單線圖關鍵字預設值為所有文章標題，而雙線則為王柏融及吳念庭，更改字詞時序圖即會自動更新']

app.layout = html.Div([

  html.H1(
        children='2021 PTT Baseball 文章標題時序圖',
        style={
            'textAlign': 'center'
        }
    ),

    html.Div(

        children=[
            html.P('使用說明：'),
            html.Ul(id='list', children=[html.Li(i) for i in instruction])
        ]
    ),

    html.Hr(),

    html.Br(),

    dcc.Input(
            id="input_keywords", type="text", placeholder="請輸入關鍵字", value = ''
        ),

    html.Br(),

    html.Br(),

    #dcc.Input(id='username', value='Initial Value', type='text'),
    #html.Button(id='submit-button', type='button', children='Submit'),

    html.Br(),

    html.Div(id='output_container', children=[]),

    html.Br(),

    dcc.Graph(id = 'output_daily', figure={}),

    ####################################################

    html.Hr(),

    html.Br(),

    html.P('第一個關鍵字：'),
    dcc.Input(
            id="input_keywords_d_1", type="text", placeholder="請輸入關鍵字", value = '王柏融|大王'
        ),

    html.Br(),

    html.P('第二個關鍵字：'),
    dcc.Input(
            id="input_keywords_d_2", type="text", placeholder="請輸入關鍵字", value = '吳念庭'
        ),

    html.Br(),

    html.Br(),

    #dcc.Input(id='username', value='Initial Value', type='text'),
    #html.Button(id='submit-button', type='button', children='Submit'),

    #html.Br(),

    #html.Div(id='output_container_2', children=[]),

    html.Br(),

    dcc.Graph(id = 'output_daily_2', figure={})

])

@app.callback(
    [
      Output(component_id = 'output_container', component_property = 'children'),
      Output(component_id = 'output_daily', component_property = 'figure'),
      Output(component_id = 'output_daily_2', component_property = 'figure')
        ],
    [
     #Input(component_id = 'submit-button', component_property = 'n_clicks'),
     Input(component_id = 'input_keywords', component_property = 'value'),
     Input(component_id = 'input_keywords_d_1', component_property = 'value'),
     Input(component_id = 'input_keywords_d_2', component_property = 'value')
     ]
      )

#def update_output(clicks, keywords):
def update_output(keywords, keywords_d_1, keywords_d_2):
  #if clicks is not None:
    #print(clicks, keywords)

    data = get_daily(keywords)
    container = '該關鍵字共有： {}'.format(int(data['article_n'].sum())) + ' 篇文章'

    line_chart = px.line()
    line_chart.add_scatter(x=data['date'], y=data['article_n'])
    line_chart.update_layout(title='2021 PTT Baseball 文章數互動時序圖',
                   xaxis_title='日期',
                   yaxis_title='文章數')

    data_d_1 = get_daily(keywords_d_1)
    data_d_2 = get_daily(keywords_d_2)

    line_chart_2 = px.line()
    line_chart_2.add_scatter(x=data_d_1['date'], y=data_d_1['article_n'], name = keywords_d_1)
    line_chart_2.add_scatter(x=data_d_2['date'], y=data_d_2['article_n'], name = keywords_d_2)
    line_chart_2.update_layout(title='2021 PTT Baseball 文章數互動雙線時序圖',
                   xaxis_title='日期',
                   yaxis_title='文章數')

    return container, line_chart, line_chart_2

if __name__ == '__main__':
    app.run_server(debug=True)


#!/usr/bin/env python3

# Implementación básica del algoritmo de compresión LZ78
# Fernando Peña Bes
# Universidad de Zaragoza, diciembre de 2020

import sys
import getopt
import os
import math

from bitarray import bitarray

# Codifica mediante el algoritmo LZ78 todos los bytes que hay en <stream>
# y los escribe en <out>
def encode(stream, out):
    # La compresión consigue reemplazando ocurrencias repetidas
    # con referencias a un diccionario, que se construye conforme
    # se lee la entrada

    # Cada entrada del diccionario es de la forma
    #   dictionary[(index, byte)] = n
    # El valor 'n' es el índice de la entrada, 'index' es un índice a una
    # entrada previa del diccionario (index < n), y 'byte' es un byte que se
    # concatena el prefijo representado (recursivamente) por la entrada previa
    # para obtener la cadena correspondiente a la entrada actual.

    dictionary = dict()

    # El índice 0 indica el prefijo vacío
    last_matching_index = 0
    next_available_index = 1

    while True:
        # leer un byte
        byte = stream.read(1)

        # Si la entrada ha terminado, el algoritmo para
        if not byte:
            if last_matching_index != 0:
                # Si el último byte ha hecho match con una entrada
                # del diccionario, se deja el campo del byte vacío
                dictionary[(last_matching_index, None)] = next_available_index
            break

        # Para cada carácter que leemos, se busca un match en el dicionario de
        # la forma '(last matching index, byte)'
        if (last_matching_index, byte) in dictionary:
            # Si se encuentra un match, se asigna a 'last_matching_index' el índice de esa entrada
            last_matching_index = dictionary[(last_matching_index, byte)]
        else:
            # Si no, se crea una nueva entrada, se resetea 'last_matching_index'
            # e incrementa 'next_available_index'
            dictionary[(last_matching_index, byte)] = next_available_index
            last_matching_index = 0
            next_available_index += 1

    # Crear array de bits a partir del diccionario
    a = bitarray()

    # Se asume que el diccionario mantiene el orden de inserción
    for n_phrase, (i, s) in enumerate(dictionary.keys(), start=1):
        # Añadir número con el mínimo número de bits posible
        number_size = math.ceil(math.log(n_phrase, 2))
        num = bin(i)[2:].zfill(number_size)
        if n_phrase != 1:
            a.extend(num)
        # Añadir byte
        if s != None:
            a.frombytes(s)

    # Añadir padding de ceros para que la longitud del bitarray sea múltiplo de 8
    a.fill()

    # Escribir el bitarray en la salida
    a.tofile(out)

# Decodifica mediante el algoritmo LZ78 los bytes leídos de <stream>
# y los escribe en <out>
def decode(stream, out):
    # Leer entrada
    phrases = dict()
    a = bitarray()
    a.fromfile(stream)

    number_size = 1
    pointer = 0
    iteration = 1

    while pointer + number_size - 1 < len(a):
        if iteration == 1:
            i = 0
        else:
            # Leer número
            i = a[pointer:pointer + number_size].to01()
            i = int(i, 2)
            pointer += number_size

        # Intentar leer byte
        if i != 0 and pointer + 8 > len(a):
            # Caso en el que no hay byte en la última pareja
            out.write(phrases[i])
            break

        s = a[pointer:pointer+8].tobytes()
        pointer += 8

        if i != 0:
            # Buscar la frase en el diccionario
            p = phrases[i] + s
        else:
            p = s

        phrases[iteration] = p

        iteration += 1
        number_size = math.ceil(math.log(iteration, 2))

        out.write(p)

def print_help(full):
    if full:
        print("""Compresor/Descompresor Lempel-Ziv 78

    Fernando Peña Bes
    Universidad de Zaragoza, diciembre de 2020
""")

    print(
        """Uso:
    lz78 -{c, d} [opciones]

Opciones:
    -f fichero_entrada    Indicar el fichero de entrada
    -o fichero_salida     Indicar el fichero de salida
    -h                    Mostrar ayuda """)

    if full:
        print(
            """
La opción -c comprime un fichero. Si no se indica un fichero de salida, se
guarda en un nuevo fichero con el mismo nombre que el de entrada más la
extensión .lz.

La opción -d restaura un fichero comprimido a su estado inicial y lo renombra
eliminado la extensión .lz. Si el fichero de entrada no tiene la extensión
.lz, el programa termina con un error y sin realizar ningún cambio.

Si no se especifican ficheros o el nombre los ficheros es -, la entrada
estándar se comprime o descomprime en la salida estándar. """)

def main(argv):
    # Obtener argumentos

    input_file = ''
    output_file = ''

    operation = None

    try:
        opts, trail_args = getopt.getopt(
            argv, "hcdf:o:", ["help", "compress", "decompress", "file=", "out="])
    except getopt.GetoptError:
        print_help(False)
        sys.exit(2)

    for opt, arg in opts:
        if opt in ("-h", "--help"):
            print_help(True)
            sys.exit(0)
        elif opt in ("-c", "--compress"):
            operation = encode
        elif opt in ("-d", "--decompress"):
            operation = decode
        elif opt in ("-f", "--file"):
            input_file = arg
        elif opt in ("-o", "--output"):
            output_file = arg

    if not operation or trail_args:
        print_help(False)
        sys.exit(2)

    # Procesar ficheros de entrada y salida

    # Entrada
    try:
        if input_file == '' or input_file == '-':
            # Usar entrada estándar
            input_stream = os.fdopen(sys.stdin.fileno(), "rb", closefd=False)
        else:
            # Comprobar que la extensión al decodificar es .lz
            if operation == decode:
                extension = os.path.splitext(input_file)[1]
                if extension != ".lz":
                    print("El fichero de entrada no tiene extensión .lz")
                    sys.exit(1)
            input_stream = open(input_file, "rb")
    except IOError:
        print("No se ha podido abrir la entrada")
        sys.exit(1)

    # Salida
    try:
        if input_file != '' and input_file != '-' and output_file == '':
            if operation == encode:
                # Concatenar al fichero de entrada .lz
                output_stream = open(input_file + ".lz", "wb")
            elif operation == decode:
                # Eliminar la extensión .lz del fichero de entrada
                output_file = os.path.splitext(input_file)[0]
                output_stream = open(output_file, "wb")
        elif output_file != '' and output_file != '-':
            if operation == encode:
                # Si en el fichero a comprimir no se ha puesto la extensión .lz, se añade
                extension = os.path.splitext(output_file)[1]
                if extension != ".lz":
                    output_file += ".lz"
            output_stream = open(output_file, "wb")
        else:
            # Usar salida estándar
            output_stream = os.fdopen(sys.stdout.fileno(), "wb", closefd=False)
    except IOError:
        print("No se ha podido abrir la salida")
        sys.exit(1)

    # Ejecutar la operación
    try:
        operation(input_stream, output_stream)
    except Exception as e:
        if operation == encode:
            print("Ha habido un error durante la compresión")
        elif operation == decode:
            print("Ha habido un error durante la descompresión")

    input_stream.close()
    output_stream.close()

if __name__ == "__main__":
    main(sys.argv[1:])


from ray.rllib.algorithms.sac import SAC
from ray.tune.registry import register_env
from gym_cooking.environment.environment import GymCookingEnvironment

n_agents = 1
num_humans = 1
render = False

level = 'open_room_interaction2'
seed = 1
record = False
max_num_timesteps = 100
recipe = "CarrotBanana"
action_scheme = "scheme3"

env_config = {
    "level": level, "record": record, "max_steps": max_num_timesteps,
    "recipe": recipe, "action_scheme": action_scheme, "obs_spaces": ["feature_vector"],
    "ghost_agents": 1
}

def env_creator(env_conf):
    return GymCookingEnvironment(**env_config)  # return an env instance

register_env("my_env", env_creator)

config = {
    "env": "my_env",
    "num_workers": 0,
    "framework": "torch",
    "model": {
        "fcnet_hiddens": [128, 128],
        "fcnet_activation": "relu",
    },
    "evaluation_config": {
        "render_env": False,
    }
}

algo = SAC(config=config)

for _ in range(10000):
    stuff = algo.train()
    interest_keys = ["episode_reward_max", "episode_reward_min", "episode_reward_mean", "episode_len_mean",
                     "episodes_this_iter", "episodes_total", "num_agent_steps_sampled"]
    filtered_stuff = {k: stuff[k] for k in interest_keys}
    print(filtered_stuff)

algo.evaluate()