import copy
import math
import random
import typing

# type: ignore
import randomhash

DEFAULT_K = 100
DEFAULT_W = 1000
DEFAULT_M = 3
DEFAULT_DEBUG = True

class DebugStatistics:

    def __init__(
        self,
        initial_value: int = 0,
        debug: bool = None,
    ):
        self._debug = debug if debug is not None else DEFAULT_DEBUG

        if self._debug:
            self._data = dict()
            self._initial_value = initial_value

    @property
    def data(self) -> typing.Dict[str, int]:
        if self._debug:
            # return dict with sorted keys
            return {
                key: self._data[key]
                for key in sorted(self._data)
            }

    def get(self, key: str) -> int:
        if self._debug:
            return self._data.get(key, self._initial_value)

    def update(self, key, increment) -> int:
        if self._debug:
            self._data[key] = (
                self._data.get(key, self._initial_value) +
                increment
            )
            return self._data[key]

# Standard Affirmative Sampling implemented in class form.

class AffSample:

    def __init__(
        self,
        k: int = DEFAULT_K,
        seed: typing.Optional[int] = None,
        debug: bool = None,
    ):
        self._k = k
        self._prng = randomhash.RandomHashFamily(count=1, seed=seed)

        # three data structures to store the sample
        self._sample_core = set()
        self._sample_xtra = set()
        self._sample_freqs = dict()

        # cached thresholds for the sample
        self._threshold_core_kth = None
        self._threshold_core_kth_z = None
        self._threshold_xtra_min = None
        self._threshold_xtra_min_z = None

        # DEBUG: statistic tracker
        self._debug = debug if debug is not None else DEFAULT_DEBUG
        self._stats = DebugStatistics()

    def _hash(self, z: str) -> int:
        return self._prng.hash(z)

    def _hash_preimage_pair(self, z: str) -> typing.Tuple[int, str]:
        return (self._hash(z), z)

    def _update_thresholds(self):
        # DEBUG: check invariants
        self._check_invariants()

        # compute the thresholds for the sample
        kth_hash, z_kth_hash = min(map(self._hash_preimage_pair, self._sample_core))
        min_hash, z_min_hash = (
            min(map(self._hash_preimage_pair, self._sample_xtra))

            if len(self._sample_xtra) > 0 else

            # if xtra is empty, the threshold to get in is the kth element hash
            (kth_hash, z_kth_hash)
        )

        # update internal values
        self._threshold_core_kth = kth_hash
        self._threshold_core_kth_z = z_kth_hash
        self._threshold_xtra_min = min_hash
        self._threshold_xtra_min_z = z_min_hash

    @property
    def threshold_core_kth(self) -> int:
        if self._threshold_core_kth is None:
            self._update_thresholds()
        return self._threshold_core_kth

    @property
    def threshold_core_kth_z(self) -> str:
        if self._threshold_core_kth_z is None:
            self._update_thresholds()
        return self._threshold_core_kth_z

    @property
    def threshold_xtra_min(self) -> int:
        if self._threshold_xtra_min is None:
            self._update_thresholds()
        return self._threshold_xtra_min

    @property
    def threshold_xtra_min_z(self) -> str:
        if self._threshold_xtra_min_z is None:
            self._update_thresholds()
        return self._threshold_xtra_min_z

    def _check_invariants(self):
        if self._debug:

            # sample core should always contain at most k elements
            assert len(self._sample_core) <= self._k

            # sample xtra should never contain any element unless sample
            # core is already filled
            assert len(self._sample_xtra) == 0 or len(self._sample_core) == self._k

            if len(self._sample_xtra) > 0:
                # the largest element of sample xtra should be smaller than the
                # smallest element of sample core
                assert max(map(self._hash, self._sample_xtra)) < min(map(self._hash, self._sample_core))

                # the sets should have no element in common
                assert self._sample_xtra.isdisjoint(self._sample_core)

            # every element of sample freqs should be in the sample
            for z in self._sample_freqs.keys():
                assert (
                    (z in self._sample_core and z not in self._sample_xtra) or
                    (z not in self._sample_core and z in self._sample_xtra)
                )

            # every element of sample core should be in sample freqs
            for z in self._sample_core:
                assert z in self._sample_freqs

            # every element of sample xtra should be in sample freqs
            for z in self._sample_xtra:
                assert z in self._sample_freqs

    def process(
        self,
        z: typing.Any,
    ):

        # DEBUG: update statistics
        self._stats.update('total_tokens', 1)

        # DEBUG: check invariants
        self._check_invariants()

        # ================================================================
        # (A1) if z is already in the sample (and therefore no need to hash it)
        if z in self._sample_core or z in self._sample_xtra:
            # update frequency
            # NOTE: this is the place we would update any other stat of z
            self._sample_freqs[z] = self._sample_freqs.get(z, 0) + 1

            # DEBUG: update statistics
            self._stats.update('case_A1', 1)
            self._stats.update('sample_tokens', 1)

            # since no new element is added, just a repetition, no other
            # update to the data structure is need -> END(added)

            return True

        # (A2) if the core sample is not filled, add z to the core sample
        if len(self._sample_core) < self._k:
            self._sample_core.add(z)
            self._sample_freqs[z] = 1

            # DEBUG: update statistics
            self._stats.update('case_A2', 1)
            self._stats.update('sample_tokens', 1)
            self._stats.update('sample_unique_tokens', 1)

            # new element added, update thresholds -> END(added)
            self._update_thresholds()

            return True

        # ================================================================
        # (B) z is not in the sample + the core sample is filled

        # so now, if the hash of z is not provided, compute it
        z_hash = self._prng.hash(z)

        # (B.1) if hash(z) is smaller than the min hash value in S
        if z_hash < self.threshold_xtra_min:
            # DISCARD = ignore the element entirely

            # DEBUG: update statistics
            self._stats.update('case_B1', 1)

            # END(dropped)
            return False

        # (B.2) if hash(z) is larger than the k-th largest hash value in S
        # hash(z) is a new k-record, needs not to be larger than the largest in the sample!
        elif z_hash > self.threshold_core_kth:
            # EXPAND = the total size sample grows by one
            # (but sample_core remains at size k)

            # add z to S
            self._sample_core.add(z)
            self._sample_freqs[z] = 1

            # move z_kth_hash from core to xtra
            self._sample_xtra.add(self._threshold_core_kth_z)
            self._sample_core.remove(self._threshold_core_kth_z)

            # DEBUG: update statistics
            self._stats.update('case_B2', 1)
            self._stats.update('sample_tokens', 1)
            self._stats.update('sample_unique_tokens', 1)

            # new element added, update thresholds -> END(added)
            self._update_thresholds()

            return True

        # (B.3) otherwise z replaces the element z* with min. hash value
        # sample_xtra must contain z_min_hash otherwise we have a contradiction
        else:
            # REPLACE = the size of the sample does not change but we
            # make sure to keep the largest elements

            # DEBUG: should never happen (right?)
            assert len(self._sample_xtra) > 0

            # remove the element with min. hash value
            self._sample_xtra.remove(self._threshold_xtra_min_z)
            token_count_removed = self._sample_freqs[self._threshold_xtra_min_z]
            del self._sample_freqs[self._threshold_xtra_min_z]

            # and replace by the current element z
            self._sample_xtra.add(z)
            self._sample_freqs[z] = 1

            # DEBUG: update statistics
            self._stats.update('case_B3', 1)
            self._stats.update('sample_tokens', 1 - token_count_removed)
            self._stats.update('sample_replaced_tokens', 1)

            # new element added, update thresholds -> END(added)
            self._update_thresholds()

            return True

    def remove(self, z: str) -> typing.NoReturn:
        assert z in self._sample_freqs, "z not in sample"

        # DEBUG: check invariants
        self._check_invariants()

        if z in self._sample_xtra:
            # if the element is in the xtra, remove it from there
            self._sample_xtra.remove(z)

        elif z in self._sample_core:
            # if the element is in the core, remove it
            self._sample_core.remove(z)

            # but since the core should not be depleted if avoidable
            # transfer the largest element of the xtra to the core
            if len(self._sample_xtra) > 0:
                max_hash, z_max_hash = max(map(self._hash_preimage_pair, self._sample_xtra))
                self._sample_xtra.remove(z_max_hash)
                self._sample_core.add(z_max_hash)

        del self._sample_freqs[z]

        # element removed, recompute thresholds
        self._update_thresholds()

    @property
    def size(self) -> int:
        return len(self._sample_core) + len(self._sample_xtra)

    @property
    def sample(self) -> typing.Dict[str, int]:
        return copy.deepcopy(self._sample_freqs)

    @property
    def statistics(self) -> typing.Dict[str, int]:
        return self._stats.data

    def contains(self, z: str) -> bool:
        return z in self._sample_freqs

    @property
    def ready(self) -> bool:
        return self.size >= self._k

    @property
    def cardinality_estimate(self) -> int:
        # KMV formula
        return (len(self._sample_freqs)-1)/(1-randomhash.int_to_real(self._threshold_xtra_min))

# WindowedV1: same as AffSamp, but filtering outdated values at every step.
#
# => Problem is that the size of the sample gets stuck around ~k, because
# the thresholds (that control the size) are too high and never lowered once
# elements raise them.

class WindowedV1AffSample(AffSample):

    def __init__(
        self,
        k: int = DEFAULT_K,
        w: int = DEFAULT_W,
        seed: typing.Optional[int] = None,
        debug: bool = None,
    ):
        super().__init__(k=k, seed=seed, debug=debug)

        # windowed stuff
        self._time = 0
        self._w = w
        self._latest_timestamp = dict()

    def remove(self, z: str):
        super().remove(z)
        del self._latest_timestamp[z]

    def _clear_outdated(self):

        # making copy so it can be updated in the loop
        elements_to_consider = list(self._latest_timestamp.items())[:]

        for z, timestamp in elements_to_consider:

            if timestamp < self._time - self._w and self.contains(z):
                self.remove(z)

    def process(
        self,
        z: typing.Any,
    ):
        self._clear_outdated()
        is_added = super().process(z=z)
        if is_added:
            self._latest_timestamp[z] = self._time
        self._time += 1

    @property
    def w(self):
        return self._w

# WindowedV2: we have two overlapping subsamples MAIN and AUXI, and we
# swap when we use each at every W step of time. The idea is that this
# will ensure that the thresholds for CORE/XTRA are only calculated on
# recent values.
#
# => Problem is that there are some *small* periodic fluctuations of
# the size of the sample (in sawtooth) and *large* periodic fluctuations
# of the cardinality estimate with KMV.
#
# The large discrepancy of the cardinality estimate comes from the fact
# that we were capturing elements in subwindows of WindowedV2.

class WindowedV2AffSample:

    def __init__(
        self,
        k: int = DEFAULT_K,
        w: int = DEFAULT_W,
        seed: typing.Optional[int] = None,
        debug: bool = None,
    ):
        self._k = k
        self._w = w

        # since we have multiple samples, they need to all use the
        # same random number generator and hash function --- we use
        # the user-provided seed (that can be None) as a source
        self._master_seed = random.Random(x=seed).randint(0, 2**32-1)

        # DEBUG: statistic tracker
        self._debug = debug if debug is not None else DEFAULT_DEBUG
        self._stats = DebugStatistics()

        self._main_sample = AffSample(k=self._k, seed=self._master_seed, debug=self._debug)
        self._auxi_sample = AffSample(k=self._k, seed=self._master_seed, debug=self._debug)
        self._time = 0

    def process(
        self,
        z: str,
    ):
        if self._time % self._w == 0:
            # swap main and auxi samples
            self._main_sample = self._auxi_sample
            self._auxi_sample = AffSample(k=self._k, debug=self._debug)

        self._main_sample.process(z=z)
        self._auxi_sample.process(z=z)

        self._time += 1

    @property
    def sample(self):
        return self._main_sample.sample

    @property
    def statistics(self) -> typing.Dict[str, int]:
        data = dict()
        data.update({
            "{}.{}".format("main", key): value
            for key, value in self._main_sample.statistics.items()
        })
        data.update({
            "{}.{}".format("auxi", key): value
            for key, value in self._auxi_sample.statistics.items()
        })
        return data

    @property
    def w(self):
        return self._w

    @property
    def size(self) -> int:
        return len(self.sample)

    def contains(self, z: str) -> bool:
        return z in self.sample

    @property
    def cardinality_estimate(self) -> int:
        return self._main_sample.cardinality_estimate

