# 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)


import json
from bisect import bisect_left, bisect_right
from collections import OrderedDict, namedtuple
from itertools import compress
from tabulate import tabulate
from raccoon.sort_utils import sorted_exists, sorted_index, sorted_list_indexes

        # check that the values data is less than or equal to the length of the indexes
        for column in values:
            if len(values[column]) > len(indexes):
                raise ValueError('length of %s column in values is longer than indexes' % column)

        # check the indexes are not duplicates
        combined_index = self._index + indexes
        if len(set(combined_index)) != len(combined_index):
            raise IndexError('duplicate indexes in DataFrames')

        if new_cols:
            for col in values:
                if col not in self._columns:
                    self._add_column(col)

        # append index value
        self._index.extend(indexes)

        # add data values, if not in values then use None
        for c, col in enumerate(self._columns):
            self._data[c].extend(values.get(col, [None] * len(indexes)))
        self._pad_data()

    def _slice_index(self, slicer):
        try:
            start_index = sorted_index(self._index, slicer.start) if self._sort else self._index.index(slicer.start)
        except ValueError:
            raise IndexError('start of slice not in the index')
        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/dataframe.py
import json
from bisect import bisect_left, bisect_right
from collections import OrderedDict, namedtuple
from itertools import compress
from tabulate import tabulate
from raccoon.sort_utils import sorted_exists, sorted_index, sorted_list_indexes


        # check that the values data is less than or equal to the length of the indexes
        for column in values:
            if len(values[column]) > len(indexes):
                raise ValueError('length of %s column in values is longer than indexes' % column)

        # check the indexes are not duplicates
        combined_index = self._index + indexes
        if len(set(combined_index)) != len(combined_index):
            raise IndexError('duplicate indexes in DataFrames')

        if new_cols:
            for col in values:
                if col not in self._columns:
                    self._add_column(col)

        # append index value
        self._index.extend(indexes)

        # add data values, if not in values then use None
        for c, col in enumerate(self._columns):
            self._data[c].extend(values.get(col, [None] * len(indexes)))
        self._pad_data()

    def _slice_index(self, slicer):
        try:
            start_index = sorted_index(self._index, slicer.start) if self._sort else self._index.index(slicer.start)
        except ValueError:
            raise IndexError('start of slice not in the index')
        try:

            end_index = sorted_index(self._index, slicer.stop) if self._sort else self._index.index(slicer.stop)