# Path: raccoon/sort_utils.py
# def sorted_exists(values, x):
#     """
#     For list, values, returns the insert position for item x and whether the item already exists in the list. This
#     allows one function call to return either the index to overwrite an existing value in the list, or the index to
#     insert a new item in the list and keep the list in sorted order.
# 
#     :param values: list
#     :param x: item
#     :return: (exists, index) tuple
#     """
#     i = bisect_left(values, x)
#     j = bisect_right(values, x)
#     exists = x in values[i:j]
#     return exists, i
# 
# def sorted_index(values, x):
#     """
#     For list, values, returns the index location of element x. If x does not exist will raise an error.
# 
#     :param values: list
#     :param x: item
#     :return: integer index
#     """
#     i = bisect_left(values, x)
#     j = bisect_right(values, x)
#     return values[i:j].index(x) + i
# 
# def sorted_list_indexes(list_to_sort, key=None, reverse=False):
#     """
#     Sorts a list but returns the order of the index values of the list for the sort and not the values themselves.
#     For example is the list provided is ['b', 'a', 'c'] then the result will be [2, 1, 3]
# 
#     :param list_to_sort: list to sort
#     :param key: if not None then a function of one argument that is used to extract a comparison key from each
#                 list element
#     :param reverse: if True then the list elements are sorted as if each comparison were reversed.
#     :return: list of sorted index values
#     """
#     if key is not None:
#         def key_func(i):
#             return key(list_to_sort.__getitem__(i))
#     else:
#         key_func = list_to_sort.__getitem__
#     return sorted(range(len(list_to_sort)), key=key_func, reverse=reverse)


from abc import ABC, abstractmethod
from bisect import bisect_left, bisect_right
from collections import OrderedDict
from itertools import compress
from tabulate import tabulate
from raccoon.sort_utils import sorted_exists, sorted_index, sorted_list_indexes

    def get_slice(self, start_index=None, stop_index=None, as_list=False):
        """
        For sorted Series will return either a Series or list of all of the rows where the index is greater than
        or equal to the start_index if provided and less than or equal to the stop_index if provided. If either the
        start or stop index is None then will include from the first or last element, similar to standard python
        slide of [:5] or [:5]. Both end points are considered inclusive.

        :param start_index: lowest index value to include, or None to start from the first row
        :param stop_index: highest index value to include, or None to end at the last row
        :param as_list: if True then return a list of the indexes and values
        :return: Series or tuple of (index list, values list)
        """
        if not self._sort:
            raise RuntimeError('Can only use get_slice on sorted Series')

        start_location = bisect_left(self._index, start_index) if start_index is not None else None
        stop_location = bisect_right(self._index, stop_index) if stop_index is not None else None

        index = self._index[start_location:stop_location]
        data = self._data[start_location:stop_location]

        if as_list:
            return index, data
        else:
            return Series(data=data, index=index, data_name=self._data_name, index_name=self._index_name,
                          sort=self._sort, dropin=self._dropin)

    def _slice_index(self, slicer):
        try:

# Path: raccoon/sort_utils.py
# def sorted_exists(values, x):
#     """
#     For list, values, returns the insert position for item x and whether the item already exists in the list. This
#     allows one function call to return either the index to overwrite an existing value in the list, or the index to
#     insert a new item in the list and keep the list in sorted order.
# 
#     :param values: list
#     :param x: item
#     :return: (exists, index) tuple
#     """
#     i = bisect_left(values, x)
#     j = bisect_right(values, x)
#     exists = x in values[i:j]
#     return exists, i
# 
# def sorted_index(values, x):
#     """
#     For list, values, returns the index location of element x. If x does not exist will raise an error.
# 
#     :param values: list
#     :param x: item
#     :return: integer index
#     """
#     i = bisect_left(values, x)
#     j = bisect_right(values, x)
#     return values[i:j].index(x) + i
# 
# def sorted_list_indexes(list_to_sort, key=None, reverse=False):
#     """
#     Sorts a list but returns the order of the index values of the list for the sort and not the values themselves.
#     For example is the list provided is ['b', 'a', 'c'] then the result will be [2, 1, 3]
# 
#     :param list_to_sort: list to sort
#     :param key: if not None then a function of one argument that is used to extract a comparison key from each
#                 list element
#     :param reverse: if True then the list elements are sorted as if each comparison were reversed.
#     :return: list of sorted index values
#     """
#     if key is not None:
#         def key_func(i):
#             return key(list_to_sort.__getitem__(i))
#     else:
#         key_func = list_to_sort.__getitem__
#     return sorted(range(len(list_to_sort)), key=key_func, reverse=reverse)


# Path: raccoon/series.py
from abc import ABC, abstractmethod
from bisect import bisect_left, bisect_right
from collections import OrderedDict
from itertools import compress
from tabulate import tabulate
from raccoon.sort_utils import sorted_exists, sorted_index, sorted_list_indexes


    def get_slice(self, start_index=None, stop_index=None, as_list=False):
        """
        For sorted Series will return either a Series or list of all of the rows where the index is greater than
        or equal to the start_index if provided and less than or equal to the stop_index if provided. If either the
        start or stop index is None then will include from the first or last element, similar to standard python
        slide of [:5] or [:5]. Both end points are considered inclusive.

        :param start_index: lowest index value to include, or None to start from the first row
        :param stop_index: highest index value to include, or None to end at the last row
        :param as_list: if True then return a list of the indexes and values
        :return: Series or tuple of (index list, values list)
        """
        if not self._sort:
            raise RuntimeError('Can only use get_slice on sorted Series')

        start_location = bisect_left(self._index, start_index) if start_index is not None else None
        stop_location = bisect_right(self._index, stop_index) if stop_index is not None else None

        index = self._index[start_location:stop_location]
        data = self._data[start_location:stop_location]

        if as_list:
            return index, data
        else:
            return Series(data=data, index=index, data_name=self._data_name, index_name=self._index_name,
                          sort=self._sort, dropin=self._dropin)

    def _slice_index(self, slicer):
        try:

            start_index = sorted_index(self._index, slicer.start) if self._sort else self._index.index(slicer.start)