# WindowedV3: To address/smooth the periodic fluctuations of V2
# that uses TWO samples, we extend the idea (and tweak it) so that
# it can use $m$ samples with k'=k/m (with simpler scheduling),
# which we constantly rotate in round robin, and which we merge to
# provide a sample of the window.
#
# => While this satisfactorily smoothes the size of the overall
# sample, it considerably degrades the cardinality estimation,
# because KMV requires us to know the exact rank of the hash
# value used to compute the estimate — and when we merge the
# local minima of each subsample, we lose the information of
# what rank that element is at.

class WindowedV3AffSample:

    def __init__(
        self,
        k: int = DEFAULT_K,
        w: int = DEFAULT_W,
        m: int = DEFAULT_M,
        seed: typing.Optional[int] = None,
        debug: bool = None,
    ):
        self._k = k # size of the core
        self._w = w # size of the window
        self._m = m # number of subsamples

        # since we have multiple samples, they need to all use the
        # same random number generator and hash function --- we use
        # the user-provided seed (that can be None) as a source
        self._master_seed = random.Random(x=seed).randint(0, 2**32-1)

        # DEBUG: statistic tracker
        self._debug = debug if debug is not None else DEFAULT_DEBUG
        self._stats = DebugStatistics()

        self._samples = [
            AffSample(
                k=self._sub_k,
                seed=self._master_seed, # each sample uses the same seed
                debug=self._debug
            )
            for j in range(self._m)
        ]
        self._time = 0
        self._latest_timestamp = dict()

    @property
    def _sub_k(self):
        # size of the core of the subsample
        return math.floor(self._k/self._m)

    @property
    def _delta(self):
        # time interval for a bucket
        return math.floor(self._w/self._m)

    def process(
        self,
        z: str,
    ):

        # find interval based on time (i is the previous bucket)
        i = math.floor((self._time-1)/self._delta) % self._m
        j = math.floor(self._time/self._delta) % self._m

        # flush if we are starting a new bucket (i != j)
        if i != j:
            # "flush"
            self._samples[j] = AffSample(k=self._sub_k, debug=self._debug)

        # add element to subsample
        is_added = self._samples[j].process(z=z)

        # if element added, update timestamp
        if is_added:
            self._latest_timestamp[z] = self._time

        # increase time step
        self._time += 1

    def _is_timestamp_in_window(self, ts: int) -> bool:
        return ts >= self._time - self._w

    @property
    def sample(self):

        # find the oldest bucket
        j_start = (math.floor(self._time/self._delta) + 1) % self._m

        merged_sample = dict()

        # filter outdated elements in the oldest sample
        for z, freq in self._samples[j_start].sample.items():
            if self._is_timestamp_in_window(self._latest_timestamp[z]):
                merged_sample[z] = freq

        # merge other samples
        for i in range(1, self._m):

            # select the right bucket
            j = (j_start + i) % self._m

            # merge with existing sample
            for z, freq in self._samples[j].sample.items():
                if z in merged_sample:
                    merged_sample[z] += freq
                else:
                    merged_sample[z] = freq

        return merged_sample

    @property
    def statistics(self) -> typing.Dict[str, int]:
        data = dict(self._stats.data)
        for i in range(self._m):
            data.update({
                "{}.{}".format("s{}".format(i), key): value
                for key, value in self._samples[i].statistics.items()
            })
        return data

    @property
    def w(self):
        return self._w

    @property
    def size(self) -> int:
        return len(self.sample)

    def contains(self, z: str) -> bool:
        return z in self.sample

    @property
    def cardinality_estimate(self) -> int:

        # merge minima
        mins = list(filter(lambda x: x is not None, [ s._threshold_xtra_min for s in self._samples ]))
        if len(mins) == 0:
            return 0

        all_min = min(mins)

        # KMV formula
        return (len(self.sample)-1)/(1-randomhash.int_to_real(all_min))

# WindowedV4: To address the problem of V3 with cardinality estimation, we try to
# compute different merged statistics. In V4, we look for the size of the smallest
# subsample, call this $r$. We know that in every subsample we have the $r$ largest
# elements to have ever appeared in that substream. Therefore we can find the
# $r$-th largest value and correctly identify it as the $r$-record. This can then
# be plugged into the KMV formula.

class WindowedV4AffSample:

    def __init__(
        self,
        k: int = DEFAULT_K,
        w: int = DEFAULT_W,
        m: int = DEFAULT_M,
        seed: typing.Optional[int] = None,
        debug: bool = None,
    ):
        self._k = k # size of the core
        self._w = w # size of the window
        self._m = m # number of subsamples

        # since we have multiple samples, they need to all use the
        # same random number generator and hash function --- we use
        # the user-provided seed (that can be None) as a source
        self._master_seed = random.Random(x=seed).randint(0, 2**32-1)

        # DEBUG: statistic tracker
        self._debug = debug if debug is not None else DEFAULT_DEBUG
        self._stats = DebugStatistics()

        self._samples = [
            AffSample(
                k=self._sub_k,
                seed=self._master_seed, # each sample uses the same seed
                debug=self._debug
            )
            for j in range(self._m)
        ]
        self._time = 0
        self._latest_timestamp = dict()

    @property
    def _sub_k(self):
        # size of the core of the subsample
        return math.floor(self._k/self._m)

    @property
    def _delta(self):
        # time interval for a bucket
        return math.floor(self._w/self._m)

    def process(
        self,
        z: str,
    ):

        # find interval based on time (i is the previous bucket)
        i = math.floor((self._time-1)/self._delta) % self._m
        j = math.floor(self._time/self._delta) % self._m

        # flush if we are starting a new bucket (i != j)
        if i != j:
            # "flush"
            self._samples[j] = AffSample(k=self._sub_k, debug=self._debug)

        # add element to subsample
        is_added = self._samples[j].process(z=z)

        # if element added, update timestamp
        if is_added:
            self._latest_timestamp[z] = self._time

        # increase time step
        self._time += 1

    def _is_timestamp_in_window(self, ts: int) -> bool:
        return ts >= self._time - self._w

    @property
    def sample(self):

        # find the oldest bucket
        j_start = (math.floor(self._time/self._delta) + 1) % self._m

        merged_sample = dict()

        # filter outdated elements in the oldest sample
        for z, freq in self._samples[j_start].sample.items():
            if self._is_timestamp_in_window(self._latest_timestamp[z]):
                merged_sample[z] = freq

        # merge other samples
        for i in range(1, self._m):

            # select the right bucket
            j = (j_start + i) % self._m

            # merge with existing sample
            for z, freq in self._samples[j].sample.items():
                if z in merged_sample:
                    merged_sample[z] += freq
                else:
                    merged_sample[z] = freq

        return merged_sample

    @property
    def statistics(self) -> typing.Dict[str, int]:
        data = dict(self._stats.data)
        for i in range(self._m):
            data.update({
                "{}.{}".format("s{}".format(i), key): value
                for key, value in self._samples[i].statistics.items()
            })
        return data

    @property
    def w(self):
        return self._w

    @property
    def size(self) -> int:
        return len(self.sample)

    def contains(self, z: str) -> bool:
        return z in self.sample

    def _purge_outdated(self, sample, with_frequencies=False):
        if with_frequencies:
            return {
                z: freq
                for z, freq in sample.items()
                if z in self._latest_timestamp and self._is_timestamp_in_window(self._latest_timestamp[z])
            }
        else:
            return [
                z
                for z in sample.keys()
                if z in self._latest_timestamp and self._is_timestamp_in_window(self._latest_timestamp[z])
            ]

    @property
    def cardinality_estimate(self) -> int:

        # THIS FUNCTION CONTAINS THE
        # ONLY DIFFERENCE WITH RESPECT TO V3

        # find $r$, the size of the smallest sample (only look at "ready" samples)
        candidates = list(map(lambda s: len(self._purge_outdated(s.sample)),
            filter(lambda s: s.ready, self._samples)))

        if len(candidates) == 0:
            return 0

        r = min(candidates)

        # avoid empty stuff
        if r == 0:
            return 0

        # find the $r$-th largest element in the merged sample S
        hashf = self._samples[0]._hash_preimage_pair # (assumes all samples have same hash)
        sorted_hashed_sample = sorted(map(hashf, self.sample), reverse=True)
        r_largest_hash, r_largest = sorted_hashed_sample[r-1]

        # KMV formula
        return (r-1)/(1-randomhash.int_to_real(r_largest_hash))

# WindowedV5: Another variant, like WindowedV4, to address the breakdown of
# KMV in the WindowedV3 algorithm, due to the fact that we do not keep track
# of the correct rank of the min of mins.
#
# In this V5, we take the max of the mins of the subsamples, and compute its
# rank in the merged sample. We can do this, because the max of the min will
# give the subsample that contains only elements larger than other unseen of
# elements. The merged element contains, for sure, all the elements in the
# window that have a hash value larger than this minimum, so we can compute
# the rank exactly (this is the dual idea to the approach in V4).

class WindowedV5AffSample:

    def __init__(
        self,
        k: int = DEFAULT_K,
        w: int = DEFAULT_W,
        m: int = DEFAULT_M,
        seed: typing.Optional[int] = None,
        debug: bool = None,
    ):
        self._k = k # size of the core
        self._w = w # size of the window
        self._m = m # number of subsamples

        # since we have multiple samples, they need to all use the
        # same random number generator and hash function --- we use
        # the user-provided seed (that can be None) as a source
        self._master_seed = random.Random(x=seed).randint(0, 2**32-1)

        # DEBUG: statistic tracker
        self._debug = debug if debug is not None else DEFAULT_DEBUG
        self._stats = DebugStatistics()

        self._samples = [
            AffSample(
                k=self._sub_k,
                seed=self._master_seed, # each sample uses the same seed
                debug=self._debug
            )
            for j in range(self._m)
        ]
        self._time = 0
        self._latest_timestamp = dict()

    @property
    def _sub_k(self):
        # size of the core of the subsample
        return math.floor(self._k/self._m)

    @property
    def _delta(self):
        # time interval for a bucket
        return math.floor(self._w/self._m)

    def process(
        self,
        z: str,
    ):

        # find interval based on time (i is the previous bucket)
        i = math.floor((self._time-1)/self._delta) % self._m
        j = math.floor(self._time/self._delta) % self._m

        # flush if we are starting a new bucket (i != j)
        if i != j:
            # "flush"
            self._samples[j] = AffSample(k=self._sub_k, debug=self._debug)

        # add element to subsample
        is_added = self._samples[j].process(z=z)

        # if element added, update timestamp
        if is_added:
            self._latest_timestamp[z] = self._time

        # increase time step
        self._time += 1

    def _is_timestamp_in_window(self, ts: int) -> bool:
        return ts >= self._time - self._w

    @property
    def sample(self):

        # find the oldest bucket
        j_start = (math.floor(self._time/self._delta) + 1) % self._m

        merged_sample = dict()

        # filter outdated elements in the oldest sample
        for z, freq in self._samples[j_start].sample.items():
            if self._is_timestamp_in_window(self._latest_timestamp[z]):
                merged_sample[z] = freq

        # merge other samples
        for i in range(1, self._m):

            # select the right bucket
            j = (j_start + i) % self._m

            # merge with existing sample
            for z, freq in self._samples[j].sample.items():
                if z in merged_sample:
                    merged_sample[z] += freq
                else:
                    merged_sample[z] = freq

        return merged_sample

    @property
    def statistics(self) -> typing.Dict[str, int]:
        data = dict(self._stats.data)
        for i in range(self._m):
            data.update({
                "{}.{}".format("s{}".format(i), key): value
                for key, value in self._samples[i].statistics.items()
            })
        return data

    @property
    def w(self):
        return self._w

    @property
    def size(self) -> int:
        return len(self.sample)

    def contains(self, z: str) -> bool:
        return z in self.sample

    def _purge_outdated(self, sample, with_frequencies=False):
        if with_frequencies:
            return {
                z: freq
                for z, freq in sample.items()
                if z in self._latest_timestamp and self._is_timestamp_in_window(self._latest_timestamp[z])
            }
        else:
            return [
                z
                for z in sample.keys()
                if z in self._latest_timestamp and self._is_timestamp_in_window(self._latest_timestamp[z])
            ]

    @property
    def cardinality_estimate(self) -> int:

        # THIS FUNCTION CONTAINS THE
        # ONLY DIFFERENCE WITH RESPECT TO V3/V4

        # all the minima of the samples
        all_mins = [
            s._threshold_xtra_min for s in self._samples
            if s._threshold_xtra_min is not None and
            self._is_timestamp_in_window(self._latest_timestamp[s._threshold_xtra_min_z])
        ]

        # find the max of the mins
        y_star = max(all_mins)

        # c = count # of elements in S > y_star
        hashf = self._samples[0]._hash # (assumes all samples have same hash)
        c = len(list(filter(lambda z: hashf(z) > y_star, self.sample)))

        # KMV formula (c is the same as (c+1 - 1) when you count y_star itself)
        return (c)/(1-randomhash.int_to_real(y_star))

