def test_bisect_left():
    slt = SortedList()
    assert slt.bisect_left(0) == 0
    slt = SortedList(range(100), load=17)
    slt.update(range(100))
    slt._check()
    assert slt.bisect_left(50) == 100
    assert slt.bisect_left(200) == 200
Example #2
0
class MyCalendar:
    '''
    We need to make sure that q1 == q2, that is that the point we we need to insert start and end is in the same gap.
    Also q1 % 2 == 0, because it should be gap between two intervals, see [1, 3), [5, 10). If we found that we need to insert both start and end to [3, 5), it is OK for us, if to [1, 3) or [5, 10) it is not OK.
    Complexity
    Time complexity of one book is O(log n), time complexity of all algorithm is O(n log n). Space complexity is O(n).
    '''
    def __init__(self):
        self.s_list = SortedList()

    def book(self, start: int, end: int) -> bool:
        left = self.s_list.bisect_right(start)
        right = self.s_list.bisect_left(end)
        if left == right and left % 2 == 0:
            #can insert
            self.s_list.add(start)
            self.s_list.add(end)
            return True
        return False

# Your MyCalendar object will be instantiated and called as such:
# obj = MyCalendar()
# param_1 = obj.book(start,end)
Example #3
0
class MKAverage:
    def __init__(self, m: int, k: int):
        self.m, self.k = m, k
        self.deque = collections.deque()
        self.sl = SortedList()
        self.total = self.first_k = self.last_k = 0

    def addElement(self, num: int) -> None:
        self.total += num
        self.deque.append(num)
        index = self.sl.bisect_left(num)
        if index < self.k:
            self.first_k += num
            if len(self.sl) >= self.k:
                self.first_k -= self.sl[self.k - 1]
        if index >= len(self.sl) + 1 - self.k:
            self.last_k += num
            if len(self.sl) >= self.k:
                self.last_k -= self.sl[-self.k]
        self.sl.add(num)
        if len(self.deque) > self.m:
            num = self.deque.popleft()
            self.total -= num
            index = self.sl.index(num)
            if index < self.k:
                self.first_k -= num
                self.first_k += self.sl[self.k]
            elif index >= len(self.sl) - self.k:
                self.last_k -= num
                self.last_k += self.sl[-self.k - 1]
            self.sl.remove(num)

    def calculateMKAverage(self) -> int:
        if len(self.sl) < self.m:
            return -1
        return (self.total - self.first_k - self.last_k) // (self.m -
                                                             2 * self.k)
Example #4
0
 def closestRoom(self, rooms: List[List[int]], queries: List[List[int]]) -> List[int]:
     new_rooms = []
     for roomId, size in rooms:
         new_rooms.append((-size, roomId))
     new_rooms.sort()
     
     new_queries = []
     for query_idx, (preferred, minSize) in enumerate(queries):
         new_queries.append((-minSize, preferred, query_idx))
     new_queries.sort()
     
     sorted_list = SortedList()
     idx = 0
     ans = [-1 for _ in range(len(queries))]
     for minSize, preferred, query_idx in new_queries:
         while idx < len(new_rooms) and new_rooms[idx][0] <= minSize:
             sorted_list.add(new_rooms[idx][1])
             idx += 1
         option1 = sorted_list.bisect_left(preferred)
         best_room = -1
         if 0 <= option1 < len(sorted_list):
             best_room = sorted_list[option1]
         option2 = option1 - 1
         if 0 <= option2 < len(sorted_list):
             # use "<=" because option2 is smaller than option1 and is preferred when absolute distance is same
             if abs(preferred - sorted_list[option2]) <= abs(preferred - best_room):
                 best_room = sorted_list[option2]
         ans[query_idx] = best_room
     return ans
     '''
     Notes:
     [1,2,3] bisect left will get the 2 at idx at 1
     [1,3,3] bisect left will get the 3 at idx at 1
     that is why we also check bosect left -1 to consider 1 at idx at 0
     '''
         
             
Example #5
0
    def get_next_book(sequence, timestamp, books_df):
        """
        Get next book after the sequence if available.

        Parameters
        ----------
        sequence: int
        timestamp: datetime
        books_df: DataFrame
            multiple books

        Returns
        -------
        GdaxOrderBook or None
        """
        if books_df.empty:
            logger.error('No books available.')
            return

        # get index of next sequence
        seq_list = SortedList(books_df['sequence'].unique())
        next_seq_idx = seq_list.bisect_left(sequence)

        if next_seq_idx >= len(seq_list):
            logger.error(
                'No book found after {} ({}). Available books: {}'.format(
                    timestamp, sequence, seq_list))
            return

        # next book
        next_seq = seq_list[next_seq_idx]
        book_df = books_df[books_df['sequence'] == next_seq]
        book = sutil.df_to_book(book_df)
        logger.info('Current message at {} ({}). Got book at {} ({})'.format(
            timestamp, sequence, book.sequence, book.timestamp))
        return book
Example #6
0
 def busiest_servers(self, k: int, arrival: List[int],
                     load: List[int]) -> List[int]:
     available = SortedList(range(k))
     busy = []
     requests = [0] * k
     for i, start in enumerate(arrival):
         while busy and busy[0][0] <= start:
             available.add(busy[0][1])
             heapq.heappop(busy)
         if not available:
             continue
         j = available.bisect_left(i % k)
         if j == len(available):
             j = 0
         idx = available[j]
         requests[idx] += 1
         heapq.heappush(busy, (start + load[i], idx))
         available.remove(idx)
     max_cnt = max(requests)
     ans = []
     for i, req in enumerate(requests):
         if req == max_cnt:
             ans.append(i)
     return ans
Example #7
0
 def sequentialDigits(self, low: int, high: int) -> List[int]:
     
     def digit_dp(curnumber,limit,ans):
         if curnumber>limit:
             return
         
         ans.add(curnumber)
         
         if curnumber==0:
             for i in range(1,10):
                 digit_dp(i,limit,ans)
         else:
             lastdigit = int(str(curnumber)[-1])
             if lastdigit!=9:
                 digit_dp(curnumber*10+lastdigit+1,limit,ans)
         
         return
     
     ansb = SortedList()
     
     digit_dp(0,high,ansb)
     #ansb = sorted(ansb)
     pos = ansb.bisect_left(low)
     return ansb[pos:]
Example #8
0
class AbstractAllocator:
    """Manage allocations of arbitrary size within a sliceable block of items.

    This is abstract because we don't actually assume anything about the types
    of the items or how to write to them.

    """

    # TODO: rename this to something like ContiguousBlockAllocator

    def __init__(self, capacity: int = 8192):
        self.capacity = capacity
        self.allocs = {}  # mapping of offset -> reserved size
        self._free = SortedList([(capacity, 0)])

    def avail(self) -> int:
        """Get the current available capacity."""
        return sum(cap for cap, off in self._free)

    def grow(self, new_capacity: int):
        """Grow the available space for allocation to new_capacity.

        No reallocations or compactions are done here so the only guaranteed
        contiguous new free space is at the end.
        """
        self._release(self.capacity, new_capacity - self.capacity)
        self.capacity = new_capacity

    def _release(self, offset, length):
        """Release the given block back to the pool.

        We consider joining this block to a subsequent one. We don't currently
        consider joining it to the previous block.

        """
        if not length:
            return

        while True:
            k = (length, offset)
            idx = self._free.bisect_left(k)
            if idx >= len(self._free):
                self._free.add(k)
                break

            next_length, next_off = self._free[idx]
            if next_off != (length + offset):
                self._free.add(k)
                break

            self._free.pop(idx)
            length += next_length

    def check(self):
        """Check invariants."""
        free = sum(cap for cap, _ in self._free)
        allocated = sum(self.allocs.values())
        assert free + allocated == self.capacity, \
            f"Free: {free}, Allocated: {allocated}, Capacity: {self.capacity}"
        return True

    def alloc(self, num: int) -> slice:
        """Allocate a block of size num.

        Return a slice, or raise NoCapacity if there was insufficient
        capacity available.

        """
        pos = self._free.bisect_left((num, 0))
        if pos == len(self._free):
            # capacity is not high enough
            new_capacity = self.capacity
            while new_capacity < num:
                new_capacity *= 2

            err = NoCapacity()
            err.recommended = self.capacity + new_capacity
            raise err

        # We have located where we want to reserve, insert it
        return self._reserve(pos, num)

    def _reserve(self, pos, num) -> slice:
        """Update the free list with the given reservation.

        Return the reserved slice.
        """
        block_size, offset = self._free.pop(pos)

        # Release the rest of the block in power-of-2 blocks
        mid = block_size // 2
        while mid >= num >= 2:
            self._release(offset + mid, block_size - mid)
            block_size = mid
            mid = block_size // 2

        end_off = offset + num

        self._release(end_off, block_size - num)

        # Store the size of the block that we allocated
        self.allocs[offset] = num
        return slice(offset, end_off)

    def realloc(self, offset: Union[int, slice], new_size: int) -> slice:
        """Reallocate the given block.

        This is optimised so that if there is extra space in the original
        allocation, it can be done without moving the block.

        This operation can fail, raising NoCapacity.

        """
        if isinstance(offset, slice):
            offset = offset.start

        try:
            size = self.allocs[offset]
        except KeyError:
            raise KeyError(f"Offset {offset} is not allocated.") from None
        if new_size <= size:
            return slice(offset, offset + new_size)

        del self.allocs[offset]
        # TODO: copying the list before use makes this operation
        # O(n) (but probably low constant factor) when it should be
        # O(log n)-ish
        free_before = list(self._free)
        self._release(offset, size)
        try:
            return self.alloc(new_size)
        except NoCapacity:
            # We don't have enough capacity, re-insert before raising
            self.allocs[offset] = size
            self._free = SortedList(free_before)
            raise

    def free(self, offset: Union[int, slice]):
        """Free the block at offset."""
        if isinstance(offset, slice):
            offset = offset.start
        try:
            size = self.allocs.pop(offset)
        except KeyError:
            raise KeyError(f"Offset {offset} is not allocated.") from None
        self._release(offset, size)
# 1871. Jump Game VII
# https://leetcode.com/problems/jump-game-vii

from sortedcontainers import SortedList
​
class Solution:
    def canReach(self, s: str, minJump: int, maxJump: int) -> bool:
        n = len(s)
        sl = SortedList([0])
        
        for i in range(1, n):
            if s[i] == "0":
                index = sl.bisect_left(i - maxJump)
                
                if 0 <= index < len(sl) and sl[index] + minJump <= i:
                    sl.add(i)
        
        return n - 1 in sl
Example #10
0
class IntervalList:
    def __init__(self, intervals):
        self.intervals = SortedList([], key=_get_lower)
        for interval in intervals:
            self.unite(interval)

    def unite(self, interval):
        """
        Inserts the given interval into the list of interval.
        Takes care to merge any adjacent intervals.
        """
        first, last = self._intersect_continuous(interval)
        if first != last:
            interval.lower = min(interval.lower, self.intervals[first].lower)
            interval.upper = max(interval.upper,
                                 self.intervals[last - 1].upper)
        del self.intervals[first:last]
        self.intervals.add(interval)

    def _intersect_continuous(self, interval):
        """
        Finds all intervals whose intersection with interval is not empty;
        also includes the two intervals the the most left and right if their
        bounds are directly adjacent to the interval. For example, if intervals
        contains:
          [..., [1, 3), [6, 8), ...]
        and this function is called with [3, 6), it includes the two intervals.
        """
        first = self.intervals.bisect_left(interval)
        last = first
        while first > 0 and \
              self.intervals[first - 1].upper >= interval.lower:
            first -= 1
        while last < len(self.intervals) and \
              self.intervals[last].lower <= interval.upper:
            last += 1
        return first, last

    def _intersect(self, interval):
        """
        Finds all intervals whose intersection with interval is not empty.
        """
        first = self.intervals.bisect_left(interval)
        last = first
        while first > 0 and \
              self.intervals[first - 1].upper > interval.lower:
            first -= 1
        while last < len(self.intervals) and \
              self.intervals[last].lower < interval.upper:
            last += 1
        return first, last

    def subtract(self, interval):
        """
        Removes the given interval from all intervals.
        Takes care to remove empty intervals.
        """
        first, last = self._intersect(interval)
        if last - first == 1:
            new_intervals = self.intervals[first].subtract(interval)
            del self.intervals[first]
            self.intervals.update(new_intervals)
        elif last - first != 0:
            if self.intervals[first].lower != interval.lower:
                self.intervals[first].upper = interval.lower
                if self.intervals[first].length > 0:
                    first = min(first + 1, len(self.intervals))
            if self.intervals[last - 1].upper != interval.upper:
                self.intervals[last - 1].lower = interval.upper
                if self.intervals[last - 1].length > 0:
                    last = max(last - 1, 0)
            del self.intervals[first:last]

    def find(self, value):
        """
        Returns the interval that contains the given value.
        """
        index = self.intervals.bisect_left(value)
        if index < len(
                self.intervals) and self.intervals[index].lower == value:
            return self.intervals[index]
        if index > 0 and self.intervals[index - 1].contains(value):
            return self.intervals[index - 1]
        return None

    def contains(self, interval):
        """
        Returns true if the list of intervals has a non-empty intersection with
        the given interval.
        """
        first, last = self._intersect(interval)
        return first != last

    def __iter__(self):
        return self.intervals.__iter__()

    def __len__(self):
        return self.intervals.__len__()
