code: def remove_duplicates(items, in_reverse=False, is_unhashable=False):
      
  
      if not items:
          return items
      elif isinstance(items, _removed_dup_types):
          return items
      elif not hasattr(items, '__iter__') and not hasattr(items, '__getitem__'):
          return items
  
      _items = items
      if in_reverse:
          subscriptable = hasattr(items, '__getitem__')
          _items = items[::-1] if subscriptable else reversed([i for i in items])
  
      is_unhashable &= not isinstance(items, STRING_TYPES)
      buffer = list() if is_unhashable else set()
      append = buffer.append if is_unhashable else buffer.add
  
      if not isinstance(items, _remove_dup_types):
          # The fastest case: handles lists (33% of other cases) and generators (25%)
          unique = [i for i in _items if i not in buffer and not append(i)]
      elif isinstance(items, tuple):
          unique = tuple(i for i in _items if i not in buffer and not append(i))
      elif isinstance(items, string_types):
          unique = EMPTY_STR.join(i for i in _items if i not in buffer and not append(i))
      else:
          # Python 3 compliance: for bytearrays, convert integers back to bytes during iteration
          unique = EMPTY_BIN.join(bytes([i]) for i in _items if i not in buffer and not append(i))
  
      return unique if not in_reverse else unique[::-1]
docs: With maximum performance, iterate over items and return unique ordered values.
      :param items: an iterable of values: lists, tuples, strings, or generator
      :param in_reverse: if True, iterate backwards to remove initial duplicates (less performant)
      :param is_unhashable: if False, use a set to track duplicates; otherwise a list (less performant)
      :return: a unique ordered list, tuple or string depending on the type of items