class WindowedV4BAffSample(WindowedV4AffSample):

    @property
    def _delta(self):
        # time interval for a bucket
        return math.floor(self._w/(self._m - 1))

# next idea: try V4C to take non-empty size

class WindowedV5BAffSample(WindowedV5AffSample):

    @property
    def _delta(self):
        # time interval for a bucket
        return math.floor(self._w/(self._m - 1))

class WindowedSimulator:

    def __init__(self, waf, tokens):
        self._waf = waf
        self._tokens = tokens

        self._sample_size = []
        self._wcard = []
        self._wcard_est = []

    def run(self):
        for timestamp, token in enumerate(self._tokens):
            self._waf.process(token)

            # record stats
            self._sample_size.append(self._waf.size)
            self._wcard.append(len(set(self._tokens[max(0, timestamp - self._waf.w):timestamp])))
            self._wcard_est.append(self._waf.cardinality_estimate)

        return self

    def plot_size(self):
        import matplotlib.pyplot as plt
        plt.plot(self._sample_size)
        plt.show()

    def plot_card(self):
        import matplotlib.pyplot as plt
        plt.plot(self._wcard_est)
        plt.plot(self._wcard)
        plt.show()


import abc
from typing import Any
from .tensor import Tensor

class Function(abc.ABC):
    counter: int = 0
    def __init__(self):
        self.name = self.__class__.__name__
        self.order_no = Function.counter
        Function.counter += 1
        self.inputs = []
        self.saved_tensors = None

    @abc.abstractmethod
    def forward(self, *args) -> Tensor:
        """
        Compute given function. Only positional arguments are allowed. Returns a Tensor.
        """
        pass

    @abc.abstractmethod
    def backward(self, out) -> list[Tensor | None]:
        """
        Backpropagate through a function. Return a gradient for each of the inputs of forward().
        """
        pass

    def save_for_backward(self, *args):
        self.saved_tensors = args

    def apply_backward(self, src_gradient):
        input_grads = self.backward(src_gradient)
        assert len(input_grads) == len(self.inputs)

        for input, gradient in zip(self.inputs, input_grads):
            assert (input is None) <= (gradient is None)
        self.saved_tensors = None
        return input_grads

    def register_input(self, input: Tensor | None):
        self.inputs.append(input)

    def register_output(self, output: Tensor):
        self.output = output

    @classmethod
    def apply(cls, *args: tuple[Any]):
        fn = cls()
        out = fn.forward(*args)

        requires_grad = False
        for input in args:
            if isinstance(input, Tensor) and input.requires_grad:
                requires_grad = True
                fn.register_input(input)
            else:
                fn.register_input(None)
        fn.register_output(out)

        out.requires_grad = requires_grad
        out.grad_fn = fn if requires_grad else None

        return out

    def __repr__(self) -> str:
        return f"<{self.name}.{self.order_no}>"


from typing import Sequence

import pytest

from purify import purify

class Nest:
    def __init__(self, num_eggs: int):
        self.num_eggs = num_eggs

class Tree:
    def __init__(self, name: str, nests: Sequence[Nest] = ()):
        self.name = name
        self.nests = list(nests)

def _rename_tree(new_name: str, tree: Tree, *, double: bool = False) -> Tree:
    if double:
        new_name = new_name + new_name
    tree.name = new_name
    return tree

rename_tree = purify("tree")(_rename_tree)

def test_shallow_purify_works_with_top_level_attrs():
    tree = Tree("Ralph")

    mutated_tree = rename_tree("Felicity", tree)
    assert tree.name == "Ralph"
    assert mutated_tree.name == "Felicity"

    mutated_tree = rename_tree("Felicity", double=True, tree=tree)
    assert tree.name == "Ralph"
    assert mutated_tree.name == "FelicityFelicity"

def increment_nests(add: int, tree: Tree) -> Tree:
    for nest in tree.nests:
        nest.num_eggs += add
    return tree

def test_shallow_purify_does_not_deepcopy():
    shallow_increment_nests = purify("tree")(increment_nests)

    tree = Tree("Steve", nests=[Nest(2), Nest(3)])

    mutated_tree = shallow_increment_nests(10, tree)
    mutated_tree.name = "Bob"

    assert mutated_tree is not tree
    assert mutated_tree.nests is tree.nests
    assert mutated_tree.nests == tree.nests
    assert tree.nests[0].num_eggs == 12
    assert tree.name == "Steve"  # still doing shallow copy though

def test_deep_purify_does_deepcopy():
    deep_increment_nests = purify("tree", deep=True)(increment_nests)

    tree = Tree("Steve", nests=[Nest(2), Nest(3)])

    mutated_tree = rename_tree("George", deep_increment_nests(10, tree))

    assert mutated_tree is not tree
    assert mutated_tree.nests != tree.nests
    assert tree.nests[0].num_eggs == 2
    assert mutated_tree.nests[0].num_eggs == 12
    assert tree.name == "Steve"  # still doing shallow copy though

@purify("nest")
def increment_nest(add: int, nest: Nest) -> Nest:
    nest.num_eggs += add
    return nest

def test_purify_leveled():
    """Demonstrates the preferred leveled approach that makes shallow copy viable"""
    tree = Tree("Tim", nests=[Nest(4), Nest(5)])

    @purify("tree")
    def increment_nests(add: int, tree: Tree) -> Tree:
        tree.nests = [increment_nest(add, nest) for nest in tree.nests]
        return tree

    new_tree = increment_nests(3, tree)
    assert new_tree != tree
    assert tree.nests[0].num_eggs == 4
    assert new_tree.nests[0].num_eggs == 7

def test_autopurify():
    tree = Tree("Josh")

    rename_tree = purify(_rename_tree)

    mtree = rename_tree("Jeff", tree)
    assert mtree.name == "Jeff"
    assert tree.name == "Josh"

@purify
def awkward(name: str, age: int, size: int, empty: bool = False) -> str:
    if empty:
        return ""
    return f"{name} {age} {size}"

def test_awkward():
    assert "peter 10 13" == awkward("peter", 10, 13)

    with pytest.raises(TypeError):
        # make sure meaningful Python errors are raised for bad calls
        awkward("peter", 13, age=10)  # type: ignore
        # this one is illegal in standard python

    assert "peter 10 13" == awkward("peter", size=13, age=10)

    assert "" == awkward("peter", 8, 3, empty=True)
    assert "" == awkward("peter", 8, 3, True)

def test_autopurify_deep():
    """Pick the last positional argument but still allow deep=True to be provided"""
    rename_tree = purify(deep=True)(_rename_tree)
    assert rename_tree("Peter", Tree("Steve")).name == "Peter"

def test_value_error():

    with pytest.raises(ValueError):

        @purify("bar")
        def f(foo: str) -> str:
            return foo


# 1
# Create a User class, that has the  ability to print the users name
# the ability to print the users age
# Create a TempUser class, that has the ability to only print his name.
# Create a function that asks you to give the user a name, and give the user an age, and will then create the user for you, and print it to the screen. The user will have a choice to either be a temp user or a User

class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

class TempUser:
    def __init__(self, name):
        self.name = name

def typeOfUser():
    newUser = ""
    userType = input("Are you a (m) main or (t) temp user?")
    if userType == "m":
        userName = input("What is your name?")
        userAge = input("What is your age?")
        newUser = User(userName, userAge)
        print(f"{newUser.name} is {newUser.age} years old and is the main user.")
        # print("%s is %s years old and is the main user." % (newUser.name, newUser.age))
    elif userType == "t":
        userName = input("What is your name?")
        newUser = TempUser(userName)
        print(f"{newUser.name} the temporary user.")
    else:
        print("Please try again.")
    return newUser

calvin = typeOfUser()
joe = typeOfUser()


from sqlalchemy import *

from models import Marketdata
from database import session

from datetime import datetime

def datestring_to_datetime(datestr, formatstr="%Y-%m-%d %H:%M:%S"):
    try:
        dt = datetime.strptime(datestr, formatstr)
    except ValueError:
        dt = None
    return dt

def run_some_query():
    q = session.query(Marketdata).\
        order_by(Marketdata.date)

    data  = q.all()

    for d in data[:10]:
        print (d.price_first,\
                d.price_max,\
                d.price_min,\
                d.price_last,\
                d.volume)

def add_data():

    m = Marketdata()
    m.quote = 'BITMEX'
    m.date = datestring_to_datetime('2018-08-07 09:50:00')
    m.price_first = 111.11
    m.price_max = 222.22
    m.price_min = 333.33
    m.price_last = 444.44
    m.volume = 1

    session.add(m)
    session.commit()

if __name__ == "__main__":

    print('Imprimindo os 10 primeiros registros...')
    run_some_query()

    print('Adicionando um registro qualquer...')
    add_data()
    print('Adicionado!')


from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^$', views.startpage, name = "startpage" ),

    url(r'^recipes', views.retrieveRecipes, name="recipes"),
    url(r'^autocorrect', views.autocorrect, name="autocorrect"),
    url(r'^presenterarecept/', views.presentRecipe, name="presenterarecept"),

    #url(r'^recipe_match', views.recipes, name="recipe_match"),

    ]


from Node import *

def toStringRec(head):
    '''
    Returns string representation of linked list with head node head

    Keyword Arguments:
    Head --  name of the head node object in the linked list

    '''
    if head.next != None:
        return head.data + '->' + toStringRec(head.next)
    else:
        return head.data + '->'

def findElement(head,i):
    '''
    returns the data element at the i-th position of the linked list with heaD node head.

    Keyword Arguments:
    Head --  name of the head node object in the linked list
    i -- index of desired data element from list

    '''
    if i == 1:
        try:
            if head.next == None:
                return None
        except AttributeError:
            return None
    if i == 0:
        return head.data
    elif i != 0:
        i -= 1
        return findElement(head.next,i)

headN = Node('!')
node1 = Node('a')
node2 = Node('b')
headN.next = node1
node1.next = node2
print(toStringRec(headN))


#!/usr/bin/python