Example #11
0
class PriorityDict(MutableMapping):
    """
    A PriorityDict provides the same methods as a dict. Additionally, a
    PriorityDict efficiently maintains its keys in value sorted order.
    Consequently, the keys method will return the keys in value sorted order,
    the popitem method will remove the item with the highest value, etc.
    """
    def __init__(self, *args, **kwargs):
        """
        A PriorityDict provides the same methods as a dict. Additionally, a
        PriorityDict efficiently maintains its keys in value sorted order.
        Consequently, the keys method will return the keys in value sorted
        order, the popitem method will remove the item with the highest value,
        etc.
        If the first argument is the boolean value False, then it indicates
        that keys are not comparable. By default this setting is True and
        duplicate values are tie-breaked on the key. Using comparable keys
        improves the performance of the PriorityDict.
        An optional *iterable* argument provides an initial series of items to
        populate the PriorityDict.  Each item in the sequence must itself
        contain two items. The first is used as a key in the new dictionary,
        and the second as the key's value. If a given key is seen more than
        once, the last value associated with it is retained in the new
        dictionary.
        If keyword arguments are given, the keywords themselves with their
        associated values are added as items to the dictionary. If a key is
        specified both in the positional argument and as a keyword argument, the
        value associated with the keyword is retained in the dictionary. For
        example, these all return a dictionary equal to ``{"one": 2, "two":
        3}``:
        * ``SortedDict(one=2, two=3)``
        * ``SortedDict({'one': 2, 'two': 3})``
        * ``SortedDict(zip(('one', 'two'), (2, 3)))``
        * ``SortedDict([['two', 3], ['one', 2]])``
        The first example only works for keys that are valid Python
        identifiers; the others work with any valid keys.
        Note that this constructor mimics the Python dict constructor. If
        you're looking for a constructor like collections.Counter(...), see
        PriorityDict.count(...).
        """
        self._dict = dict()

        if len(args) > 0 and isinstance(args[0], bool):
            if args[0]:
                self._list = SortedList()
            else:
                self._list = SortedListWithKey(key=lambda tup: tup[0])
        else:
            self._list = SortedList()

        self.iloc = _IlocWrapper(self)
        self.update(*args, **kwargs)

    def clear(self):
        """Remove all elements from the dictionary."""
        self._dict.clear()
        self._list.clear()

    def clean(self, value=0):
        """
        Remove all items with value less than or equal to `value`.
        Default `value` is 0.
        """
        _list, _dict = self._list, self._dict
        pos = self.bisect_right(value)
        for key in (key for value, key in _list[:pos]):
            del _dict[key]
        del _list[:pos]

    def __contains__(self, key):
        """Return True if and only if *key* is in the dictionary."""
        return key in self._dict

    def __delitem__(self, key):
        """
        Remove ``d[key]`` from *d*.  Raises a KeyError if *key* is not in the
        dictionary.
        """
        value = self._dict[key]
        self._list.remove((value, key))
        del self._dict[key]

    def __getitem__(self, key):
        """
        Return the priority of *key* in *d*.  Raises a KeyError if *key* is not
        in the dictionary.
        """
        return self._dict[key]

    def __iter__(self):
        """
        Create an iterator over the keys of the dictionary ordered by the value
        sort order.
        """
        return iter(key for value, key in self._list)

    def __reversed__(self):
        """
        Create an iterator over the keys of the dictionary ordered by the
        reversed value sort order.
        """
        return iter(key for value, key in reversed(self._list))

    def __len__(self):
        """Return the number of (key, value) pairs in the dictionary."""
        return len(self._dict)

    def __setitem__(self, key, value):
        """Set `d[key]` to *value*."""
        if key in self._dict:
            old_value = self._dict[key]
            self._list.remove((old_value, key))
        self._list.add((value, key))
        self._dict[key] = value

    def copy(self):
        """Create a shallow copy of the dictionary."""
        result = PriorityDict()
        result._dict = self._dict.copy()
        result._list = self._list.copy()
        result.iloc = _IlocWrapper(result)
        return result

    def __copy__(self):
        """Create a shallow copy of the dictionary."""
        return self.copy()

    @classmethod
    def fromkeys(cls, iterable, value=0):
        """
        Create a new dictionary with keys from `iterable` and values set to
        `value`. The default *value* is 0.
        """
        return PriorityDict((key, value) for key in iterable)

    def get(self, key, default=None):
        """
        Return the value for *key* if *key* is in the dictionary, else
        *default*.  If *default* is not given, it defaults to ``None``,
        so that this method never raises a KeyError.
        """
        return self._dict.get(key, default)

    def has_key(self, key):
        """Return True if and only in *key* is in the dictionary."""
        return key in self._dict

    def pop(self, key, default=_NotGiven):
        """
        If *key* is in the dictionary, remove it and return its value,
        else return *default*. If *default* is not given and *key* is not in
        the dictionary, a KeyError is raised.
        """
        if key in self._dict:
            value = self._dict[key]
            self._list.remove((value, key))
            return self._dict.pop(key)
        else:
            if default == _NotGiven:
                raise KeyError
            else:
                return default

    def popitem(self, index=-1):
        """
        Remove and return item at *index* (default: -1). Raises IndexError if
        dict is empty or index is out of range. Negative indices are supported
        as for slice indices.
        """
        value, key = self._list.pop(index)
        del self._dict[key]
        return key, value

    def setdefault(self, key, default=0):
        """
        If *key* is in the dictionary, return its value.  If not, insert *key*
        with a value of *default* and return *default*.  *default* defaults to
        ``0``.
        """
        if key in self._dict:
            return self._dict[key]
        else:
            self._dict[key] = default
            self._list.add((default, key))
            return default

    def elements(self):
        """
        Return an iterator over elements repeating each as many times as its
        count. Elements are returned in value sort-order. If an element’s count
        is less than one, elements() will ignore it.
        """
        values = (repeat(key, value) for value, key in self._list)
        return chain.from_iterable(values)

    def most_common(self, count=None):
        """
        Return a list of the `count` highest priority elements with their
        priority. If `count` is not specified, `most_common` returns *all*
        elements in the dict. Elements with equal counts are ordered by key.
        """
        _list, _dict = self._list, self._dict

        if count is None:
            return [(key, value) for value, key in reversed(_list)]

        end = len(_dict)
        start = end - count

        return [(key, value) for value, key in reversed(_list[start:end])]

    def subtract(self, elements):
        """
        Elements are subtracted from an iterable or from another mapping (or
        counter). Like dict.update() but subtracts counts instead of replacing
        them. Both inputs and outputs may be zero or negative.
        """
        self -= Counter(elements)

    def tally(self, *args, **kwargs):
        """
        Elements are counted from an iterable or added-in from another mapping
        (or counter). Like dict.update() but adds counts instead of replacing
        them. Also, the iterable is expected to be a sequence of elements, not a
        sequence of (key, value) pairs.
        """
        self += Counter(*args, **kwargs)

    @classmethod
    def count(self, *args, **kwargs):
        """
        Consume `args` and `kwargs` with a Counter and use that mapping to
        initialize a PriorityDict.
        """
        return PriorityDict(Counter(*args, **kwargs))

    def update(self, *args, **kwargs):
        """
        Update the dictionary with the key/value pairs from *other*, overwriting
        existing keys.
        *update* accepts either another dictionary object or an iterable of
        key/value pairs (as a tuple or other iterable of length two).  If
        keyword arguments are specified, the dictionary is then updated with
        those key/value pairs: ``d.update(red=1, blue=2)``.
        """
        _list, _dict = self._list, self._dict

        if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], Mapping):
            items = args[0]
        else:
            items = dict(*args, **kwargs)

        if (10 * len(items)) > len(_dict):
            _dict.update(items)
            _list.clear()
            _list.update((value, key) for key, value in iteritems(_dict))
        else:
            for key, value in iteritems(items):
                old_value = _dict[key]
                _list.remove((old_value, key))
                _dict[key] = value
                _list.add((value, key))

    def index(self, key):
        """
        Return the smallest *i* such that `d.iloc[i] == key`.  Raises KeyError
        if *key* is not present.
        """
        value = self._dict[key]
        return self._list.index((value, key))

    def bisect_left(self, value):
        """
        Similar to the ``bisect`` module in the standard library, this returns
        an appropriate index to insert *value* in PriorityDict. If *value* is
        already present in PriorityDict, the insertion point will be before (to
        the left of) any existing entries.
        """
        return self._list.bisect_left((value,))

    def bisect(self, value):
        """Same as bisect_left."""
        return self._list.bisect((value,))

    def bisect_right(self, value):
        """
        Same as `bisect_left`, but if *value* is already present in
        PriorityDict, the insertion point will be after (to the right
        of) any existing entries.
        """
        return self._list.bisect_right((value, _Biggest))

    def __iadd__(self, that):
        """Add values from `that` mapping."""
        _list, _dict = self._list, self._dict
        if len(_dict) == 0:
            _dict.update(that)
            _list.update((value, key) for key, value in iteritems(_dict))
        elif len(that) * 3 > len(_dict):
            _list.clear()
            for key, value in iteritems(that):
                if key in _dict:
                    _dict[key] += value
                else:
                    _dict[key] = value
            _list.update((value, key) for key, value in iteritems(_dict))
        else:
            for key, value in iteritems(that):
                if key in _dict:
                    old_value = _dict[key]
                    _list.remove((old_value, key))
                    value = old_value + value
                _dict[key] = value
                _list.add((value, key))
        return self

    def __isub__(self, that):
        """Subtract values from `that` mapping."""
        _list, _dict = self._list, self._dict
        if len(_dict) == 0:
            _dict.clear()
            _list.clear()
        elif len(that) * 3 > len(_dict):
            _list.clear()
            for key, value in iteritems(that):
                if key in _dict:
                    _dict[key] -= value
            _list.update((value, key) for key, value in iteritems(_dict))
        else:
            for key, value in iteritems(that):
                if key in _dict:
                    old_value = _dict[key]
                    _list.remove((old_value, key))
                    value = old_value - value
                    _dict[key] = value
                    _list.add((value, key))
        return self

    def __ior__(self, that):
        """Or values from `that` mapping (max(v1, v2))."""
        _list, _dict = self._list, self._dict
        if len(_dict) == 0:
            _dict.update(that)
            _list.update((value, key) for key, value in iteritems(_dict))
        elif len(that) * 3 > len(_dict):
            _list.clear()
            for key, value in iteritems(that):
                if key in _dict:
                    old_value = _dict[key]
                    _dict[key] = old_value if old_value > value else value
                else:
                    _dict[key] = value
            _list.update((value, key) for key, value in iteritems(_dict))
        else:
            for key, value in iteritems(that):
                if key in _dict:
                    old_value = _dict[key]
                    _list.remove((old_value, key))
                    value = old_value if old_value > value else value
                _dict[key] = value
                _list.add((value, key))
        return self

    def __iand__(self, that):
        """And values from `that` mapping (min(v1, v2))."""
        _list, _dict = self._list, self._dict
        if len(_dict) == 0:
            _dict.clear()
            _list.clear()
        elif len(that) * 3 > len(_dict):
            _list.clear()
            for key, value in iteritems(that):
                if key in _dict:
                    old_value = _dict[key]
                    _dict[key] = old_value if old_value < value else value
            _list.update((value, key) for key, value in iteritems(_dict))
        else:
            for key, value in iteritems(that):
                if key in _dict:
                    old_value = _dict[key]
                    _list.remove((old_value, key))
                    value = old_value if old_value < value else value
                    _dict[key] = value
                    _list.add((value, key))
        return self

    def __add__(self, that):
        """Add values from this and `that` mapping."""
        result = PriorityDict()
        _list, _dict = result._list, result._dict
        _dict.update(self._dict)
        for key, value in iteritems(that):
            if key in _dict:
                _dict[key] += value
            else:
                _dict[key] = value
        _list.update((value, key) for key, value in iteritems(_dict))
        return result

    def __sub__(self, that):
        """Subtract values in `that` mapping from this."""
        result = PriorityDict()
        _list, _dict = result._list, result._dict
        _dict.update(self._dict)
        for key, value in iteritems(that):
            if key in _dict:
                _dict[key] -= value
        _list.update((value, key) for key, value in iteritems(_dict))
        return result

    def __or__(self, that):
        """Or values from this and `that` mapping."""
        result = PriorityDict()
        _list, _dict = result._list, result._dict
        _dict.update(self._dict)
        for key, value in iteritems(that):
            if key in _dict:
                old_value = _dict[key]
                _dict[key] = old_value if old_value > value else value
            else:
                _dict[key] = value
        _list.update((value, key) for key, value in iteritems(_dict))
        return result

    def __and__(self, that):
        """And values from this and `that` mapping."""
        result = PriorityDict()
        _list, _dict = result._list, result._dict
        _dict.update(self._dict)
        for key, value in iteritems(that):
            if key in _dict:
                old_value = _dict[key]
                _dict[key] = old_value if old_value < value else value
        _list.update((value, key) for key, value in iteritems(_dict))
        return result

    def __eq__(self, that):
        """Compare two mappings for equality."""
        if isinstance(that, PriorityDict):
            that = that._dict
        return self._dict == that

    def __ne__(self, that):
        """Compare two mappings for inequality."""
        if isinstance(that, PriorityDict):
            that = that._dict
        return self._dict != that

    def __lt__(self, that):
        """Compare two mappings for less than."""
        if isinstance(that, PriorityDict):
            that = that._dict
        _dict = self._dict
        return (_dict != that and self <= that)

    def __le__(self, that):
        """Compare two mappings for less than equal."""
        if isinstance(that, PriorityDict):
            that = that._dict
        _dict = self._dict
        return (len(_dict) <= len(that) and
                all(_dict[key] <= that[key] if key in that else False
                    for key in _dict))

    def __gt__(self, that):
        """Compare two mappings for greater than."""
        if isinstance(that, PriorityDict):
            that = that._dict
        _dict = self._dict
        return (_dict != that and self >= that)

    def __ge__(self, that):
        """Compare two mappings for greater than equal."""
        if isinstance(that, PriorityDict):
            that = that._dict
        _dict = self._dict
        return (len(_dict) >= len(that) and
                all(_dict[key] >= that[key] if key in _dict else False
                    for key in that))

    def isdisjoint(self, that):
        """
        Return True if no key in `self` is also in `that`.
        This doesn't check that the value is greater than zero.
        To remove keys with value less than or equal to zero see *clean*.
        """
        return not any(key in self for key in that)

    def items(self):
        """
        Return a list of the dictionary's items (``(key, value)``
        pairs). Items are ordered by their value from least to greatest.
        """
        return list((key, value) for value, key in self._list)

    def iteritems(self):
        """
        Return an iterable over the items (``(key, value)`` pairs) of the
        dictionary. Items are ordered by their value from least to greatest.
        """
        return iter((key, value) for value, key in self._list)

    @not26
    def viewitems(self):
        """
        In Python 2.7 and later, return a new `ItemsView` of the dictionary's
        items. Beware iterating the `ItemsView` as items are unordered.
        In Python 2.6, raise a NotImplementedError.
        """
        if hexversion < 0x03000000:
            return self._dict.viewitems()
        else:
            return self._dict.items()

    def keys(self):
        """
        Return a list of the dictionary's keys. Keys are ordered
        by their corresponding value from least to greatest.
        """
        return list(key for value, key in self._list)

    def iterkeys(self):
        """
        Return an iterable over the keys of the dictionary. Keys are ordered
        by their corresponding value from least to greatest.
        """
        return iter(key for value, key in self._list)

    @not26
    def viewkeys(self):
        """
        In Python 2.7 and later, return a new `KeysView` of the dictionary's
        keys. Beware iterating the `KeysView` as keys are unordered.
        In Python 2.6, raise a NotImplementedError.
        """
        if hexversion < 0x03000000:
            return self._dict.viewkeys()
        else:
            return self._dict.keys()

    def values(self):
        """
        Return a list of the dictionary's values. Values are
        ordered from least to greatest.
        """
        return list(value for value, key in self._list)

    def itervalues(self):
        """
        Return an iterable over the values of the dictionary. Values are
        iterated from least to greatest.
        """
        return iter(value for value, key in self._list)

    @not26
    def viewvalues(self):
        """
        In Python 2.7 and later, return a `ValuesView` of the dictionary's
        values. Beware iterating the `ValuesView` as values are unordered.
        In Python 2.6, raise a NotImplementedError.
        """
        if hexversion < 0x03000000:
            return self._dict.viewvalues()
        else:
            return self._dict.values()

    def __repr__(self):
        """Return a string representation of PriorityDict."""
        return 'PriorityDict({0})'.format(repr(dict(self)))

    def _check(self):
        self._list._check()
        assert len(self._dict) == len(self._list)
        assert all(key in self._dict and self._dict[key] == value
                   for value, key in self._list)
