# 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
        if not len(self._data):  # if all the columns have been deleted, remove index
            self.index = list()

    def sort_index(self):
        """
        Sort the DataFrame by the index. The sort modifies the DataFrame inplace

        :return: nothing
        """
        sort = sorted_list_indexes(self._index)
        # sort index
        self._index = self._dropin([self._index[x] for x in sort]) if self._dropin else [self._index[x] for x in sort]
        # each column
        for c in range(len(self._data)):
            self._data[c] = self._dropin([self._data[c][i] for i in sort]) if self._dropin \
                else [self._data[c][i] for i in sort]

    def sort_columns(self, column, key=None, reverse=False):
        """
        Sort the DataFrame by one of the columns. The sort modifies the DataFrame inplace. The key and reverse
        parameters have the same meaning as for the built-in sort() function.

        :param column: column name to use for the 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 sort as if each comparison were reversed.
        :return: nothing
        """
        if self._check_list(column):
            raise TypeError('Can only sort by a single column  ')

# 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

        if not len(self._data):  # if all the columns have been deleted, remove index
            self.index = list()

    def sort_index(self):
        """
        Sort the DataFrame by the index. The sort modifies the DataFrame inplace

        :return: nothing
        """
        sort = sorted_list_indexes(self._index)
        # sort index
        self._index = self._dropin([self._index[x] for x in sort]) if self._dropin else [self._index[x] for x in sort]
        # each column
        for c in range(len(self._data)):
            self._data[c] = self._dropin([self._data[c][i] for i in sort]) if self._dropin \
                else [self._data[c][i] for i in sort]

    def sort_columns(self, column, key=None, reverse=False):
        """
        Sort the DataFrame by one of the columns. The sort modifies the DataFrame inplace. The key and reverse
        parameters have the same meaning as for the built-in sort() function.

        :param column: column name to use for the 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 sort as if each comparison were reversed.
        :return: nothing
        """
        if self._check_list(column):
            raise TypeError('Can only sort by a single column  ')

        sort = sorted_list_indexes(self._data[self._columns.index(column)], key, reverse)