from mako.template import Template
import os
import tempfile
import subprocess
from glob import glob

class FeatManager(object):
	def __init__(self, op, fsf_file):
		self._op = op
		self._fsf_file = os.path.join(op.study_dir(), "models", fsf_file)
		self.__read_feat_template__(self._fsf_file)

	def __read_feat_template__(self, fsf_file):
		with open(fsf_file, 'r') as fh:
			template_data = fh.read()

		self._fsf_template = Template(template_data)

	def __generate_feat_config__(self, run_name, subject_code, config_file):
		base_dir = self._op.study_dir()
		sub_name = "sub{:0>3d}".format(int(subject_code))
		run_name = run_name

		fsf_config = self._fsf_template.render(base_dir=base_dir, sub_name=sub_name, run_name=run_name)

		with open(config_file, 'w') as config_file_handler:
				config_file_handler.write(fsf_config)

	def __execute_feat__(self, subject_code, run_name):
		if os.path.exists(os.path.join(self._op.study_dir(), "sub{:0>3d}".format(int(subject_code)), 'model', 'model001', '{}.feat'.format(run_name))):
			return

		config_file = os.path.join(self._op.study_dir(), "sub{:0>3d}".format(int(subject_code)), 'model', 'model001', '{}.fsf'.format(run_name))
		self.__generate_feat_config__(run_name, subject_code, config_file)

		subprocess.call('feat {}'.format(config_file), shell=True)

	def __get_task_runs__(self, subject_code, task_number):
		sub_name  = "sub{:0>3d}".format(int(subject_code))
		task_name = "task{:0>3d}".format(int(task_number))

		task_runs = glob(os.path.join(self._op.study_dir(), sub_name, 'BOLD', '{}_*'.format(task_name)))
		run_names = [task_run.split('/')[-1] for task_run in task_runs]

		return run_names

	def execute(self, subject, task):
		for run_name in self.__get_task_runs__(subject, task):
			self.__execute_feat__(subject, run_name)

if __name__ == "__main__":
	import OpenFMRIData
	from threading import Thread
	op = OpenFMRIData.OpenFMRIData('/home/user/data', 'LP')

	FM1= FeatManager(op, 'model001/task001.fsf')
	FM2= FeatManager(op, 'model001/task002.fsf')
	FM_gfeat= FeatManager(op, 'model001/task001_gfeat.fsf')

	#x = FM.__execute_feat__('task001_run002', 2)
	#x = FM.__execute_feat__('task001_run001', 2)
	#FM.__get_task_runs__(1, 1)
	all_subject_dirs = op.all_subjects_dirs_with_raw()
	for subject in all_subject_dirs:
		#Thread(target=FM2.execute, args=(subject.subcode(),2)).start()
		#Thread(target=FM1.execute, args=(subject.subcode(),1)).start()
		FM_gfeat.__execute_feat__(subject.subcode(),'task001')


def buscarAluno(alunosDic = {}):
    ler = input("\nDigite o nome do Aluno: ").upper()

    if(ler in alunosDic):
        print("\nAluno:",ler,"\nNotas:",alunosDic[ler],"\nMédia: %.1f" %(sum(alunosDic[ler]) / 3),"\nStatus: ")
    else:
        print("\nAluno não cadastrado!")

def  bestAluno(alunosDic = {}):
    alunosM = {}
    alunoBest = {}
    mediaANT = 0

    for i in alunosDic.keys():
        media = sum(alunosDic[i]) / 3
        alunosM[i] = media

        if(media > mediaANT):
            alunoBest = {}
            alunoBest[i] = media

        elif(media == mediaANT):
            alunoBest[i] = media

        mediaANT = media

    print(alunoBest.items())


from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys

from Dollar import Dollar
from Oil import Oil
from Ruble import Ruble

top = 400
left = 400
width = 400
height = 250

# 1 баррель нефти рублях
OIL_RUB = 3796

# 1 баррель нефти в долларах
OIL_DOLLAR = 52

class Window(QMainWindow):
    def __init__(self):
        super().__init__()

        title = "Генератор курса валют на торговом рынке"

        self.setWindowTitle(title)
        self.setGeometry(top, left, width, height)
        self.setFixedSize(width, height)

        validator = QDoubleValidator(self)
        locale = QLocale("en")
        validator.setLocale(locale)

        self.oilLabel = QLabel(self)
        self.rubleLabel = QLabel(self)
        self.dollarLabel = QLabel(self)

        # Конфигурация для нефти
        self.oil = Oil(1)
        self.oilLabel.setText("Нефть:")
        self.oilLabel.move(30,20)
        self.oilLabel.resize(200, 30)
        self.oilLabel.setFont(QFont('Arial', 20))

        self.oilEdit = QLineEdit(self)
        self.oilEdit.move(150, 20)
        self.oilEdit.resize(200, 30)
        self.oilEdit.setText(self.oil.getOil())
        self.oilEdit.setFont(QFont('Arial', 10))
        self.oilEdit.setValidator(validator)

        # Конфигурация для рубля
        self.ruble = Ruble(OIL_RUB)
        self.ruble.setRuble.connect(self.ruble.update)
        self.rubleLabel.setText("Рубль:")
        self.rubleLabel.move(30,80)
        self.rubleLabel.setFont(QFont('Arial', 20))

        self.rubleInput = QLineEdit(self)
        self.rubleInput.move(150,80)
        self.rubleInput.resize(200, 30)
        self.rubleInput.setFont(QFont('Arial', 10))
        self.rubleInput.setText(self.ruble.getRuble())
        self.rubleInput.setReadOnly(True)

        # Конфигурация для доллара
        self.dollar = Dollar(OIL_DOLLAR)
        self.dollar.setDollar.connect(self.dollar.update)

        self.dollarLabel.setText("Доллар:")
        self.dollarLabel.move(30,140)
        self.dollarLabel.setFont(QFont('Arial', 20))

        self.dollarInput = QLineEdit(self)
        self.dollarInput.move(150, 140)
        self.dollarInput.resize(200, 30)
        self.dollarInput.setText(self.dollar.getDollar())
        self.dollarInput.setFont(QFont('Arial', 10))
        self.dollarInput.setReadOnly(True)

        self.btn1 = QPushButton("Анализ", self)
        self.btn1.move(150,200)
        self.btn1.setFont(QFont('Arial', 10))
        self.btn1.clicked.connect(self.buttonClicked)

    def buttonClicked(self):
        newOil = float(self.oilEdit.text())
        self.oil.setOil(self.oilEdit.text())

        self.dollar.setDollar.emit(newOil * OIL_DOLLAR)
        self.dollarInput.setText(self.dollar.getDollar())

        self.ruble.setRuble.emit(newOil * OIL_RUB)
        self.rubleInput.setText(self.ruble.getRuble())

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    app.exec()


#202210947 유태석
import sys
def readData(filename):
    return_dic = dict()
    try:
        with open(filename) as f:
            for line in f.readlines():
                lst = line.strip("\n").split(",")
                return_dic[lst[0]] = [lst[1],int(lst[2])]
        return return_dic
    except FileNotFoundError:
        print(f"{filename}이 없습니다.")
        sys.exit()
#print(readData("items.cp949.txt"))
#print(readData("없는 파일"))
d = readData("items.cp949.txt")
def printItem(d, itemName):
    cnt = 0
    for idx in d.keys():
        try:
            if idx.count(itemName):
                print(f"상품명 : {idx}, 분류 : {d[idx][0]}, 가격 : {d[idx][1]}")
                cnt = 1
        except:
            pass
    if not cnt:
        print(f"상품 {itemName}이 없습니다.")
#print(printItem(d,"주걱"))
#print(printItem(d,"없는 상품"))
def printCategory(d, category):
    cnt = 0
    for idx in d:
        if d[idx][0].lower() == category.lower():
            print(f"상품명 : {idx}, 분류 : {d[idx][0]}, 가격 : {d[idx][1]}")
            cnt = 1
    if not cnt:
        print(f"분류항목 {category}가 없습니다.")
#print(printCategory(d, "Food"))
#print(printCategory(d,"없는 카테고리"))
def printPriceInRange(d, price1, price2):
    cnt = 0
    try:
        price1 = int(price1)
        price2 = int(price2)
        for idx in d:
            if price1 <= d[idx][1] and d[idx][1] < price2:
                print(f"상품명 : {idx}, 분류 : {d[idx][0]}, 가격 : {d[idx][1]}")
                cnt = 1
        if not cnt:
            print(f"가격이 {price1} ~ {price2} 에 해당되는 물품이 없습니다.")
    except BaseException:
        print("가격은 정수로 입력하세요.")
#print(printPriceInRange(d, 100, 5000))
#print(printPriceInRange(d, 0, 3000))
#print(printPriceInRange(d,"가","나"))
def main(filename):
    d = readData(filename)
    inp= input("\"명령:데이터\" 형태로 입력하세요: ")
    if inp[:inp.find(":")] == "item":
        return printItem(d, inp[inp.find(":")+1:])
    elif inp[:inp.find(":")] == "category":
        return printCategory(d, inp[inp.find(":")+1:])
    elif inp[:inp.find(":")] == "price":
        price1 = inp[inp.find(":")+1:inp.find("~")]
        price2 = inp[inp.find("~")+1:]
        return printPriceInRange(d, price1, price2)
    else:
        pass
main("items.cp949.txt")


#Rahman ve Rahim Olan Allah'ın Adıyla, Hamd Alemlerin Rabb'i Allah'a Mahsustur!!!

import math
import numpy as np
#FOR TRAJECTORY IMPORT
from numpy import linalg as LA
from scipy import optimize
from copy import deepcopy

from State import DesiredTrajectoryState
from utils import Hessian, polyder, normalize, position_to_numpy
import time
import matplotlib.pyplot as plt

from mpl_toolkits.mplot3d import Axes3D
from scipy.optimize import linear_sum_assignment as hungarian

from Parameters import *
from enums import *