class Analyse():
    def __init__(self, config):

        self.year = config['YEAR']
        self.startTime = config['START_TIME']
        self.endTime = config['END_TIME']
        self.stockList = config['STOCK_LIST']
        self.mode = config['MODE']
        self.logBucket = config['LOG_BUCKET_DATA']
        self.hedgeFlag = config['HEDGE']
        self.hedgeStock = config['HEDGE_STOCK']

        self.divideByVol = config['DIVIDE_BY_VOLATILITY']

        self.modStockList = self.stockList

        if (self.hedgeFlag):
            self.betaCorrelation = config['BETA_CORR']
            self.modStockList = [config['HEDGE_STOCK']] + self.stockList
            self.corrFlag = config['BETA_CORR_TYPE']

        if (self.mode == 'bucket'):
            self.bucketSize = config['BUCKET_SIZE']
            self.numBucket = config['NUM_BUCKET']
        elif (self.mode == 'percentile'):
            self.bucketSize = config['BUCKET_SIZE']
            self.minSize = config['MIN_SIZE']
            self.maxSize = config['MAX_SIZE']
            self.absFlag = config['ABS_FLAG']

        config['STOCK_LIST'] = self.modStockList
        # Datastore contains functions to read and update prices
        self.dataStore = dataStore(config)
        config['STOCK_LIST'] = self.stockList

        # Class members containing relevant Statistics
        # self.results: Dictionary containing stock names as keys
        #               Maps to a list of lists, where each list member
        #               contains gapSize, timeStamp, Open/Close prices
        #               along with holding periods, etc
        self.results = {}
        self.gapListNormalized = []

        self.prevCloseVWAPWindow = config['VWAP_PREV_CLOSE_WINDOW']
        self.currOpenVWAPWindow = config['VWAP_CURR_OPEN_WINDOW']
        self.posEntryVWAPWindow = config['VWAP_POSITION_ENTRY_WINDOW']
        self.posExitVWAPWindow = config['VWAP_POSITION_EXIT_WINDOW']

        self.printFlag = 0

        self.stopLoss = config['STOP_LOSS']
        self.targetPrice = config['TARGET_PRICE']

        self.tTestFlag = config['T_TEST_FLAG']
        if (self.tTestFlag):
            self.profitByGapPercentile = {}

            for i in range(0, 100):
                self.profitByGapPercentile[i] = []

        self.stockReturns = {}

    def loadData(self):
        '''
        Loads price data for the specified year and stock list
        Returns:
            None, only class members are modified
        '''
        self.dataStore.loadPriceData()

        for stock in self.stockList:
            price = pd.DataFrame(
                self.dataStore.priceDataList[stock][:]).iloc[:, 6]
            returns = ((price / price.shift(1)) - 1)[1:]
            self.stockReturns[stock] = returns
        if (self.hedgeFlag):
            price = pd.DataFrame(
                self.dataStore.priceDataList[self.hedgeStock][:]).iloc[:, 6]
            returns = ((price / price.shift(1)) - 1)[1:]
            self.stockReturns[self.hedgeStock] = returns
        # print(self.stockReturns)

    def getRetList(self, stock):

        price = pd.DataFrame(
            self.dataStore.priceDataList[stock][::minInDay]).iloc[:, 6]
        price = ((price / price.shift(1)) - 1)[1:]

        return price

    def getBenchmarkVolatility(self):

        price = pd.DataFrame(
            self.hedgePriceList[self.hedgeStock][::minInDay]).iloc[:, 6]
        price = ((price / price.shift(1)) - 1)[1:]

        return price

    def getVolatilityNDays(self, stock, n, currTimeRow):
        """
        Gets the volatility by taking returns of close prices for the last n days
        and does P(t) / P(t-1) - 1 for each of the n days and takes stDev
        """

        # price = pd.DataFrame(self.dataStore.priceDataList[stock][currTimeRow - 1 - (n * 375):currTimeRow - 1]).iloc[:, 6]
        # returns = ((price / price.shift(1)) - 1)[1:]

        returns = self.stockReturns[stock].iloc[currTimeRow - 1 -
                                                (n * 375):currTimeRow - 1]

        if (debug):
            print("Volatility: " + str(np.std(returns)))

        return np.std(returns)

    def getCorrelation(self, stock1, stock2, i1, i2, n):
        """
        Takes the prices of two stocks, calculates their return and gives their correlation
        """

        # price1 = pd.DataFrame(self.dataStore.priceDataList[stock1][-(n * 375) - 1 + i1:i1]).iloc[:, 6]
        # price2 = pd.DataFrame(self.dataStore.priceDataList[stock2][-(n * 375) - 1 + i2:i2]).iloc[:, 6]

        returns1 = self.stockReturns[stock1].iloc[i1 - 1 - (n * 375):i1 - 1]
        returns2 = self.stockReturns[stock2].iloc[i2 - 1 - (n * 375):i2 - 1]

        print(i1, i2)

        # print(returns1[-10:])
        # print(returns2[-10:])

        # if(len(price1) > len(price2)):
        #     # print("Price1: " + str(price1))
        #     # print("Price2: " + str(price2))
        #     price1 = price1[-len(price2):]
        #     print(i1,i2,len(price1),len(price2))

        # if(len(price2) > len(price1)):
        #     price2 = price2[-len(price1):]
        #     print(i1,i2,len(price1),len(price2))

        correlation = np.corrcoef(returns1, returns2)[1][0]

        return correlation

    def getVolAvgPrice(self, stock, left, right):
        '''
        Computes the volume weighted price for the range [left, right)
        price = (low + high + open + close)/4
        '''
        if (debug):
            print('\n' + ''.join(['*'] * 50))
            print("Stock prices")
            print(left, right)
            print("Left price: " +
                  str(self.dataStore.priceDataList[stock][left]))
            print("Right price: " +
                  str(self.dataStore.priceDataList[stock][right]))

        price = np.array(self.dataStore.priceDataList[stock][left:right])[:,
                                                                          5:]
        price = price.astype(np.float64)

        # 5, 6, 7, 8, 9: Open, Close, Low, High, Volume
        # After trimming off strings, 0, 1, 2, 3, 4: Opne, Close, Low, High, Volume
        avgPrice = (price[:, 0] + price[:, 1] + price[:, 2] +
                    price[:, 3]) / 4.0
        volume = price[:, 4]
        volAvgPrice = np.average(avgPrice, weights=volume)

        return volAvgPrice

    def getTTestScores(self,
                       boundary,
                       profitByGapPercentileLocal,
                       verbose=False):
        #Returns the T test score and p-value of two arrays

        arr1 = []
        arr2 = []

        for i in range(1, boundary + 1):
            arr1 += profitByGapPercentileLocal[i]

        for i in range(boundary + 1, 99):
            arr2 += profitByGapPercentileLocal[i]

        tTest = ttest_ind(arr1, arr2)

        tValue, pValue = tTest[0], tTest[1]

        if (verbose):
            print("Boundary: " + str(boundary))
            print("T Value: " + str(tValue))
            print("P Value: " + str(pValue))

        return tValue, pValue

    def getGapStats(self, holdPeriodList, volType='nGapVol', verbose=False):
        '''
        Gives the statistics (Gap trading) for all hold periods specified
        The stats include
        timestamp, curr open price (after VWAP), prev close price (after VWAP), volatility
        holding period (H), min price/max price in interval, closing price after H etc
        Args:
            holdPeriodList: Contains holding periods as number of minutes
            volType; dailyVol or nDayVol (n = 30 by default)
        Returns:
            Dictionary as described above
        '''

        statList = {}
        priceList = {}
        gapList = {}

        if (self.hedgeFlag):
            # BM is benchmark
            gapListBM = []
            volListBM = []
            timeListBM = []
            # retList contains daily returns
            retListBM = []
            priceListBM = []
            priceTimeBM = []
            #Stores all the timestamps for which the benchmark is indexed
            benchmarkTimeStamps = [
                eachList[0]
                for eachList in self.dataStore.priceDataList[self.hedgeStock]
            ]

        volN = 70  # For standard volatility calculation of gapsize
        if (volType != 'stdVol'):
            volN = 30

        volDays = 70  # For standard volatility of entire calculation of returns

        for stock in self.modStockList:
            # Perform analysis for each stock

            infoList = self.dataStore.priceDataList[stock]
            statList[stock] = []
            priceList[stock] = []
            gapList[stock] = []
            # gapListBenchmark[self.hedgeStock] = []

            retList = self.getRetList(stock)
            prevTime = 0
            print 'Currently analysing:', stock

            for i in range(len(infoList)):

                currTime = infoList[i][0]
                currTimeStamp = datetime.fromtimestamp(currTime)
                currDay = currTimeStamp.date()
                currHour = currTimeStamp.time().hour
                currMins = currTimeStamp.time().minute

                # Account for duplicates
                if (prevTime == currTime):
                    continue
                prevTime = currTime

                if (not (self.startTime <= currTime <= self.endTime)):
                    # Check if it is in the valid range
                    continue

                if ((currHour == 9) and (currMins == 15)):
                    # Checking for day starting time

                    if (stock == 'SBIN' and currTimeStamp.date().day == 9
                            and currTimeStamp.date().month == 11
                            and self.year == 2016):
                        self.printFlag = 1

                    if (debug):
                        print('\n' + ''.join(['*'] * 50))

                    #getting prices for stock
                    currOpen = self.getVolAvgPrice(stock, i,
                                                   i + self.currOpenVWAPWindow)
                    prevClose = self.getVolAvgPrice(
                        stock, i - self.prevCloseVWAPWindow, i)
                    posEntryPrice = self.getVolAvgPrice(
                        stock, i + self.currOpenVWAPWindow,
                        i + self.currOpenVWAPWindow + self.posEntryVWAPWindow)

                    if ((self.hedgeFlag) and (self.hedgeStock == stock)):
                        priceListBM.append(currOpen)
                        priceTimeBM.append(currTime)

                    priceList[stock].append(currOpen)
                    gapList[stock].append((currOpen - prevClose) / prevClose)

                    # Not enough samples to compute std dev, added five to handle edge cases
                    if (len(gapList[stock]) < volN + 5):
                        continue

                    # Refers to the stats common accross the holding periods
                    commStats = {}
                    commStats['time'] = currTime
                    commStats['readableTime'] = datetime.fromtimestamp(
                        currTime)
                    commStats['ticker'] = stock
                    commStats['currOpen'] = currOpen
                    commStats['prevClose'] = prevClose
                    commStats['posEntryPrice'] = posEntryPrice

                    commStats['gapSize'] = ((currOpen - prevClose) / prevClose)

                    if (self.absFlag):
                        commStats['gapSize'] = np.abs(commStats['gapSize'])

                    if (volType == 'stdVol'):
                        commStats['volatility'] = np.std(
                            retList[len(gapList[stock]) -
                                    volN:len(gapList[stock])])
                    else:
                        commStats['volatility'] = np.std(
                            gapList[stock][-volN:])
                    commStats['gapRatio'] = commStats['gapSize'] / commStats[
                        'volatility']

                    #correct volatility using stDev of returns for 70 days of per minute returns
                    commStats['stockVolatility'] = self.getVolatilityNDays(
                        stock, volDays, i)

                    if (self.hedgeFlag):
                        if (stock != self.hedgeStock):

                            # Binary search in the timeStamps of the benchmark
                            row = bisect(timeListBM, currTime) - 1
                            retBM = retListBM[row]
                            volBM = volListBM[row]

                            bmI = bisect_left(benchmarkTimeStamps, currTime)
                            posEntryBM = self.getVolAvgPrice(
                                self.hedgeStock, bmI + self.currOpenVWAPWindow,
                                bmI + self.currOpenVWAPWindow +
                                self.posEntryVWAPWindow)

                            #modifying volatility
                            commStats[
                                'indexVolatility'] = self.getVolatilityNDays(
                                    self.hedgeStock, volDays, bmI)

                            if (debug):
                                #Prints the timestamps of both the current stock row and the current benchmark row
                                print(
                                    self.dataStore.priceDataList[stock][i][0],
                                    self.dataStore.priceDataList[
                                        self.hedgeStock][bmI][0])

                            commStats['posEntryPriceBM'] = posEntryBM

                            if (self.corrFlag != 'constant'):
                                priceRow = bisect(priceTimeBM, currTime)

                                # print len(priceList[stock][-volN:])
                                # print len(priceList[stock])
                                # print len(priceListBM)
                                # print -volN + priceRow, priceRow
                                # print len(priceList[self.hedgeStock][-volN + priceRow: priceRow])
                                self.betaCorrelation = np.corrcoef(
                                    priceList[stock][-volN:],
                                    priceListBM[-volN +
                                                priceRow:priceRow])[1][0]

                                # self.betaCorrelation = self.getCorrelation(stock,self.hedgeStock,i,bmI,volDays)

                            # beta = self.betaCorrelation * (volBM / commStats['volatility'])
                            # beta = self.betaCorrelation * (commStats['volatility'] / volBM)
                            beta = self.betaCorrelation * (
                                commStats['stockVolatility'] /
                                commStats['indexVolatility'])

                            if (debug):
                                print("Stock Volatility: " +
                                      str(commStats['stockVolatility']))
                                print("Index Volatility: " +
                                      str(commStats['indexVolatility']))

                            commStats['betaCorr'] = self.betaCorrelation
                            commStats['Beta'] = beta

                            if (verbose):
                                print(''.join(['*'] * 50))
                                print("Beta : " + str(beta))
                                print("Stock : " + stock)
                                print("Stock currOpen: " + str(currOpen))
                                print("Stock prevClose: " + str(prevClose))
                                print("Stock Return: " +
                                      str(commStats['gapSize']))
                                print("Stock Volatility: " +
                                      str(commStats['volatility']))
                                print("Stock Normalized Return: " +
                                      str(commStats['gapRatio']))
                                print("Benchmark Return: " + str(retBM))
                                print("Benchmark Volatility: " + str(volBM))
                                print("Benchmark Normalized Return: " +
                                      str(retBM / volBM))

                        else:
                            timeListBM.append(currTime)
                            retListBM.append(commStats['gapSize'])
                            volListBM.append(commStats['volatility'])

                    minPriceList = [float(infoList[i][6])]
                    maxPriceList = [float(infoList[i][6])]
                    # Identifying the array index limit
                    holdLim = min(max(holdPeriodList), len(infoList) - i - 1)
                    for j in range(holdLim):
                        minPriceList.append(
                            min(minPriceList[-1], float(infoList[i + j][6])))
                        maxPriceList.append(
                            max(maxPriceList[-1], float(infoList[i + j][6])))

                    #Appending volatility normalized gap value for determining distribution plot
                    self.gapListNormalized.append(commStats['gapSize'] /
                                                  commStats['volatility'])

                    reachedStopOrTarget = 0
                    stopOrTargetRelReturn = 0

                    for hold in holdPeriodList:
                        tmpStats = commStats.copy()

                        minPrice = minPriceList[min(hold, holdLim)]
                        maxPrice = maxPriceList[min(hold, holdLim)]

                        tmpStats['holdPeriod'] = hold
                        tmpStats['min'] = minPrice
                        tmpStats['max'] = maxPrice
                        tmpStats['finClose'] = infoList[min(
                            (i + self.currOpenVWAPWindow +
                             self.posEntryVWAPWindow + hold),
                            len(infoList) - 1)][6]

                        #Normalizing the volatility based on hold period
                        tmpStats['stockVolAfterNorm'] = commStats[
                            'stockVolatility'] * np.sqrt(hold)

                        if (self.hedgeFlag):
                            bmI = bisect_left(benchmarkTimeStamps, currTime)
                            tmpStats['finCloseBM'] = self.dataStore.priceDataList[self.hedgeStock][min((bmI + self.currOpenVWAPWindow + self.posEntryVWAPWindow + hold)\
                                , len(self.dataStore.priceDataList[self.hedgeStock]) - 1)][6]

                            # exitTime = infoList[i + hold][0]

                            # bmI = bisect_left(benchmarkTimeStamps, exitTime)
                            # tmpStats['finCloseBM'] = self.dataStore.priceDataList[self.hedgeStock][min((bmIExit), len(infoList) -1)][6]

                        if (not (stock == self.hedgeStock)):
                            #Calculating profits and all
                            tmpStats['profit'] = ((- np.sign(tmpStats['currOpen'] - tmpStats['prevClose'])) * \
                                ((tmpStats['finClose'] - tmpStats['posEntryPrice']) / tmpStats['posEntryPrice']))
                            tmpStats['absReturn'] = tmpStats['profit']
                            tmpStats['absReturnPerUnitVol'] = tmpStats[
                                'absReturn'] / tmpStats['stockVolAfterNorm']

                            if (self.hedgeFlag):
                                tmpStats['marketReturn'] = (
                                    (tmpStats['finCloseBM'] -
                                     tmpStats['posEntryPriceBM']) /
                                    tmpStats['posEntryPriceBM'])
                                tmpStats['returnOffset'] = (
                                    (tmpStats['finCloseBM'] -
                                     tmpStats['posEntryPriceBM']) /
                                    tmpStats['posEntryPriceBM']
                                ) * tmpStats['Beta']
                                tmpStats['relReturn'] = tmpStats['profit'] + (
                                    np.sign(tmpStats['currOpen'] -
                                            tmpStats['prevClose']) *
                                    tmpStats['returnOffset'])
                                tmpStats['relReturnPerUnitVol'] = tmpStats[
                                    'relReturn'] / tmpStats['stockVolAfterNorm']

                                if ((tmpStats['relReturn'] <= self.stopLoss or
                                     tmpStats['relReturn'] >= self.targetPrice)
                                        and reachedStopOrTarget == 0):
                                    reachedStopOrTarget = 1
                                    stopOrTargetRelReturn = tmpStats[
                                        'relReturn']

                                if (reachedStopOrTarget):
                                    tmpStats[
                                        'relReturnWithStopLoss'] = stopOrTargetRelReturn
                                else:
                                    tmpStats[
                                        'relReturnWithStopLoss'] = tmpStats[
                                            'relReturn']

                            else:
                                tmpStats['relReturn'] = tmpStats['profit']
                                tmpStats['relReturnPerUnitVol'] = tmpStats[
                                    'absReturnPerUnitVol']

                            # tmpStats['profitDividedByVol'] = tmpStats['relReturn'] / tmpStats['stockVolAfterNorm']

                        if (self.printFlag == 1):
                            for key in tmpStats:
                                print(key + ": " + str(tmpStats[key]))

                        statList[stock].append(tmpStats)

                        if (not (stock == self.hedgeStock)):
                            grandDict[stock].append(tmpStats)
                            # grandDF.append(tmpStats)

                    self.printFlag = 0

        self.results = statList

        # print sorted([statList[key][x]['gapRatio'] for key in statList.keys() for x in range(len(statList[key]))])[-1000:-900]

        return statList

    def compileResults(self, holdPeriodList):
        '''
        Compile the results extracted from getGapStats()
        The rows are indexed with RELATIVE RANK
        The columns are Count (For all stocks), also compute
        stock based results. E, P, R fraction.
        Win Rate: The fraction of actual fades
        Anti: Average profit on winning fade trades
        With: Average loss on losing fade trades 
        Exp: Expectation of profit
        Args:
            Hold period list, should be consistent with getGapStats()
        Returns:
            Matrix with the following column convention
            0: Count, 1: E, 2: P, 3: R, 4: P(S), 5: Anti, 6: With, 7:Exp
        '''

        self.timeWiseStats = {}
        self.cumStats = {}

        for hold in holdPeriodList:
            # numStocks rows, column mapping is given above
            if self.mode == 'relative':
                numRows = len(self.stockList)
            elif self.mode == 'percentile':
                numRows = int(100 / self.bucketSize)
            else:
                numRows = (2 * self.numBucket) + 1
            self.cumStats[hold] = np.zeros((numRows, 8))
            self.timeWiseStats[hold] = {}

            if (self.logBucket):
                # Stores a list of timetamps for each bucket
                # Stores a list of
                self.bucketTimeList = []
                self.bucketTradeList = []
                tmpDict = {key: [] for key in self.stockList}
                for i in range(numRows):
                    self.bucketTimeList.append(list())
                    self.bucketTradeList.append(tmpDict.copy())

        for stockId in range(len(self.stockList)):

            stock = self.stockList[stockId]

            for i in range(len(self.results[stock])):

                tmpStats = self.results[stock][i]

                time = tmpStats['time']
                hold = tmpStats['holdPeriod']
                currOpen = tmpStats['currOpen']
                prevClose = tmpStats['prevClose']
                minPrice = tmpStats['min']
                maxPrice = tmpStats['max']
                finClose = tmpStats['finClose']
                gapRatio = tmpStats['gapRatio']
                gapSize = tmpStats['gapSize']
                # volatility= tmpStats['volatility']
                posEntry = tmpStats['posEntryPrice']
                finClose = tmpStats['finClose']

                volatility = tmpStats['stockVolAfterNorm']

                #Hedging support, not technically hedging, just offsetting with respect to the index return
                if (self.hedgeFlag):
                    hedge = (
                        (tmpStats['finCloseBM'] - tmpStats['posEntryPriceBM'])
                        / tmpStats['posEntryPriceBM']) * tmpStats['Beta']
                    if (self.divideByVol):
                        hedge /= volatility

                # Initial 8 elements represent the standard stats
                # The last ones will be used for ranking later
                tmpArr = np.zeros(12)
                tmpArr[0] += 1
                tmpArr[8] = stockId
                tmpArr[9] = gapSize
                tmpArr[10] = gapRatio
                tmpArr[11] = stockId

                targetPrice = finClose

                profit = ((-np.sign(currOpen - prevClose)) *
                          ((targetPrice - posEntry) / posEntry))

                if (self.divideByVol):
                    profit /= volatility

                if (self.hedgeFlag):
                    profit -= (-np.sign(currOpen - prevClose)) * hedge

                fillFlag = np.sign(profit)

                if (fillFlag < 0):
                    # Refers to the E case i.e. extension
                    tmpArr[1] += 1
                    tmpArr[6] += profit
                else:
                    if ((currOpen - prevClose) *
                        (prevClose - targetPrice) < 0):
                        # Refers to the P case i.e. partial fill
                        tmpArr[2] += 1
                    else:
                        # Refers to the R case i.e. reversal
                        tmpArr[3] += 1
                    # Adding profits
                    tmpArr[5] += profit

                # Adding the result to the corresponding time in the dict
                if (time not in self.timeWiseStats[hold]):
                    self.timeWiseStats[hold][time] = []
                self.timeWiseStats[hold][time].append(tmpArr)

        for hold in holdPeriodList:

            if self.mode == 'percentile':
                minSize = self.minSize
                maxSize = self.maxSize
                self.gapQueue = deque([], maxlen=maxSize)
                self.orderedGaps = SortedList(load=50)

            for time in sorted(self.timeWiseStats[hold].keys()):

                if (self.mode == 'relative'):
                    # Sort the list according to the magnitude of gap size
                    self.timeWiseStats[hold][time].sort(
                        key=lambda x: np.abs(x[-1]), reverse=True)
                    for i in range(len(self.timeWiseStats[hold][time])):
                        self.cumStats[hold][i] += self.timeWiseStats[hold][
                            time][i][:8]

                elif (self.mode == 'percentile'):

                    newGapLen = len(self.timeWiseStats[hold][time])
                    newValList = []
                    # If there are enough elements for identifying percentile
                    if (len(self.gapQueue) >= minSize):

                        for i in range(newGapLen):
                            searchKey = self.timeWiseStats[hold][time][i][10]
                            # if (self.absFlag):
                            #     searchKey = np.abs(searchKey)
                            percentile = self.orderedGaps.bisect_left(
                                searchKey)
                            currSize = len(self.gapQueue)
                            # To avoid having percentile as 1.0, since percentile <= percSize + 1
                            percentile = percentile / (currSize + 2.0)
                            row = int(percentile * int(100 / self.bucketSize))
                            # print row
                            self.cumStats[hold][row] += self.timeWiseStats[
                                hold][time][i][:8]
                            if (self.tTestFlag):
                                self.profitByGapPercentile[int(
                                    percentile * 100)].append(
                                        self.timeWiseStats[hold][time][i][5] +
                                        self.timeWiseStats[hold][time][i][6])

                            if (self.logBucket):
                                # Adding time to this bucket's list
                                self.bucketTimeList[row].append(time)
                                # Since at least one of these is zero, by construction
                                profit = self.timeWiseStats[hold][time][i][
                                    5] + self.timeWiseStats[hold][time][i][6]
                                stockId = int(
                                    self.timeWiseStats[hold][time][i][11])
                                self.bucketTradeList[row][
                                    self.stockList[stockId]].append(profit)
                                bucketTradeListGlobal[row][
                                    self.stockList[stockId]].append(profit)

                        # Updating the queue and removing elements from the tree
                        for i in range(newGapLen):
                            lastVal = self.gapQueue.popleft()
                            self.orderedGaps.remove(lastVal)

                    for i in range(newGapLen):
                        searchKey = self.timeWiseStats[hold][time][i][10]
                        # if (self.absFlag):
                        #     searchKey = np.abs(searchKey)
                        newValList.append(searchKey)

                    # Adding the new values to the queue simultaneously
                    self.gapQueue.extend(newValList)
                    # Adding the new values to the tree simultaneously
                    self.orderedGaps.update(newValList)

                else:
                    for i in range(len(self.timeWiseStats[hold][time])):
                        # Sort the list according to the magnitude of gap size
                        gapRatio = self.timeWiseStats[hold][time][i][10]

                        # Get the position in the matrix, note that the bucket sizes are of size 10%
                        bucket = int(
                            np.sign(gapRatio) *
                            int(np.abs(gapRatio * 10) / self.bucketSize))
                        bucket = int(
                            np.sign(bucket) * self.numBucket
                        ) if np.abs(bucket) >= self.numBucket else bucket
                        row = self.numBucket + bucket

                        self.cumStats[hold][row] += self.timeWiseStats[hold][
                            time][i][:8]

    def tTestWrapper(self, profitByGapPercentile, verbose=True):
        """
        Tries various boundary values and gets the stats for each value from 1..99 as the boundary for percentile and
        Perfroms T Test on the profits >=value and <=value arrays
        """
        print(''.join(['*'] * 50))
        print("Cumulative Stats")

        if (self.tTestFlag):
            for i in range(10, 100, 10):
                tValue, pValue = self.getTTestScores(i, profitByGapPercentile)
                if (verbose):
                    print("Boundary: " + str(i))
                    print("T Value: " + str(tValue))
                    print("P Value: " + str(pValue))

    def getProfitGapPercentile(self):
        return self.profitByGapPercentile

    def finalizeStats(self, holdPeriodList):
        '''
        Finally processes the stats matrices, note that the resulting matrices
        cannot be compiled again directly as frequencies have become probs 
        '''

        for hold in holdPeriodList:
            self.cumStats[hold] = processStatMatrix(self.cumStats[hold])

    def plotDistribution(self, plotSeries, saveAsFile=False, logValues=False):
        '''
        Plots a histogram for the given plotsSeries
        Args:
            saveAsFile: Whether to save to file or plotting on screen
            logValues: Whether the y axis is log scaled
        Return:
            None, side effects could include saving a file
        '''

        stDev = np.std(plotSeries)

        #xLabels from -3*sigma to 3*sigma
        xLabels = np.array(range(-3, 4)) * stDev

        plt.figure(figsize=(100, 100))
        fig, ax = plt.subplots(1, 1)
        axes = plt.gca()
        plt.hist(plotSeries, bins=100, log=logValues)
        plt.xlabel("Normalized Gap Size")
        plt.ylabel("Number of Gap Sizes")
        axes.set_xlim([xLabels[0] - 0.5, xLabels[-1] + 0.5])

        ax.set_xticks(xLabels)

        plt.tight_layout()

        if (saveAsFile):
            plt.savefig("results/gapDistribution.svg")
        else:
            plt.show()
Example #13
0
ann_id = 0
for gen_id in tqdm(range(TOT_GEN)):
    remain = 2048
    current_height = 0
    noceil = 0

    seg_id = random.randint(0,len(top_segs)-1)
    top_segment = top_segs[seg_id]
    # print(top_segment)
    remain -= top_segment['height']
    file_name = "top" + str(seg_id)
    top_frq[seg_id] += 1

    bot_segment = None
    if remain >= bot_segs[0]['height']:
        idx = min( bot_segs.bisect_left({'height': remain}) ,len(bot_segs)-1)
        seg_id = random.randint(0, idx)
        bot_segment = bot_segs[seg_id]
        remain -= bot_segment['height']
        file_name += "-bot" + str(seg_id)
        bot_frq[seg_id] += 1
    # print(bot_segment)

    gen_image = Image.new("L", (1447, 2048), color=255)

    annotation, height = cut_image(gen_image, top_segment, gen_id, ann_id, current_height)
    annotations.extend(annotation)
    current_height += math.ceil(height)
    # noceil+=height
    ann_id += len(annotation)
Example #14
0
class ListWithAdjustments(object):
    """
  To prepare inserts, we adjust elements to be inserted and elements in the underlying list. We
  don't want to actually touch the underlying list, but we need to remember the adjustments,
  because later adjustments may depend on and readjust earlier ones.
  """
    def __init__(self, orig_list):
        """
    Orig_list must be a a SortedListWithKey.
    """
        self._orig_list = orig_list
        self._key = orig_list._key

        # Stores pairs (i, new_key) where i is an index into orig_list.
        #   Note that adjustments don't affect the order in the original list, so the list is sorted
        #   both on keys an on indices; and a missing index i means that (i, orig_key) fits into the
        #   adjustments list both by key and by index.
        self._adjustments = SortedListWithKey(key=lambda pair: pair[1])

        # Stores keys for new insertions.
        self._insertions = SortedList()

    def get_insertions(self):
        return self._insertions

    def get_adjustments(self):
        return self._adjustments

    def _adj_bisect_key_left(self, key):
        """
    Works as bisect_key_left(key) on the orig_list as if all adjustments have been applied.
    """
        adj_index = self._adjustments.bisect_key_left(key)
        adj_next = (self._adjustments[adj_index][0]
                    if adj_index < len(self._adjustments) else len(
                        self._orig_list))
        adj_prev = self._adjustments[adj_index - 1][0] if adj_index > 0 else -1
        orig_index = self._orig_list.bisect_key_left(key)
        if adj_prev < orig_index and orig_index < adj_next:
            return orig_index
        return adj_next

    def _adj_get_key(self, index):
        """
    Returns the key corresponding to the given index into orig_list as if all adjustments have
    been applied.
    """
        i = bisect.bisect_left(self._adjustments, (index, float('-inf')))
        if i < len(self._adjustments) and self._adjustments[i][0] == index:
            return self._adjustments[i][1]
        return self._key(self._orig_list[index])

    def count_range(self, begin, end):
        """
    Returns the number of elements with keys in the half-open interval [begin, end).
    """
        adj_begin = self._adj_bisect_key_left(begin)
        adj_end = self._adj_bisect_key_left(end)
        ins_begin = self._insertions.bisect_left(begin)
        ins_end = self._insertions.bisect_left(end)
        return (adj_end - adj_begin) + (ins_end - ins_begin)

    def _adjust_range(self, begin, end):
        """
    Make changes to stored adjustments and insertions to distribute them equally in the half-open
    interval of keys [begin, end).
    """
        adj_begin = self._adj_bisect_key_left(begin)
        adj_end = self._adj_bisect_key_left(end)
        ins_begin = self._insertions.bisect_left(begin)
        ins_end = self._insertions.bisect_left(end)
        self._do_adjust_range(adj_begin, adj_end, ins_begin, ins_end, begin,
                              end)

    def _adjust_all(self):
        """
    Renumber everything to be equally distributed in the open interval (new_begin, new_end).
    """
        orig_len = len(self._orig_list)
        ins_len = len(self._insertions)
        self._do_adjust_range(0, orig_len, 0, ins_len, 0.0,
                              orig_len + ins_len + 1.0)

    def _do_adjust_range(self, adj_begin, adj_end, ins_begin, ins_end,
                         new_begin_key, new_end_key):
        """
    Implements renumbering as used by _adjust_range() and _adjust_all().
    """
        count = (adj_end - adj_begin) + (ins_end - ins_begin)

        prev_keys = ([(self._adj_get_key(i), False, i)
                      for i in xrange(adj_begin, adj_end)] +
                     [(self._insertions[i], True, i)
                      for i in xrange(ins_begin, ins_end)])
        prev_keys.sort()
        new_keys = get_range(new_begin_key, new_end_key, count)

        for (old_key, is_insert, i), new_key in zip(prev_keys, new_keys):
            if is_insert:
                self._insertions.remove(old_key)
                self._insertions.add(new_key)
            else:
                # (i, old_key) pair may not be among _adjustments, so we discard() rather than remove().
                self._adjustments.discard((i, old_key))
                self._adjustments.add((i, new_key))

    def prep_inserts_at_index(self, index, count):
        # This is the crux of the algorithm, inspired by the [Bender] paper (cited above).
        # Here's a brief summary of the algorithm, and of our departures from it.
        # - The algorithm inserts keys while it is able. When there isn't enough space, it walks
        #   enclosing intervals around the key it wants to insert, doubling the interval each time,
        #   until it finds an interval that doesn't overflow. The overflow threshold is calculated in
        #   such a way that the bigger the interval, the smaller the density it seeks.
        # - The algorithm uses integers, picking the number of bits to work for list length between
        #   n/2 and 2n, and rebuilding from scratch any time length moves out of this range. We don't
        #   rebuild anything, don't change number of bits, and use floats. This breaks some of the
        #   theoretical results, and thinking about floats is much harder than about integers. So we
        #   are not on particularly solid ground with these changes (but it seems to work).
        # - We try different thresholds, which seems to perform better. This is mentioned in "Variable
        #   T" section of [Bender] paper, but our approach isn't quite the same. So it's also on shaky
        #   theoretical ground.
        assert count > 0
        begin = self._adj_get_key(index - 1) if index > 0 else 0.0
        end = self._adj_get_key(index) if index < len(
            self._orig_list) else begin + count + 1
        if begin < 0 or end <= 0 or math.isinf(max(begin, end)):
            # This should only happen if we have some invalid positions (e.g. from before we started
            # using this logic). In this case, just renumber everything 1 through n (leaving space so
            # that the count insertions take the first count integers).
            self._insertions.update([begin if index > 0 else float('-inf')] *
                                    count)
            self._adjust_all()
            return

        self._insertions.update(get_range(begin, end, count))
        if not is_valid_range(begin, self._insertions.irange(begin, end), end):
            assert self.count_range(begin, end) > 0
            min_key, max_key = self._find_sparse_enough_range(begin, end)
            self._adjust_range(min_key, max_key)
            assert is_valid_range(begin, self._insertions.irange(begin, end),
                                  end)

    def _find_sparse_enough_range(self, begin, end):
        # frac is a parameter used for relabeling, corresponding to 2/T in [Bender]. Its
        # interpretation is that frac^i is the overflow limit for intervals of size 2^i.
        for frac in (1.14, 1.3):
            thresh = 1
            for i in xrange(64):
                rbegin, rend = range_around_float(begin, i)
                assert self.count_range(rbegin, rend) > 0
                if end <= rend and self.count_range(rbegin, rend) < thresh:
                    return (rbegin, rend)
                thresh *= frac
        raise ValueError("This isn't expected")