class TrajGenerator:
    def __init__(self , order = 10):
        #Order can not change, for this implementation
        self.waypoints = None
        self.order = order
        self.yaw = 0
        self.heading = np.zeros(2)
        self.trajectory_generated = False

    def generate_trajectory_for_path(self,initial_position,waypoints,max_vel,gamma,path_manipulate_coef):
        waypoints.insert(0,initial_position)
        waypoints = position_to_numpy(waypoints)
        self.manipulate_coef = path_manipulate_coef
        self.waypoints = waypoints/self.manipulate_coef #waypoints numpy array
        len,dim = waypoints.shape
        self.dim = dim
        self.len = len
        self.max_vel = max_vel
        self.gamma = gamma
        self.TS = np.zeros(self.len)  #Segment time'lar initialize edildi
        self.optimize() # UPDATE THE COEFFICIENT OF TRAJECTORY, Her cagirilista yeni trajectory olusturur
    #RAHMAN VE RAHIM OLAN ALLAH'ın ADIYLA!

    def generate_trajectory_for_rotate(self,initial_position,waypoints,step_size,max_vel,gamma):
        np.insert(waypoints,0,np.array([initial_position.x,initial_position.y,initial_position.z]))
        #waypoints = position_to_numpy(waypoints)
        self.waypoints = waypoints #waypoints numpy array
        len,dim = waypoints.shape
        self.dim = dim
        self.len = len
        self.max_vel = max_vel
        self.gamma = gamma
        self.TS = np.zeros(self.len)  #Segment time'lar initialize edildi
        self.optimize_for_fixed_time(step_size) # UPDATE THE COEFFICIENT OF TRAJECTORY, Her cagirilista yeni trajectory olusturur

    def generate_trajectory(self,initial_position,waypoints,max_vel,gamma):
        waypoints.insert(0,initial_position)
        waypoints = position_to_numpy(waypoints)
        self.waypoints = waypoints #waypoints numpy array
        len,dim = waypoints.shape
        self.dim = dim
        self.len = len
        self.max_vel = max_vel
        self.gamma = gamma
        self.TS = np.zeros(self.len)  #Segment time'lar initialize edildi
        self.optimize() # UPDATE THE COEFFICIENT OF TRAJECTORY, Her cagirilista yeni trajectory olusturur

    def reset(self):
        self.dim = None
        self.len = None
        self.TS = None
        self.waypoints = None
        self.coeffs = None
        self.trajectory_generated = False

    def get_cost(self,T):
        coeffs,cost = self.MinimizeSnap(T)
        cost = cost + self.gamma*np.sum(T)
        return cost

    def optimize_for_fixed_time(self,step_size):
        T = np.ones(self.len-1)*(1/step_size)

        self.TS[1:] = np.cumsum(T)
        self.coeffs, self.cost = self.MinimizeSnap(T)

    def optimize(self):
        diff = self.waypoints[0:-1] - self.waypoints[1:]
        Tmin = LA.norm(diff,axis = -1)/self.max_vel #Olabilecek en dusuk hız, bizim verdigimiz max_vel ile belirlenir
        t1 = time.time()
        T = optimize.minimize(self.get_cost,Tmin, method="COBYLA",constraints= ({'type': 'ineq', 'fun': lambda T: T-Tmin}))['x']
        #print("NE KADAR SURDU",time.time()-t1)

        #T'yi optimize etmek icin GAMMA'lı cost isin icine katılarak cost hesaplandı, evet okey

        self.TS[1:] = np.cumsum(T)
        self.coeffs, self.cost = self.MinimizeSnap(T)

    def MinimizeSnap(self,T):
        unkns = 4*(self.len - 2)

        Q = Hessian(T)
        A,B = self.get_constraints(T)

        invA = LA.inv(A)

        if unkns != 0:
            R = invA.T@Q@invA

            Rfp = R[:-unkns,-unkns:]
            Rpp = R[-unkns:,-unkns:]

            B[-unkns:,] = -LA.inv(Rpp)@Rfp.T@B[:-unkns,]

        P = invA@B
        cost = np.trace(P.T@Q@P)

        return P, cost

    def get_constraints(self,T):
        n = self.len - 1
        o = self.order

        A = np.zeros((self.order*n, self.order*n))
        B = np.zeros((self.order*n, self.dim))

        B[:n,:] = self.waypoints[ :-1, : ]
        B[n:2*n,:] = self.waypoints[1: , : ]

        #waypoints contraints
        for i in range(n):
            A[i, o*i : o*(i+1)] = polyder(0, order = self.order)
            A[i + n, o*i : o*(i+1)] = polyder(T[i], order = self.order)

        #continuity contraints
        for i in range(n-1):
            A[2*n + 4*i: 2*n + 4*(i+1), o*i : o*(i+1)] = -polyder(T[i],'all',order = self.order)
            A[2*n + 4*i: 2*n + 4*(i+1), o*(i+1) : o*(i+2)] = polyder(0,'all',order = self.order)

        #start and end at rest
        A[6*n - 4 : 6*n, : o] = polyder(0,'all',order = self.order)
        A[6*n : 6*n + 4, -o : ] = polyder(T[-1],'all',order = self.order)

        #free variables
        for i in range(1,n):
            A[6*n + 4*i : 6*n + 4*(i+1), o*i : o*(i+1)] = polyder(0,'all',order=self.order)

        return A,B

    def get_yaw(self,vel):
        prev_heading = self.heading
        curr_heading = vel/LA.norm(vel)
        cosine = max(-1,min(np.dot(prev_heading, curr_heading),1))
        dyaw = np.arccos(cosine)
        norm_v = np.cross(prev_heading,curr_heading)
        self.yaw += np.sign(norm_v)*dyaw

        if self.yaw > np.pi: self.yaw -= 2*np.pi
        if self.yaw < -np.pi: self.yaw += 2*np.pi

        self.heading = curr_heading
        return self.yaw,dyaw

    def get_des_state(self,t):

        if t > self.TS[-1]: t = self.TS[-1] - 0.001

        i = np.where(t >= self.TS)[0][-1] #Segment selection, assume we have 1 segment!

        t = t - self.TS[i] #For each segment at start t = 0, bu satır bunu saglıyor.
        coeff = (self.coeffs.T)[:,self.order*i:self.order*(i+1)] #Give coefficient
        pos  = coeff@polyder(t,order = self.order)   #Gives pos value at segment time t! not t+dt!
        vel  = coeff@polyder(t,1,order = self.order) #Gives vel value at segment time t! not t+dt!
        accl = coeff@polyder(t,2,order = self.order) #Gives accl value at segment time t! not t+dt!
        jerk = coeff@polyder(t,3,order = self.order) #Gives Jerk value at segment time t! not t+dt! ---> Result: Trajectory'i yanlıs isliyoruz!

        thrust = accl + np.array([0, 0, 9.81]) #Thrust calculation from many paper
        z_body = normalize(thrust) #Drone Body z_body unit vector!
        jerk_orth_zbody = jerk - (np.dot(jerk, z_body) * z_body) #This calculation from paper of Mellinger and Kumar
        h_w = jerk_orth_zbody / np.linalg.norm(thrust) #This calculation from paper of Mellinger and Kumar, pls refer paper and ask question if you want!

        #set yaw in the direction of velocity
        yaw,dyaw = self.get_yaw(vel[:2]) #Get yaw value at segment time t! We can add the yaw controller, yaw---> yaw_rate!

        x_world = np.array([np.cos(yaw), np.sin(yaw), 0])
        y_body = normalize(np.cross(z_body, x_world))
        x_body = np.cross(y_body, z_body)
        omega = np.array([-np.dot(h_w, y_body), np.dot(h_w, x_body), z_body[2] * dyaw]) #In the WORLD FRAME, not body frame. This equation comes from, Mellinger Kumar Paper. Model....
        #Bu omega'nın z'sini body'e cevirebiliriz! Bunun body-z'si + yaw--->yawrate PID olmalı! duzgun kontrol olmazsa! Ama calısması gerek. Position icin de aynı

        return DesiredTrajectoryState(pos, vel, accl, yaw, omega)

class TrajectoryExecuter:
    def __init__(self,des_state,Tmax,
                 pos = None,
                 control_frequency = 62):

        self.initial_step_time = 0 #Used for trajectory time calculation elhamdulillah
        self.t = 0
        self.Tmax = Tmax
        self.dt = 1/control_frequency #YANLIS her bir .step() fonksiyonu cagırıldıgında 1/56sn sonraki traj state donuyor. Buraya da bir kontrol mekanizması konulabilir! nerede olduğuna gore!
                                      # Ve bu boyle her step bir sonraki hedef degeri verir ? Belki de initial veriyo.
        self.des_state = des_state
        if pos is None: pos = self.des_state(0).desired_position #initial Position #NOT USED
        end_state = self.des_state(self.Tmax-0.000001) ### ????? Tmax'ta çalışmıyor
        self.end_position = end_state.desired_position
        self.end_yaw = end_state.desired_yaw
        self.traj_completed = False

    #Rahman ve Rahim Olan Allah'ın Adıyla!
    def step(self):
        if self.t == 0:
            self.initial_step_time = time.time() #Inıtıalize time

        self.t = time.time()- self.initial_step_time
        print("Passed Time T: ",self.t)
        desired_state = self.des_state(self.t)
        #desired_state.desired_velocity[2] += 0.125 #BUNU MELLINGER KONTROLCU KENDISI EKLIYOR GEREK YOK
        if self.t >= self.Tmax:
            self.traj_completed = True
            return DesiredTrajectoryState(self.end_position,np.array([0,0,0]),np.zeros(3),self.end_yaw,np.zeros(3)),self.traj_completed ### En son gitmesi gereken pozisyonda Hover
        return desired_state,self.traj_completed


import math
import unittest

from calculate_area.area_calculation import Circle

class TestCircle(unittest.TestCase):
    def test_area_circle(self):
        circle = Circle(5)
        self.assertAlmostEqual(circle.calculate_area(), math.pi * 5**2, places=2)

    def test_area_zero_and_negative_radios(self):
        for radius in [0, -2]:
            with self.subTest(radius=radius):
                with self.assertRaises(ValueError):
                    circle = Circle(radius)

if __name__ == "__main__":
    unittest.main()


"""
author: Shiri Yarom
name: SynDataBase
description: sync of the readers and the writters
so they would have te accsses by the right rules
"""

import threading
import multiprocessing
from filedatabase import FileDatabase

class SynDataBase():

    def init(self, mode, database):
        """
              init class that sync between readers and writers
              :param mode: threading/ processing
              :param database
              """
        super().init()
        self.mode = mode
        self.database = database
        if self.mode == 'threading':
            self.lock = threading.Lock()
            self.semaphore = threading.Semaphore(10)
        if self.mode == 'processing':
            self.lock = multiprocessing.Lock()
            self.semaphore = multiprocessing.Semaphore(10)

    def write_data(self):
        """
              have the accseed to changes values in the dictionary
              :return: None
              """
        self.lock.acquire()
        for k in range(10):
            self.semaphore.acquire()

    def write_release(self):
        """
               release access to change values in the dictionary
               :return: None
               """
        for k in range(10):
            self.semaphore.release()
        self.lock.release()

    def get_value(self, key):
        """
                returns in the dictionary the value of the value before of
                the key
                :param key
                :return: flag - the value of the value of the key's dictionary
                """
        self.semaphore.acquire()
        value = self.database.get_value(key)
        self.semaphore.release()
        return value

    def set_value(self, key, val):
        """
             changes in the dictionary the value of the value before of
             the key
             :param key
             :param val
             :return: flag - True/ False if it works
             """
        self.lock.acquire()
        for k in range(10):
            self.semaphore.acquire()
        flag = self.database.set_value(key, val)
        self.lock.release()
        for k in range(10):
            self.semaphore.release()
        return flag

    def delete_value(self, key):
        """
                    deletes in the dictionary the value of the value before of
                     the key
                     :param key
                     :param val
                     :return: flag - True/ False if it works
                     """
        self.write_data()
        flag = self.database.delete_value(key)
        self.write_release()
        return flag

    def reader_db(database):
        """
        reader is trying to get an access to read
        the value from the dictionary
        :param database
        :return: None
        """
        print("reader started")
        for k in range(100):
            flag = database.get_value(k) == k or database.get_value(k) is None
            assert flag
        print("reader left")

    def writer_db(database):
        """
        writer is trying to get an access to write the
        value from the dictionary
        :param database
        :return: None
        """
        print("writer has accsess")
        for k in range(100):
            assert database.set_value(k, k)
        for k in range(100):
            flag = database.delete_value(k) == k or database.delete_value(k) is None
            assert flag
        print("writer is gone")


# Map

l = [1,2,3,4,5,6,7,8]
newlist = list(map(lambda x: x*x,l))
print(newlist)

# Filter

def filter_fun(x):
    return x>4

fil = list(filter(filter_fun,l))
print(fil)

# Reduce

from functools import reduce

red = reduce(lambda x,y: x+y,l)
print(red)


gols = 0
pessoal = []
cont = 0
while True:
    dados = {'Nome':'','gols': []}
    dados['Nome'] = str(input('Digite o Nome do Jogador : ')).capitalize()
    partidas = int(input(f'Quantas partidas {dados["Nome"]} jogou : '))
    for c in range(0, partidas):
        gol = int(input(f'  Quantas gols {dados["Nome"]} fez na {c+1}ª  Partida:'))
        dados['gols'].append(gol)
        gols += gol
    dados['goltotal'] = gols
    pessoal.append(dados.copy())
    gols = 0
    dados.clear()
    while True:
        ir = str(input('Deseja continuar [S/N]')).upper().strip()[0]
        if ir == 'N':
            break
        elif ir == 'S':
            break
        else:
            print('Digite uma opção válida')
    if ir == 'N':
        break