Example #15
0
    class ManagerNode(object):
        def __init__(self, node, parent, position):
            """
            :param node: the behavior that is represented by this ManagerNode
            :type node: py_trees.behaviour.Behaviour
            :param parent: the parent of the behavior that is represented by this ManagerNode
            :type parent: py_trees.behaviour.Behaviour
            :param position: the position of the node in the list of children of the parent
            :type position: int
            """
            self.node = node
            self.parent = parent
            self.position = position
            self.disabled_children = SortedList()
            self.enabled_children = SortedList()

        def __lt__(self, other):
            return self.position < other.position

        def __gt__(self, other):
            return self.position > other.position

        def __eq__(self, other):
            return self.node == other.node and self.parent == other.parent

        def disable_child(self, manager_node):
            """
            marks the given manager node as disabled in the internal tree representation and removes it to the behavior tree
            :param manager_node:
            :type manager_node: TreeManager.ManagerNode
            :return:
            """
            self.enabled_children.remove(manager_node)
            self.disabled_children.add(manager_node)
            if isinstance(self.node, PluginBehavior):
                self.node.remove_plugin(manager_node.node.name)
            else:
                self.node.remove_child(manager_node.node)

        def enable_child(self, manager_node):
            """
            marks the given manager node as enabled in the internal tree representation and adds it to the behavior tree
            :param manager_node:
            :type manager_node: TreeManager.ManagerNode
            :return:
            """
            self.disabled_children.remove(manager_node)
            self.enabled_children.add(manager_node)
            if isinstance(self.node, PluginBehavior):
                self.node.add_plugin(manager_node.node)
            else:
                idx = self.enabled_children.index(manager_node)
                self.node.insert_child(manager_node.node, idx)

        def add_child(self, manager_node):
            """
            adds the given manager node to the internal tree map and the corresponding behavior to the behavior tree
            :param manager_node:
            :type manager_node: TreeManager.ManagerNode
            :return:
            """
            if isinstance(self.node, PluginBehavior):
                self.enabled_children.add(manager_node)
                self.node.add_plugin(manager_node.node)
            else:
                if manager_node.position < 0:
                    manager_node.position = 0
                    if self.enabled_children:
                        manager_node.position = max(
                            manager_node.position,
                            self.enabled_children[-1].position + 1)
                    if self.disabled_children:
                        manager_node.position = max(
                            manager_node.position,
                            self.disabled_children[-1].position + 1)
                    idx = manager_node.position
                else:
                    idx = self.disabled_children.bisect_left(manager_node)
                    for c in self.disabled_children.islice(start=idx):
                        c.position += 1
                    idx = self.enabled_children.bisect_left(manager_node)
                    for c in self.enabled_children.islice(start=idx):
                        c.position += 1
                self.node.insert_child(manager_node.node, idx)
                self.enabled_children.add(manager_node)

        def remove_child(self, manager_node):
            """
            removes the given manager_node from the internal tree map and the corresponding behavior from the behavior tree
            :param manager_node:
            :type manager_node: TreeManager.ManagerNode
            :return:
            """
            if isinstance(self.node, PluginBehavior):
                if manager_node in self.enabled_children:
                    self.enabled_children.remove(manager_node)
                    self.node.remove_plugin(manager_node.node.name)
                elif manager_node in self.disabled_children:
                    self.disabled_children.remove(manager_node)
                else:
                    raise RuntimeError(
                        'could not remove node from parent. this probably means that the tree is inconsistent'
                    )
            else:
                if manager_node in self.enabled_children:
                    self.enabled_children.remove(manager_node)
                    self.node.remove_child(manager_node.node)
                elif manager_node in self.disabled_children:
                    self.disabled_children.remove(manager_node)
                else:
                    raise RuntimeError(
                        'could not remove node. this probably means that the tree is inconsistent'
                    )
                idx = self.disabled_children.bisect_right(manager_node)
                for c in self.disabled_children.islice(start=idx):
                    c.position -= 1
                idx = self.enabled_children.bisect_right(manager_node)
                for c in self.enabled_children.islice(start=idx):
                    c.position -= 1
Example #16
0
# 1944. Number of Visible People in a Queue
# https://leetcode.com/problems/number-of-visible-people-in-a-queue/

from sortedcontainers import SortedList
​
class Solution:
    def canSeePersonsCount(self, heights: List[int]) -> List[int]:
        n = len(heights)
        res = [1] * (n - 1) + [0]
        sl = SortedList()
        mp = collections.defaultdict(int)
        
        for i, h in list(enumerate(heights))[::-1]:
            index = sl.bisect_left(h)
​
            if sl:
                res[i] = max(1, min(len(sl), index + 1))
            
            if i + 1 < n and h > heights[i + 1]:
                for _ in range(index):
                    sl.pop(0)
                    
            sl.add(h)  
            mp[h] = i
            
        return res
Example #17
0
class MKAverage(object):
    def __init__(self, m, k):
        """
        :type m: int
        :type k: int
        """
        self.m, self.k, self.sum = m, k, 0
        self.lower, self.middle, self.upper = SortedList(), SortedList(
        ), SortedList()
        self.nums = collections.deque()

    def shiftLeft(self, l_list, r_list):
        l_list.add(r_list.pop(0))

    def shiftRight(self, l_list, r_list):
        r_list.add(l_list.pop())

    def addElement(self, num):
        """
        :type num: int
        :rtype: None
        """
        self.nums.append(num)
        if self.lower and self.lower[-1] >= num:
            self.lower.add(num)
        elif self.upper and self.upper[0] <= num:
            self.upper.add(num)
        else:
            self.middle.add(num)
            self.sum += num

        while len(self.lower) > self.k:
            self.sum += self.lower[-1]
            self.shiftRight(self.lower, self.middle)
        while len(self.upper) > self.k:
            self.sum += self.upper[0]
            self.shiftLeft(self.middle, self.upper)

        if len(self.nums) > self.m:
            d = self.nums.popleft()
            if self.lower.bisect_left(d) < self.k:
                self.lower.remove(d)
            elif self.middle.bisect_left(d) < len(self.middle):
                self.middle.remove(d)
                self.sum -= d
            else:
                self.upper.remove(d)

        if len(self.nums) >= self.m:
            while len(self.lower) < self.k:
                self.sum -= self.middle[0]
                self.shiftLeft(self.lower, self.middle)
            while len(self.upper) < self.k:
                self.sum -= self.middle[-1]
                self.shiftRight(self.middle, self.upper)

    def calculateMKAverage(self):
        """
        :rtype: int
        """
        if len(self.nums) < self.m:
            return -1
        return self.sum // (self.m - self.k * 2)
Example #18
0
        maxSize = min(rows, cols)
        
        prefix = [[0] * (cols + 1) for _ in range(rows + 1)]
        
        for i in range(rows):
            for j in range(cols):
                prefix[i + 1][j + 1] += prefix[i + 1][j] + matrix[i][j]
        
        for i in range(rows):
            for j in range(cols):
                prefix[i + 1][j + 1] += prefix[i][j + 1]