print('=-=' * 20)
print('Cod Nome        Gols        total')
for k, v in enumerate(pessoal):
    print(f'{k:>3} ', end='')
    for c in v.values():
        print(f'{str(c)!s:<15}', end='')
    print()
print('=-=' * 20)
while True:
    sele = int(input('Mostrar dado de qual jogador : [999 Para!]'))
    if sele == 999:
        print('Finalizando')
        break
    elif sele > len(pessoal):
        print('Digite um valor valido')
    else:
        print(f'Levantamento do jogador {pessoal[sele]["Nome"]}')
        for i,g in enumerate(pessoal[sele]['gols']):
            print(f'No jogo {i+1} foram {g} gols')


import unittest
import sys

sys.path.append("/home/labex/project")
from chunk import *
from math import ceil

def chunk(lst, size):
    return list(
        map(
            lambda x: lst[x * size : x * size + size],
            list(range(ceil(len(lst) / size))),
        )
    )

class TestChunk(unittest.TestCase):
    def test_chunk(self):
        self.assertEqual(chunk([1, 2, 3, 4, 5], 2), [[1, 2], [3, 4], [5]])
        self.assertEqual(chunk([1, 2, 3, 4, 5], 3), [[1, 2, 3], [4, 5]])
        self.assertEqual(chunk([1, 2, 3, 4, 5], 1), [[1], [2], [3], [4], [5]])
        self.assertEqual(chunk([], 2), [])

if __name__ == "__main__":
    unittest.main()


def main():
    # 補全成3位數
    data = "{:0<3d}".format(int(input()))
    results = []

    # 重複3次
    for i in range(3):
        data = calculate(data="{:0<3d}".format(int(data)))
        results.append(data)
    print(",".join(each for each in results))

def calculate(data):
    input_numbers = sorted([int(i) for i in data])
    # 最大值
    min_num = input_numbers[0]
    # 中間值
    medium_num = input_numbers[1]
    # 最小值
    max_num = input_numbers[2]

    max_data = max_num * 100 + medium_num * 10 + min_num
    min_data = min_num * 100 + medium_num * 10 + max_num
    result = max_data - min_data
    return str(result)

if __name__ == "__main__":
    main()


import importlib
from sys import exit

from BasicPyLib import simpleLogger
# noinspection PyUnresolvedReferences
from Config import base_settings
from BasicPyLib.BasicTools import CONSTANTS, isAllLettersCapital
from BasicPyLib.Printable import PrintableII, Printable
import time

from Config.configTools import get_user_input_and_set_default_values
from IBridgePy.MarketManagerBase import setup_services
from IBridgePy.constants import DataProviderName, BrokerName, LiveBacktest, TraderRunMode, TimeGeneratorType, MarketName

"""
To create a new config
step 1: create a class like MARKET_MANAGER_Config; the format must be XXX_Config or XXX_XXX_Config
step 2: in base_setting.py add setting values as dictionary, naming must be XXX or XXX_XXX. It must to add all possible values and give default values
step 3: If it is user input setting, put default value for user in setting.py
step 4: If it needs special config other than base, add something like Config/backtest_settings.py, naming must be xxxx_settings.py
"""

class Config(PrintableII):
    def __init__(self, dict_settings):
        for key in dict_settings:
            setattr(self, key, dict_settings[key])

    def override(self, settings, settingFileName):
        for key in settings:
            # TODO: The whole part is due to IB_Client is a dict, not a XXX_config class
            if isinstance(settings[key], dict):
                originalValues = None
                try:
                    originalValues = getattr(self, key)
                except AttributeError as e:
                    print(__name__ + '::override: EXIT, %s. Hint: Check Config::base_settings if it has the attribute.' % (e,))
                    exit()
                newValueDict = settings[key]
                for k in newValueDict:
                    if k in originalValues:
                        originalValues[k] = newValueDict[k]
                    else:
                        print(__name__ + '::override: EXIT, key=%s in the file of <%s.py> does not exist in Config::base_settings.py' % (k, settingFileName))
                        exit()
            else:
                self.set_value(key, settings[key], settingFileName)
        return self

    def set_value(self, fieldName, value, settingName):
        """

        :param fieldName:
        :param value:
        :param settingName: string, used to display the fileName/source of the settings
        :return:
        """
        if hasattr(self, fieldName):
            setattr(self, fieldName, value)
        else:
            print(__name__ + '::set_value: class=%s key=%s settingFileName=%s does not exist.' % (self.__class__, fieldName, settingName))
            print('base_config: %s' % (self,))
            exit()

# noinspection PyPep8Naming
class MARKET_MANAGER_Config(Config):
    def __init__(self, dict_settings):
        Config.__init__(self, dict_settings)

# noinspection PyPep8Naming
class REPEATER_Config(Config):
    def __init__(self, dict_settings):
        Config.__init__(self, dict_settings)

# noinspection PyPep8Naming
class TIME_GENERATOR_Config(Config):
    def __init__(self, dict_settings):
        Config.__init__(self, dict_settings)

# noinspection PyPep8Naming
class BROKER_SERVICE_Config(Config):
    def __init__(self, dict_settings):
        Config.__init__(self, dict_settings)

# noinspection PyPep8Naming
class PROJECT_Config(Config):
    def __init__(self, dict_settings):
        Config.__init__(self, dict_settings)

# noinspection PyPep8Naming
class TRADER_Config(Config):
    def __init__(self, dict_settings):
        Config.__init__(self, dict_settings)

# noinspection PyPep8Naming
class BROKER_CLIENT_Config(Config):
    def __init__(self, dict_settings):
        Config.__init__(self, dict_settings)

# noinspection PyPep8Naming
class EMAIL_CLIENT_Config(Config):
    def __init__(self, dict_settings):
        Config.__init__(self, dict_settings)

# noinspection PyPep8Naming
class UserConfigBase(Printable):  # should not be used directly
    def __init__(self):
        """
        For example, there is a file Config::hft_settings.py Then, a possible value of addOnSettings is "hft_settings"
        """
        # NEW config system
        self.projectConfig = None
        self.marketManagerConfig = None
        self.repeaterConfig = None
        self.timeGeneratorConfig = None
        self.traderConfig = None
        self.brokerClientConfig = None
        self.emailClientConfig = None

        self.initialize_quantopian = None
        self.handle_data_quantopian = None
        self.before_trading_start_quantopian = None

        self.timeGenerator = None
        self.dataFromServer = None
        self.singleTrader = None
        self.trader = None
        self.dataProvider = None
        self.dataProviderClient = None
        self.log = None
        self.userLog = None
        self.brokerService = None
        self.brokerClient = None
        self.emailClient = None  # to send email out

    def load_settings(self, str_settings):
        module_settings = importlib.import_module(str_settings)
        for item in dir(module_settings):
            if isAllLettersCapital(item):
                if item == 'PROJECT':
                    self.projectConfig = PROJECT_Config(getattr(module_settings, item))
                elif item == 'MARKET_MANAGER':
                    self.marketManagerConfig = MARKET_MANAGER_Config(getattr(module_settings, item))
                elif item == 'REPEATER':
                    self.repeaterConfig = REPEATER_Config(getattr(module_settings, item))
                elif item == 'TIME_GENERATOR':
                    self.timeGeneratorConfig = TIME_GENERATOR_Config(getattr(module_settings, item))
                elif item == 'TRADER':
                    self.traderConfig = TRADER_Config(getattr(module_settings, item))
                elif item == 'BROKER_CLIENT':
                    self.brokerClientConfig = BROKER_CLIENT_Config(getattr(module_settings, item))
                elif item == 'EMAIL_CLIENT':
                    self.emailClientConfig = EMAIL_CLIENT_Config(getattr(module_settings, item))
                else:
                    print(__name__ + '::load_settings: EXIT, cannot handle item=%s in %s.py' % (item, str_settings))
                    exit()
        return self

    def overrideBy(self, str_settings):
        module_settings = importlib.import_module(str_settings)
        for item in dir(module_settings):
            if isAllLettersCapital(item):
                if item == 'PROJECT':
                    self.projectConfig.override(getattr(module_settings, item), str_settings)
                elif item == 'MARKET_MANAGER':
                    self.marketManagerConfig.override(getattr(module_settings, item), str_settings)
                elif item == 'REPEATER':
                    self.repeaterConfig.override(getattr(module_settings, item), str_settings)
                elif item == 'TIME_GENERATOR':
                    self.timeGeneratorConfig.override(getattr(module_settings, item), str_settings)
                elif item == 'TRADER':
                    self.traderConfig.override(getattr(module_settings, item), str_settings)
                elif item == 'BROKER_CLIENT':
                    self.brokerClientConfig.override(getattr(module_settings, item), str_settings)
                elif item == 'EMAIL_CLIENT':
                    self.emailClientConfig.override(getattr(module_settings, item), str_settings)
                else:
                    print(__name__ + '::overrideBy: EXIT, cannot handle item=%s in %s' % (item, str_settings))
                    exit()
        return self

    def build(self):
        # userLog is for the function of record (). User will use it for any reason.
        dateTimeStr = time.strftime("%Y_%m_%d_%H_%M_%S")
        self.userLog = simpleLogger.SimpleLoggerClass(filename='userLog_' + dateTimeStr + '.txt', logLevel='NOTSET', addTime=False)
        sysLogFileName = 'TraderLog_' + time.strftime("%Y-%m-%d") + '.txt'
        self.log = simpleLogger.SimpleLoggerClass(sysLogFileName, self.projectConfig.logLevel)
        return self

    def validate(self):
        if self.projectConfig.dataProviderName in [DataProviderName.RANDOM, DataProviderName.LOCAL_FILE]:
            assert(self.projectConfig.brokerName == BrokerName.LOCAL_BROKER)

        if self.projectConfig.histIngestionPlan is None:
            if self.projectConfig.liveOrBacktest == LiveBacktest.BACKTEST:
                assert(self.projectConfig.dataProviderName == DataProviderName.RANDOM)

        if self.traderConfig.runMode in [TraderRunMode.RUN_LIKE_QUANTOPIAN, TraderRunMode.SUDO_RUN_LIKE_QUANTOPIAN]:
            if self.projectConfig.repBarFreq != 60:
                print(__name__ + '::validate: EXIT, projectConfig.repBarFreq=%s It should be 60 when traderConfig.runMode is either RUN_LIKE_QUANTOPIAN or SUDO_RUN_LIKE_QUANTOPIAN' % (self.projectConfig.repBarFreq,))
                exit()

        # if self.traderConfig.runMode == TraderRunMode.REGULAR:
        #     if self.marketManagerConfig.marketName != MarketName.NONSTOP:
        #         print(__name__ + '::validate: EXIT, marketManagerConfig.marketName=%s, it should be NONSTOP' % (self.marketManagerConfig.marketName,))
        #         exit()

    def load_userInput_to_userConfig(self, user_input):
        # No action to map from user_input to user_config
        for item in ['accountCode', 'repBarFreq', 'fileName', 'logLevel', 'dataProviderName', 'histIngestionPlan']:
            if hasattr(user_input, item):
                self.projectConfig.set_value(item, getattr(user_input, item), __name__ + '::load_userInput_to_userConfig')
        if hasattr(user_input, 'marketName'):
            self.marketManagerConfig.set_value('marketName', user_input.marketName, __name__ + '::load_userInput_to_userConfig')

        if hasattr(user_input, 'timeGeneratorType'):
            self.timeGeneratorConfig.set_value('timeGeneratorType', user_input.timeGeneratorType, __name__ + '::load_userInput_to_userConfig')
            if user_input.timeGeneratorType == TimeGeneratorType.AUTO:
                if hasattr(user_input, 'startTime'):
                    self.timeGeneratorConfig.set_value('startingTime', user_input.startTime, __name__ + '::load_userInput_to_userConfig')
                else:
                    print(__name__ + '::load_input: EXIT, startTime is None.')
                    exit()

                if hasattr(user_input, 'endTime'):
                    self.timeGeneratorConfig.set_value('endingTime', user_input.endTime, __name__ + '::load_userInput_to_userConfig')
                else:
                    print(__name__ + '::load_input: EXIT, endTime is None.')
                    exit()

                if hasattr(user_input, 'freq'):
                    self.timeGeneratorConfig.set_value('freq', user_input.freq, __name__ + '::load_userInput_to_userConfig')
                else:
                    print(__name__ + '::load_input: EXIT, freq is None.')
                    exit()
            elif user_input.timeGeneratorType == TimeGeneratorType.CUSTOM:
                if hasattr(user_input, 'customSpotTimeList'):
                    self.timeGeneratorConfig.set_value('custom', user_input.customSpotTimeList, __name__ + '::load_userInput_to_userConfig')
                else:
                    print(__name__ + '::load_input: EXIT, customSpotTimeList is empty.')
                    exit()

        if hasattr(user_input, 'runMode'):
            if user_input.runMode in [TraderRunMode.RUN_LIKE_QUANTOPIAN, TraderRunMode.SUDO_RUN_LIKE_QUANTOPIAN]:
                self.projectConfig.set_value('repBarFreq', 60, __name__ + '::load_userInput_to_userConfig')
                self.traderConfig.set_value('runMode', user_input.runMode, __name__ + '::load_userInput_to_userConfig')
                if user_input.runMode == TraderRunMode.SUDO_RUN_LIKE_QUANTOPIAN:
                    self.marketManagerConfig.set_value('marketName', MarketName.NONSTOP, __name__ + '::load_userInput_to_userConfig')

        self.initialize_quantopian = user_input.initialize
        self.handle_data_quantopian = user_input.handle_data
        self.before_trading_start_quantopian = user_input.before_trading_start

    def prepare_userConfig_with_trader(self, trader, globalsV):
        userInput = get_user_input_and_set_default_values(globalsV)

        # base userConfig is passed-in.
        # This part only merge userInput into userConfig and then build userConfig.
        self.load_userInput_to_userConfig(userInput)  # set many values in userConfig
        self.build()  # set many values in userConfig

        # cannot instantiate trader here because defined_functions user's input need to go after trader.
        setup_services(self, trader)  # set many values in userConfig
        trader.update_from_userConfig(self)  # should not set any values in userConfig
        self.validate()