​
        res = float('-inf')
        for row1 in range(rows):
            for row2 in range(row1, rows):
                sl = SortedList()
                sl.add(0)
                
                for j in range(cols):
                    curr = prefix[row2 + 1][j + 1] - prefix[row1][j + 1]
                    target = curr - k
                    
                    index = sl.bisect_left(target)
                    
                    if 0 <= index < len(sl):
                        res = max(res, curr - sl[index])
                    
                    sl.add(curr)
​
        return res
Example #19
0
class MKAverage:
    '''
    use sorted list 
    it is a data structure to add and remove at O(logN) time 
    and can get min and max in an efficient way
    
    '''
    def __init__(self, m: int, k: int):
        self.m = m
        self.k = k
        self.nums = deque()
        self.sorted_nums = SortedList()
        self.total = self.topk_big = self.topk_small = 0

    def addElement(self, num: int) -> None:
        # handle new number num
        self.total += num
        self.nums.append(num)
        idx = self.sorted_nums.bisect_left(num)

        # handle topksmall
        if idx < self.k:
            self.topk_small += num
            if len(self.sorted_nums) >= self.k:
                self.topk_small -= self.sorted_nums[self.k - 1]

        # handle topkbig
        if idx >= len(self.sorted_nums) + 1 - self.k:
            self.topk_big += num
            if len(self.sorted_nums) >= self.k:
                self.topk_big -= self.sorted_nums[-self.k]

        self.sorted_nums.add(num)

        # handle the num that needs to be removed from size m sliding window
        if len(self.nums) > self.m:
            num_to_remove = self.nums.popleft()
            self.total -= num_to_remove
            idx = self.sorted_nums.index(num_to_remove)

            if idx < self.k:
                self.topk_small -= num_to_remove
                self.topk_small += self.sorted_nums[self.k]

            if idx >= len(self.sorted_nums) - self.k:
                self.topk_big -= num_to_remove
                self.topk_big += self.sorted_nums[-self.k - 1]

            self.sorted_nums.remove(num_to_remove)

    def calculateMKAverage(self) -> int:
        if len(self.nums) < self.m:
            return -1
        return (self.total - self.topk_small - self.topk_big) // (self.m -
                                                                  (2 * self.k))


# Your MKAverage object will be instantiated and called as such:
# obj = MKAverage(m, k)
# obj.addElement(num)
# param_2 = obj.calculateMKAverage()
Example #20
0
# 2179. Count Good Triplets in an Array
# https://leetcode.com/problems/count-good-triplets-in-an-array

from sortedcontainers import SortedList
​
class Solution:
    def goodTriplets(self, nums1: List[int], nums2: List[int]) -> int:
        n = len(nums1)
        pos = {x:i for i,x in enumerate(nums2)}
        
        sl = SortedList([pos[nums1[0]]])
        prefix = [0]
        
        for i in range(1, n):
            sl.add(pos[nums1[i]])
            prefix.append(sl.bisect_left(pos[nums1[i]]))
        
        sl = SortedList([pos[nums1[-1]]])
        suffix = [0]
        
        for i in range(n - 2, -1, -1):
            index = sl.bisect_left(pos[nums1[i]])
            suffix.append(len(sl) - index)
            sl.add(pos[nums1[i]])
        suffix.reverse()
        
        res = 0
        for a, b in zip(prefix, suffix):
            res += a * b
        
        return res
Example #21
0
# 1944. Number of Visible People in a Queue
# https://leetcode.com/problems/number-of-visible-people-in-a-queue

from sortedcontainers import SortedList
​
class Solution:
    def canSeePersonsCount(self, heights: List[int]) -> List[int]:
        heights.reverse()
        res = []
        sl = SortedList()
        
        for h in heights:
            res.append(min(len(sl), sl.bisect_left(h) + 1))
            
            while sl and h > sl[0]:
                sl.pop(0)
            
            sl.add(h)
            
        return res[::-1]
Example #22
0
class AutoComplete(wx.Frame):
    def __init__(self, parent, mainFrame, dictfile=None):
        self.geekey = mainFrame
        wx.Frame.__init__(self,
                          parent,
                          wx.NewId(),
                          "Popup",
                          style=wx.STAY_ON_TOP | wx.FRAME_NO_TASKBAR
                          | wx.NO_BORDER | wx.FRAME_TOOL_WINDOW)
        self.MaxNum = 9
        self.ShowBitNum = 3
        self.fontsize = 12
        ###################
        font = wx.Font(self.fontsize, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
        self.lists = wx.ListBox(self, )
        self.lists.SetFont(font)
        self.lists.Bind(wx.EVT_LISTBOX, self.OnLists)
        ###################
        self.min_width = 100
        self.max_width = 1666  # change to the longest candidates length
        self.item_num = self.MaxNum
        #self.char_width,self.char_height = (8,16)
        self.char_width, self.char_height = self.lists.GetTextExtent('O')
        self.switch_force_on = False
        self.auto_complete_on = False
        #log.log 'char width,height =',self.char_width,self.char_height
        ################### read word list in from file
        self.word_list = SortedList()

        self.StateReset()

        ###################
        #get self hw
        self.ui = GUITHREADINFO(cbSize=sizeof(GUITHREADINFO))
        self.hw = self.GetTopWindow()
        #log.log("current hw is %s"%self.hw)
        self.Show(False)
        self.Raise()
        self.Move((-200, -200))
        self.popup_hw = self.GetTopWindow()
        win32gui.SetForegroundWindow(self.hw)
        #self.ui = None

        ####################
        #load dict
        self.dictfile = dictfile
        if os.path.exists(dictfile):
            f = open(dictfile, 'r')
            cont = f.read()
            f.close()
            for row in cont.split('\n'):
                items = row.split()
                if len(items) == 0: continue
                word = items[0]
                freq = 1
                if len(items) > 1: freq = toFloat(items[1], freq)
                self.UpdateWord(word, freq * 0.9)
                pass
            pass
        ####
        self.height = 0
        self.width = 0
        self.start_pos = (0, 0)
        pass

    def FindWord(self, word):
        ind = self.word_list.bisect_left([word.lower(), word, -1])
        if ind >= len(self.word_list): return False, ind
        if self.word_list[ind][1] != word: return False, ind
        return True, ind

    def FindSection(self, prefix):
        left_ind = self.word_list.bisect_left(
            [prefix.lower(), prefix.upper(), -1])
        right_ind = self.word_list.bisect_right(
            [prefix.lower() + "~",
             prefix.lower() + "~", -1])
        return (left_ind, right_ind)

    def UpdateWord(self, word, freq=1):
        if freq <= 0: return
        res, ind = self.FindWord(word)
        #log.log("update list of len %s with word %s"%(len(self.word_list),self.word) )
        if res:
            self.word_list[ind][2] += freq
        else:
            self.word_list.add([word.lower(), word, freq])
            pass
        pass

    def DeleteWord(self, word):
        res, ind = self.FindWord(word)
        if res:
            del self.word_list[ind]
            pass
        pass

    def PopupActive(self):
        return (self.GetTopWindow() == self.popup_hw)

    def GetInput(self, evtType, key, geekey, shift, ctrl, alt):
        ###
        if self.geekey.getConfig('geekeyenabled') == 'False': return True
        if not self.auto_complete_on: return True

        #log.log("get into get input")
        self.state_on_geekey = geekey
        self.state_on_shift = shift
        self.state_on_ctrl = ctrl
        self.state_on_alt = alt
        is_active = (self.GetTopWindow() == self.popup_hw)

        # # esc to cancel candinate selection
        # if is_active and key == 'esc':
        #     if is_active:
        #         log.log('cancel auto complete by esc')
        #         self.StateReset()
        #     return False

        if key == 'tab':
            #log.log("tab here in tab dealing")
            #log.log("prepare to deal with tab without geekey state_is_on = %s"%self.state_is_on)
            if self.state_is_on:  #candidate is shown on the screen
                if evtType == 'key up': return False
                #log.log("is active = %s  wtop = %s wpop = %s"%(is_active,self.GetTopWindow(),self.popup_hw ) )
                if is_active:  #the same as self.state_is_selection
                    #pop up current selection
                    #log.log("the popup window is focused.")
                    select = self.lists.GetSelection()
                    select_word = self.lists.GetString(select)
                    win32gui.SetForegroundWindow(self.hw)
                    log.log("update candidate %s with %s" %
                            (select_word, self.word))
                    self.UpdateTabCandidate(select_word, self.word)
                    self.StateReset()
                elif self.state_is_selection:  #on screen current selection
                    pass
                elif self.state_is_tab_selection:  #move to next candidate in tab selection mode
                    #log.log("already in tab selection mode, move to the the next one candidate")
                    direction = 'down' if not shift else 'up'
                    word = self.MoveCandidateSelection(direction)
                    #log.log("update candidate %s with %s"%(self.word,word ) )
                    self.UpdateTabCandidate(word, self.word)
                    self.word = word
                    pass
                else:  #still in input mode, enter the tab selection mode
                    #log.log("start the tab selection mode")
                    #choose the first one to be on screen
                    self.state_is_tab_selection = True
                    select = self.lists.GetSelection()
                    reset = True
                    if select == wx.NOT_FOUND:
                        self.lists.SetSelection(0)
                        select = 0
                        reset = False
                        pass
                    new_word = self.lists.GetString(select)
                    #log.log("update candidate %s on %s"%(new_word,self.word) )
                    self.UpdateTabCandidate(new_word, self.word)
                    self.word = new_word
                    if reset or self.item_num == 1: self.StateReset()
                    pass
                #log.log("get out tab when is selection")
                return False
            else:
                pass  # normal tab, do nothing, and return True to pass the tab by
            #log.log("get out normal tab")
            return True

        if key == 'Packet': return True
        if key == '': return True

        if self.state_is_on and key == 'return':
            if evtType == 'key up': return False
            select = self.lists.GetSelection()
            if select < 0:
                self.StateReset()
                return True
            select_word = self.lists.GetString(select)
            self.UpdateTabCandidate(select_word, self.word)
            self.StateReset()
            return False

        if self.state_is_on and key == 'delete':
            if evtType == 'key up': return False
            select = self.lists.GetSelection()
            select_word = self.lists.GetString(select)
            if select_word != '': self.DeleteWord(select_word)
            self.StateReset()
            return False

        if key in ('up', 'down'):
            if self.state_is_on:  ##get into selection mode
                if evtType == 'key up': return False
                #log.log("get in up down")
                self.MoveCandidateSelection(key)
                self.state_is_tab_selection = False
                #self.StateReset()
                #log.log("get out up down")
                return False
            #else
            return True

        if key in ('page up', 'page down'):
            if self.state_is_on:  ##get into selection mode
                if evtType == 'key up': return False
                #log.log("get in page up down")
                incr = self.item_num if key == 'page down' else -self.item_num
                self.MoveCandidateSelection(incr)
                #self.StateReset()
                self.state_is_tab_selection = False
                #log.log("get out page up down")
                return False
            #else
            return True

        if is_active: return False

        if evtType == 'key up':
            #log.log("get out key up")
            return True

        pos = self.GetCaretPosition()
        #log.log(pos)
        old_pos = self.last_pos
        self.last_pos = pos
        #log.log('pos = %s old_pos = %s'%(pos,old_pos) )
        if (old_pos and (pos[0] - old_pos[0] < -8 or pos[1] - old_pos[1] > 16
                         or pos[1] - old_pos[1] < -16)):
            #log.log("position condition not meet. StateReset")
            self.StateReset()
            return True

        #log.log 'with key = %s'%(key)

        ### combo key with ctrl or alt encoutered, rest
        if ctrl or alt or not key in StringKeys:
            #log.log("non-string key encountered")
            if key == 'backspace':
                if len(self.section_list) >= 1: del self.section_list[-1]
                if self.word: self.word = self.word[:-1]
                self.ShowSelection()
                #log.log("get out up backspace")
                return True
            else:  #record the word to word_list
                #log.log("try update %s to word_list %s"%(self.word,self.word_list) )
                #log.log len(self.word), self.ShowBitNum
                #remove  last SpecKeys
                while len(self.word) > 1 and self.word[-1] in SpecKeys:
                    self.word = self.word[:-1]
                if len(self.word) >= self.ShowBitNum + 2 and min(
                        self.word) != max(self.word):
                    #log.log("update word %s"%self.word)
                    #check if word is consist of a single char ignore
                    self.UpdateWord(self.word)
                    #log.log("after word_list = %s"%(self.word_list) )
                    pass
                else:
                    #log.log("word content condition not meet to updateword")
                    #log.log("word = %s min %s max %s"%(self.word,min(self.word),max(self.word)))
                    pass
                self.StateReset()
                #log.log("get out up backspace")
                return True
            pass

        ### normal char dealing
        #log.log("normal char dealing key = %s shift = %s"%(key,shift) )
        ### add key to word
        if shift:
            self.word += (UpperKeys[key] if key in UpperKeys else key.upper())
        else:
            self.word += key

        #log.log("word expanded to %s"%self.word)
        ###
        if len(self.word) > 2 and self.word[0] == self.word[1]:
            self.StateReset()
            return True
        if not self.auto_complete_on:
            return True
        #log.log self.word
        wordlen = len(self.word)

        if wordlen == 1:
            self.start_pos = pos
            return True

        if wordlen < self.ShowBitNum: return True

        if wordlen == self.ShowBitNum:  ### show selection window at starting pos
            #candidate will be at least MaxNum+2 bits
            lind, rind = self.FindSection(self.word)
            self.section_list.append(
                (lind, rind))  ## may append (0,0) if not find
            self.ShowSelection(True)
            return True

        if wordlen > self.ShowBitNum:  ### update selection window
            lind, rind = self.FindSection(self.word)
            self.section_list.append((lind, rind))
            self.ShowSelection()
            return True

        ### won't come here
        return True

    def ShowSelection(self, initshow=False):
        lind, rind = (0, 0)
        if len(self.section_list) > 0: lind, rind = self.section_list[-1]
        #log.log lind,rind
        if lind == rind:
            self.state_is_on = False
            self.state_no_candidate = True
            self.Show(False)
            return False
        #log.log "section = %s lind = %s rind = %s"%(self.section_list,lind,rind)
        #log.log "section list now ",self.word_list[ lind:rind ]
        #find out the max width
        self.state_is_on = True
        self.width = self.min_width
        self.width = min(
            self.max_width,
            max(
                list(
                    map(lambda x: len(x[1]) * self.char_width,
                        self.word_list[lind:rind]))) + 40)
        self.item_num = min(rind - lind, self.MaxNum)
        #log.log 'show list with item_num = ',self.item_num
        self.old_height = self.height
        self.height = (self.char_height) * self.item_num + 6
        self.SetSize((self.width, self.height))
        self.Move((self.start_pos[0], self.start_pos[1] - self.height))

        word_list = list(map(lambda x: x[1], self.word_list[lind:rind]))
        self.lists.Set(word_list)
        self.Show(True)
        win32gui.SetForegroundWindow(self.hw)
        pass

    def HideSelection(self):
        self.Show(False)

    def StateReset(self):
        self.Show(False)
        self.section_list = []
        self.word = ''
        self.start_pos = None
        self.state_is_on = False
        self.state_is_selection = False
        self.state_is_tab_selection = False
        self.state_no_candidate = False
        self.tab_selection_word = ''
        self.last_pos = None
        pass

    def UpdateTabCandidate(self, new_word, old_word):
        #log.log("get into update cadidate")
        for i in range(len(old_word)):
            geeKeyboard.keyPress('backspace')
            geeKeyboard.keyRelease('backspace')
            pass
        for ch in new_word:
            upper = False
            if ch in LowerKeys:
                upper = True
                ch = LowerKeys[ch]
            elif ch.isupper():
                upper = True
                ch = ch.lower()
                pass
            #log.log("deal char %s with upper = %s and shift = %s"%(ch,upper,self.state_on_shift) )
            if self.state_on_shift:
                shift_key = 'left shift' if keyboard.is_pressed(
                    'left shift') else 'right shift'
                if upper:
                    #log.log"case 1" #shift is on and a upper char
                    geeKeyboard.keyPress(ch)
                    geeKeyboard.keyRelease(ch)
                else:
                    #log.log"case 2" #not upper char, but the shift key is on
                    geeKeyboard.keyRelease(shift_key)
                    geeKeyboard.keyPress(ch)
                    geeKeyboard.keyRelease(ch)
                    geeKeyboard.keyPress(shift_key)
                    pass
                pass
            else:  # not on shift
                if upper:
                    #log.log"case 3" #not on shift and A upper char
                    geeKeyboard.keyPress('left shift')
                    geeKeyboard.keyPress(ch)
                    geeKeyboard.keyRelease(ch)
                    geeKeyboard.keyRelease('left shift')
                else:
                    #log.log"case 4"  #not shift and not upper
                    geeKeyboard.keyPress(ch)
                    geeKeyboard.keyRelease(ch)
                    pass
                pass
            pass
        #log.log("get out of update cadidate")
        pass

    def MoveCandidateSelection(self, direction='down'):
        if direction == 'down': incr = 1
        elif direction == 'up': incr = -1
        else: incr = direction
        new_item = self.lists.GetSelection() + incr
        if new_item < 0: new_item = 0
        elif new_item >= self.lists.GetCount():
            new_item = self.lists.GetCount() - 1
        else:
            pass
        word = self.lists.GetString(new_item)
        self.lists.SetSelection(new_item)
        self.lists.EnsureVisible(new_item)
        return word

    def GetTopWindow(self):
        fhwnd = win32gui.GetFocus()
        user32.GetGUIThreadInfo(0, byref(self.ui))
        return self.ui.hwndFocus

    def GetCaretPosition(self):
        fhwnd = win32gui.GetFocus()
        user32.GetGUIThreadInfo(None,
                                byref(self.ui))  # None/0 for foreground thread
        if self.ui.hwndFocus != self.popup_hw: self.hw = self.ui.hwndFocus
        return win32gui.ClientToScreen(
            self.ui.hwndFocus, (self.ui.rcCaret.left, self.ui.rcCaret.top))

    def OnLists(self, evt):
        #log.log("selection = %s "%(self.lists.GetSelection() ) )
        pass

    def Destroy(self):
        ### save lists to dictfile
        cont = ''
        for item in self.word_list:
            if item[2] <= 0: continue
            cont += "%s %s\n" % (item[1], item[2])
            pass
        f = open(self.dictfile, 'w')
        f.write(cont)
        f.close()

        super(AutoComplete, self).Destroy()
        pass

    def SwitchState(self, state=None):
        #log.log("geekey+tab to switch the auto complete state")
        if not state: self.auto_complete_on = not self.auto_complete_on
        else:
            self.auto_complete_on = True if state in ('True', 'on') else False

        if not self.auto_complete_on:  #turn off
            self.StateReset()
            self.geekey.vim.indicator.StateReset(lt('_autocomplete'),
                                                 lt('_autocomplete_off'))
            pass
        else:  #turn on
            self.geekey.vim.indicator.StateReset(lt('_autocomplete'),
                                                 lt('_autocomplete_on'))
            pass

        pass

    pass
Example #23
0
from sortedcontainers import SortedList
​
class Solution:
    def minimumDifference(self, nums: List[int]) -> int:
        m = len(nums)
        n = m // 3
        
        left = SortedList(nums[:n])
        right = SortedList(nums[n:])
        sumLeft = sum(left)
        sumRight = sum(right[-n:])
        res = sumLeft - sumRight
        
        for i in range(n, 2 * n):
            index = right.bisect_left(nums[i])
            
            if index >= len(right) - n:
                sumRight -= nums[i]
                sumRight += right[-n-1]
            
            index = left.bisect_left(nums[i])
            
            if index < n:
                sumLeft += nums[i]
                sumLeft -= left[n - 1]
            
            right.discard(nums[i])
            left.add(nums[i])
            
            res = min(res, sumLeft - sumRight)
Example #24
0
class ExamRoom:
    def __init__(self, n: int):
        self.treemap1 = SortedList(
        )  # 每个元素为(区间大小,start,end),因为区间大小为偶数时与减少一时相同,所以要减少1
        self.treemap2 = SortedList()  # 每个元素为(start,end),主要目的是在leave时,定位p所在的区间
        self.n = n
        self.treemap1.add((self.distance(0, n - 1), 0, n - 1))
        self.treemap2.add((0, n - 1))

    def distance(self, start, end):
        d = end - start + 1
        if d % 2 == 0:
            d -= 1
        return d

    def seat(self) -> int:
        r = self.treemap1.pop()  # 弹出最大的区间
        size, start, end = r
        start = -start
        self.treemap2.remove((start, end))
        if start == 0:  # 左边界
            p = 0
            if end >= 1:
                rr = (self.distance(1, end), -1, end)
                self.treemap1.add(rr)
                self.treemap2.add((1, end))
        elif end == self.n - 1:  # 右边界
            p = self.n - 1
            if p - 1 >= start:
                lr = (self.distance(start, p - 1), -start, p - 1)
                self.treemap1.add(lr)
                self.treemap2.add((start, p - 1))
        else:  # 选中了中间的区间,区间可以拆分成2个
            p = (start + end) // 2
            if p > start:
                lr = (self.distance(start, p - 1), -start, p - 1)
                self.treemap1.add(lr)
                self.treemap2.add((start, p - 1))
            if p < end:
                rr = (self.distance(p + 1, end), -(p + 1), end)
                self.treemap1.add(rr)
                self.treemap2.add((p + 1, end))
        return p

    def leave(self, p: int) -> None:
        midRange = (p, p)
        idx = self.treemap2.bisect_left(midRange)
        if idx > 0:
            leftRange = self.treemap2[idx - 1]
            if leftRange[1] == p - 1:  # 左侧区间与p相邻,进行合并
                self.treemap2.remove(leftRange)
                self.treemap1.remove(
                    (self.distance(leftRange[0],
                                   leftRange[1]), -leftRange[0], leftRange[1]))
                midRange = (leftRange[0], p)
                idx -= 1
        if idx < len(self.treemap2):
            rightRange = self.treemap2[idx]
            if rightRange[0] == p + 1:  # 右侧区间与p相邻,进行合并
                self.treemap2.remove(rightRange)
                self.treemap1.remove((self.distance(rightRange[0],
                                                    rightRange[1]),
                                      -rightRange[0], rightRange[1]))
                midRange = (midRange[0], rightRange[1])
        self.treemap2.add(midRange)
        self.treemap1.add(
            (self.distance(midRange[0],
                           midRange[1]), -midRange[0], midRange[1]))
Example #25
0
from sortedcontainers import SortedList
​
class Solution:
    def totalSteps(self, nums: List[int]) -> int:
        nums = [float('-inf')] + nums + [float('inf')]
        res = 0
        
        sl = SortedList([(i, x) for i, x in enumerate(nums)])
        p = set()
        
        for i, (a, b) in enumerate(zip(nums, nums[1:])):
            if a > b:
                p.add((i + 1, b))
                
        while p:
            newP = set()
            res += 1
            
            for j, b in p:
                index = sl.bisect_left((j, b))
                i, a = sl[index - 1]
                k, c = sl[index + 1]
                del sl[index]
                
                if a > c and (k, c) not in p:
                    newP.add((k, c))
            
            p = newP
        
        return res
Example #27
0
# 2012. Sum of Beauty in the Array
# https://leetcode.com/problems/sum-of-beauty-in-the-array/

from sortedcontainers import SortedList
​
class Solution:
    def sumOfBeauties(self, nums: List[int]) -> int:
        n = len(nums)
        left = SortedList()
        left.add(nums[0])
        
        right = SortedList()
        for i in range(2, n):
            right.add(nums[i])
        res = 0
        
        for i in range(1, n - 1):
            left_index = left.bisect(nums[i])
            right_index = right.bisect_left(nums[i])
​
            if left_index == len(left) and left[left_index - 1] < nums[i] and right_index == 0 and right[0] > nums[i]:
                res += 2
            elif nums[i - 1] < nums[i] < nums[i + 1]:
                res += 1
                
            left.add(nums[i])
            right.remove(nums[i + 1])
​
        return res