class UserConfig(CONSTANTS):
    # Override sequence MATTERS!!!
    REGULAR = UserConfigBase().load_settings('Config.base_settings').overrideBy('settings')
    BACKTEST = UserConfigBase().load_settings('Config.base_settings').overrideBy('Config.backtest_settings').overrideBy('settings')
    HFT = UserConfigBase().load_settings('Config.base_settings').overrideBy('Config.hft_settings').overrideBy('settings')
    ROBINHOOD = UserConfigBase().load_settings('Config.base_settings').overrideBy('Config.robinhood_settings').overrideBy('settings')
    TD = UserConfigBase().load_settings('Config.base_settings').overrideBy('Config.td_settings').overrideBy('settings')

    @staticmethod
    def choose(settingNames):
        if isinstance(settingNames, str):
            list_settingName = [settingNames]
        else:
            list_settingName = settingNames
        ans = UserConfigBase().load_settings('Config.base_settings')
        for settingName in list_settingName:
            # print(__name__ + '::choose: loading %s' % (settingFileName,))
            ans = ans.overrideBy('Config.%s' % (settingName,))
        ans = ans.overrideBy('settings')
        # print(ans)
        return ans

def test__isAllLettersCapital():
    assert(isAllLettersCapital('asdfEdasdf') is False)
    assert(isAllLettersCapital('BROKER_CLIENT') is True)
    assert(isAllLettersCapital('BROKER_CLIENt') is False)


from typing import Dict

from prefect import Flow, task, case
from prefect import Parameter

from lume_services.results import Result
from lume_services.tasks import (
    configure_lume_services,
    prepare_lume_model_variables,
    check_local_execution,
    SaveDBResult,
    LoadDBResult,
    LoadFile,
    SaveFile,
)
from lume_services.files import TextFile
from lume_model.variables import InputVariable, OutputVariable
from prefect.storage import Module

from lume_lcls_cu_inj_nn.model import LCLSCuInjNN
from lume_lcls_cu_inj_nn import INPUT_VARIABLES, CU_INJ_MAPPING_TABLE

import torch
import matplotlib.pyplot as plt

from lume_model.utils import variables_from_yaml
from lume_model.torch import LUMEModule, PyTorchModel
import os

@task(log_stdout=True)
def format_result(
    input_variables: Dict[str, InputVariable],
    output_variables,
    output_variables_names
):
    outputs = {}
    output_variables = output_variables.tolist()
    output_variables_names = list(output_variables_names.keys())
    print('Output Variables List - ', output_variables_names)

    for i in range(len(output_variables)):
        outputs[output_variables_names[i]] = output_variables[i]

    return Result(inputs=input_variables, outputs=outputs)

@task(log_stdout=True)
def evaluate(formatted_input_vars, lume_module):
    all_input_values = []
    for key in formatted_input_vars:
        all_input_values.append(formatted_input_vars[key])

    all_input_values = torch.Tensor([all_input_values])
    with torch.no_grad():
        predictions = lume_module(all_input_values)
    print('Predictions - ', predictions)
    return predictions

save_db_result_task = SaveDBResult(timeout=30)

@task(log_stdout=True)
def load_input(var_name, parameter):
    #Confirm Inputs are Correctly Loaded!
    print('Loaded ', str(var_name), ' with value - ', parameter)
    return parameter

with Flow("lume-lcls-cu-inj-nn", storage=Module(__name__)) as flow:

    print('Starting Flow Run')
    # CONFIGURE LUME-SERVICES
    # see https://slaclab.github.io/lume-services/workflows/#configuring-flows-for-use-with-lume-services

    configure = configure_lume_services()

    # CHECK WHETHER THE FLOW IS RUNNING LOCALLY
    # If the flow runs using a local backend, the results service will not be available
    running_local = check_local_execution()
    running_local.set_upstream(configure)

    input_variable_parameter_dict = {}

    for var_name, var in INPUT_VARIABLES.items():
        input_variable_parameter_dict[var_name] = load_input(var_name, Parameter(var_name, default=var.default))

    print('Input Variable Parameters - ',input_variable_parameter_dict)

    if os.path.exists('model/'):
        TORCH_MODEL_PATH = 'model/'
    elif os.path.exists('/lume-lcls-cu-inj-nn/lume_lcls_cu_inj_nn/model/'):
        #This is the Docker Path
        TORCH_MODEL_PATH = '/lume-lcls-cu-inj-nn/lume_lcls_cu_inj_nn/model/'
    elif os.path.exists('lume_lcls_cu_inj_nn/model/'):
        TORCH_MODEL_PATH = 'lume_lcls_cu_inj_nn/model/'
    else:
        print('Path Not Found')

    print('Reached Here with TORCH MODEL PATH - ', TORCH_MODEL_PATH)
    print(print(os.listdir()))

    input_transformer = torch.load(TORCH_MODEL_PATH+"input_transformer.pt")
    output_transformer = torch.load(TORCH_MODEL_PATH+"output_transformer.pt")
    input_variables_names, output_variables_names = variables_from_yaml(open(TORCH_MODEL_PATH+"variables.yml"))

    # create lume model
    lume_model = PyTorchModel(
        model_file=TORCH_MODEL_PATH+"model.pt",
        input_variables=input_variables_names,
        output_variables=output_variables_names,
        input_transformers=[input_transformer],
        output_transformers=[output_transformer],
    )
    lume_module = LUMEModule(
        model=lume_model,
        feature_order=lume_model.features,
        output_order=lume_model.outputs,
    )

    output_variables = evaluate(input_variable_parameter_dict, lume_module)

    # SAVE RESULTS TO RESULTS DATABASE, requires LUME-services results backend
    with case(running_local, False):
        # CREATE LUME-services Result object
        formatted_result = format_result(
            input_variables=input_variable_parameter_dict, output_variables=output_variables, output_variables_names=output_variables_names
        )

        # RUN DATABASE_SAVE_TASK
        saved_model_rep = save_db_result_task(formatted_result)
        saved_model_rep.set_upstream(configure)

def get_flow():
    return flow


# -*- coding: utf-8 -*-
"""
Code for the Backward difference equation
Partial Differential Equations
@author: flanagans

TRY with :
    Ut = 5*Uxx
    deltax = .01
    U(0,x) = x*(x-1)*(x+1)
    -1 <= x <=1
    t >=0
"""

import numpy as np
import matplotlib.pyplot as plt
from numpy.linalg import inv

xleft = -1
xright = 1
delta_t = 0.001
tfinal = 0.01
iterations = int(tfinal / delta_t)
delta_x = .1

gamma = 5
length = 1
n = int((xright - xleft)/delta_x) #this value comes from dividing l by delta_t
delta_x_squared = (delta_x)**2 #delta x = .1

m = (gamma*delta_t)/delta_x_squared #formula for calculating mu
#print(m)
#acceptable range of x values which will be evaluated by the piecewise function stored as array
x=np.linspace(-1, 1, n+1)

def mastery(x):
    "This is the function given for mastery check"
    y = x*(x-1)*(x+1)
    return y

u_1 = np.zeros(n+1)

for i in range(0, n):
    u_1[i] = mastery(x[i])

#Vector w alpha and beta values
b = np.zeros(n-1)
b[0] = u_1[0]
b[n-2] = u_1[n]
bt = np.matrix.transpose(b)
#Vector with the initial u conditions
u = np.zeros(n-1)
for i in range (0, (n-1)):
    u[i] = u_1[i+1]

ut = np.matrix.transpose(u)
#print(ut)

size = (n-1, n-1)
A_hat = np.zeros(size)
#print(A)

for i in range(n - 1):
    A_hat[i, i] = 1 + 2*m

for i in range(n - 2):
    A_hat[i, i + 1] = -m

for i in range(n-2):
    A_hat[i+1, i] = -m

A_hat_inv = inv(A_hat)

for i in range(iterations):
    u_b = ut - bt
    u_j = np.matmul(A_hat_inv, u_b)
    u_t = u_j

Solution = np.zeros(n+1)
Solution[0] = b[0]
Solution[n] = b[n-2]
for i in range (0, n-1):
    Solution[i+1] = u_j[i]

print(Solution)
#plt.plot(Solution)
plt.plot(x, Solution)
plt.ylabel('Temperature')
plt.xlabel('Position In X')
plt.title('Backward Difference Method')
plt.show()


import sys
sys.stdin = open('input.txt', 'r')

N = int(input())
inf = float('inf')
board = [[inf] * N for _ in range(N)]

M = int(input())
for _ in range(M):
    a, b, c = map(int, input().split())
    if c < board[a-1][b-1]:
        board[a-1][b-1] = c

for k in range(N):
    for i in range(N):
        if board[i][k] == inf:
            continue
        for j in range(N):
            if i == j or j == k:
                continue
            board[i][j] = min(board[i][j], board[i][k] + board[k][j])

for i in range(N):
    for j in range(N):
        if board[i][j] == inf:
            board[i][j] = 0

for r in range(N):
    row = ' '.join(list(map(str, board[r])))
    print(row)


import torch
from torch import nn
import torch.nn.functional as F

class BasicConv2d(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, padding= 0):
        super(BasicConv2d, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, padding= padding)
    def forward(self, x):
        x = F.relu(self.conv(x), inplace=True)
        #inplace参数对计算结果不会有影响。利用in-place计算可以节省内（显）存，同时还可以省去反复申请和释放内存的时间。
        #但是会对原变量覆盖，只要不带来错误就用
        return x

class inceptionv1(nn.Module):
    def __init__(self, input_dim, hidden_1_1, hidden_2_1, hidden_2_3, hidden_3_1, output_3_5, output_4_1):
        super(inceptionv1,self).__init__()
        self.branch1x1 = BasicConv2d(input_dim, hidden_1_1, 1)
        self.branch3x3 = nn.Sequential(
            BasicConv2d(input_dim, hidden_2_1, 1),
            BasicConv2d(hidden_2_1, hidden_2_3, 3, padding=1)
        )
        self.branch5x5 = nn.Sequential(
            BasicConv2d(input_dim, hidden_3_1, 1),
            BasicConv2d(hidden_3_1, output_3_5, 5, padding=2)
        )
        self.branch_pool = nn.Sequential(
            nn.MaxPool2d(3, stride= 1, padding= 1),
            BasicConv2d(input_dim, output_4_1, 1)
        )

    def forward(self, x):
        x1 = self.branch1x1(x)
        x2 = self.branch3x3(x)
        x3 = self.branch5x5(x)
        x4 = self.branch_pool(x)
        output = torch.cat((x1, x2, x3, x4), dim=1)
        return output

inceptionv1 = inceptionv1(3, 64, 32, 64, 64, 96, 32)
input = torch.randn(1, 3, 256, 256)
output = inceptionv1(input)

print(output.shape)


import numpy as np
import pandas as pd
from sklearn.neighbors import NearestNeighbors
from sklearn.preprocessing import StandardScaler
from core.recommender_systems.RecommenderSystem import RecommenderSystem

class CoolStartRS(RecommenderSystem):
    """
        Implement the RecommenderSystem class, this class just need
        information on the songs and, through a "songs space" is able to
        give recommendation, considering the last song listened
        by the user.

        The recommendation is performed by means of K-NN with the cosine
        similarity as metric. N closest songs-points, to the last listened
        song-point, are recommended.

        Parameters
        ----------

    """

    def __init__(self):
        super().__init__(table_name='SONGS')
        self.rec_songs = {}
        self.columns_of_number_type = self.sql_handler.select_columns(table_name=self.table_name,
                                                                      data_types=['integer', 'double'])
        x = self.sql_handler.search(table_name=self.table_name, conditions=(self.columns_of_number_type, []))
        self.x = pd.DataFrame(data=x, columns=self.columns_of_number_type)

    def k_neighbors(self, current_user_id: str, current_song_id: str, n_songs: int, index: list, get_listened: bool):
        """
            Select n_songs from index row, under the condition if the already listened songs
            have to be considered or not.

            Parameters
            ----------
            current_user_id : ID of the logged user

            current_song_id : ID of the last listened song

            n_songs : number of songs to be recommended

            index : list of row index of the songs, ordered according the distances from current_song_id

            get_listened : if already listened songs have to be considered or not

            Returns
            -------
            rec_songs : list of recommended songs by ID
        """
        if get_listened:
            rec_songs = [self.get_vector(current_song_id)]
        else:
            rec_songs = {}
        counter, list_position = (0, 0)

        while list_position < len(index[1]) and counter < n_songs:
            idx = index[1][list_position]
            rec_song = self.sql_handler.search(self.table_name, (('song_id',), [('ROWID', '=', int(idx))]))
            if len(rec_song) != 0:
                rec_song_listened = self.sql_handler.search('HISTORY', (('song_id',),
                                                                        [('user_id', '=', current_user_id),
                                                                         'AND',
                                                                         ('song_id', '=', rec_song)]))

                if len(rec_song_listened) != 0:
                    rec_song_listened = rec_song == rec_song_listened

                if get_listened:
                    if rec_song_listened:
                        rec_songs.append(self.get_vector(rec_song))
                    counter += 1
                else:
                    if not rec_song_listened:
                        rec_songs[rec_song] = 1 - index[0][list_position]  # cosine similarity = 1 - cosine distance
                        counter += 1

            list_position += 1
        return rec_songs

    def get_vector(self, song_id: str):
        """
            Given a songID, this method provide the array of number features.

            Parameters
            ----------
            song_id : ID of the given song

            Returns
            -------
            array of features song
        """
        columns = self.sql_handler.columns_of(table_name=self.table_name)
        idx_columns_of_number_type = []
        for column in self.columns_of_number_type:
            idx_columns_of_number_type.append(columns.index(column))

        song_data = self.sql_handler.search(table_name=self.table_name,
                                            conditions=(('*',), [('song_id', '=', song_id)]))
        song_vector = []
        for idx in idx_columns_of_number_type:
            song_vector.append(song_data[idx])

        return np.array(song_vector)

    def recommend_songs(self, current_user_id: str, current_song_id: str, n_songs: int = 10):
        """
            Create a song space by their features, and apply KNN on the current song.
            The Algorithm follow this procedure:
            1. Select n_songs items closest to current_song_id
            2. Select from (1) already listened songs, current_song_id is part of this set
                2.1. If no how was already listened, then suggest them to the user
            3. Compute the 'artificial middle song' among songs from (2)
            4. Select the n_songs never listened items closest to the artificial song point
            5. Recommend items from (4)

            Parameters
            ----------
            current_user_id : ID of the logged user

            current_song_id : ID of the last listened song

            n_songs : number of songs to be recommended

            Returns
            -------
            rec_songs : list of recommended songs by ID
        """
        song_vector = self.get_vector(current_song_id)
        scaler = StandardScaler()
        scaler.fit(self.x)
        scaled_data = scaler.transform(self.x)
        scaled_song_vector = scaler.transform(song_vector.reshape(1, -1))
        neigh = NearestNeighbors(n_neighbors=10, metric='cosine', n_jobs=-1)
        neigh.fit(scaled_data)

        index = neigh.kneighbors(X=scaled_song_vector, n_neighbors=10, return_distance=True)
        index = [index[0][0].tolist(), index[1][0].tolist()]  # just for a better readability

        already_listened_songs_vectors = self.k_neighbors(current_user_id, current_song_id, n_songs, index, True)

        if len(already_listened_songs_vectors) > 1:
            listened_songs_matrix = np.array(already_listened_songs_vectors)
            listened_songs_mean_vector = np.mean(listened_songs_matrix, axis=0)

            array_comparison_bool_matrix = np.equal(self.x, listened_songs_mean_vector)  # matrix of bool

            # check if listened_songs_mean_vector is present in self.x as a vector row
            if True in np.apply_along_axis(lambda x: True if np.alltrue(x) else False, 1, array_comparison_bool_matrix):
                listened_mean_row = [(self.columns_of_number_type[idx], listened_songs_mean_vector[idx])
                                     for idx in range(0, len(listened_songs_mean_vector))]
                column_conditions = []
                for column_name, column_value in listened_mean_row:
                    condition = (column_name, '=', column_value)
                    column_conditions.append(condition)
                    column_conditions.append('AND')

                conditions = (('song_id',), column_conditions[:-1])
                mean_song_id = self.sql_handler.search(self.table_name, conditions)

                if mean_song_id and mean_song_id not in self.sql_handler.search('HISTORY',
                                                                                (('song_id',),
                                                                                [('user_id', '=', current_user_id),
                                                                                'AND',
                                                                                ('song_id', '=', mean_song_id)])):
                    self.rec_songs[mean_song_id] = 1.0  # mean_song_id first recommendation
                    n_songs = n_songs - 1  # update number of items to find

            scaled_listened_songs_mean_vector = scaler.transform(listened_songs_mean_vector.reshape(1, -1))

            index = neigh.kneighbors(X=scaled_listened_songs_mean_vector, n_neighbors=scaled_data.shape[0],
                                     return_distance=True)
            index = [index[0][0].tolist(), index[1][0].tolist()]  # just for a better readability
            self.rec_songs.update(self.k_neighbors(current_user_id, current_song_id, n_songs, index, False))
        else:
            self.rec_songs = self.k_neighbors(current_user_id, current_song_id, n_songs, index, False)

        return self.rec_songs

    def __str__(self):
        return self.print('CoolStartRS', self.rec_songs) if self.rec_songs else ''


# https://school.programmers.co.kr/learn/courses/30/lessons/72412

import bisect

def solution(info, query):
    L = ['java','python','cpp']
    J = ['backend','frontend']
    C = ['junior','senior']
    F = ['chicken','pizza']
    answer = []
    dic = {}
    cache = {}
    for l in L:
        dic[l]={}
        for j in J:
            dic[l][j]={}
            for c in C:
                dic[l][j][c]={}
                for f in F:
                    dic[l][j][c][f]=[]

    for i in info:
        i = list(i.split())
        dic[i[0]][i[1]][i[2]][i[3]].append(int(i[4]))
        dic[i[0]][i[1]][i[2]][i[3]].sort()

    for q in query:
        l,j,c,f,s = list(q.replace(' and','').split())
        count = 0
        if q in cache:
            count = cache[q]
        else:
            l=L if l=='-' else [l]
            j=J if j=='-' else [j]
            c=C if c=='-' else [c]
            f=F if f=='-' else [f]
            s = int(s)
            for ll in l:
                for jj in j:
                    for cc in c:
                        for ff in f:
                            count+=len(dic[ll][jj][cc][ff])-bisect.bisect_left(dic[ll][jj][cc][ff],s)
            cache[q] = count
        answer.append(count)

    return(answer)

if __name__ == "__main__":
    info = ["java backend junior pizza 150","python frontend senior chicken 210","python frontend senior chicken 150","cpp backend senior pizza 260","java backend junior chicken 80","python backend senior chicken 50"]
    query = ["java and backend and junior and pizza 100","python and frontend and senior and chicken 200","cpp and - and senior and pizza 250","- and backend and senior and - 150","- and - and - and chicken 100","- and - and - and - 150"]
    result = [1,1,1,1,2,4]
    print(solution(info, query), result)


import logging
import discord
from discord import embeds
import os
import toml
config = toml.load('config.toml')
from src.modules.commands import BaseCommand
from src.modules.commands.utils import TraceCommand

class InfoCommand(BaseCommand):

    @TraceCommand.traceback_print
    async def apply(self):
        import src.texts.help_texts as texts
        from src.texts.const import const
        logging.info("Enviando mensaje de información")
        bot_info_channel_id = config['INFO_BOT_CHANNEL_ID']
        channel = self.ctx.guild.get_channel(bot_info_channel_id)

        embed = discord.Embed(
            colour=discord.Colour.blue(),
            title=f"{const.EVENT_NAME} {const.EVENT_YEAR}" ,
            description=texts.START_DESCRIPTION_MESSAGE
        )
        embed.add_field(name=f'{const.COMMAND_PREFIX}help',value=texts.HELP_COMMAND, inline=False)
        embed.add_field(name=f'{const.COMMAND_PREFIX}login',value=texts.LOGIN_COMMAND, inline=False)
        embed.add_field(name=f'{const.COMMAND_PREFIX}create <team_name>',value=texts.CREATE_COMMAND, inline=False)
        embed.add_field(name=f'{const.COMMAND_PREFIX}invite <discord_user>',value=texts.INVITE_COMMAND, inline=False)
        embed.add_field(name=f'{const.COMMAND_PREFIX}join <team_name>',value=texts.JOIN_COMMAND, inline=False)
        embed.add_field(name=f'{const.COMMAND_PREFIX}leave',value=texts.LEAVE_COMMAND, inline=False)
        embed.add_field(name=f'{const.COMMAND_PREFIX}ask <question>',value=texts.ASK_COMMAND, inline=False)
        embed.add_field(name=f'{const.COMMAND_PREFIX}joke',value=texts.JOKE_COMMAND, inline=False)
        embed.add_field(name=f'{const.COMMAND_PREFIX}rpsls',value=texts.RPSLS_COMMAND, inline=False)

        await channel.send(embed=embed)