class_docstr: FlatData is a dataset optimized for efficient repeated concatenation
  and subset operations.
  
  The class combines concatentation and subsampling operations in a single
  class.
  
  Class for internal use only. Users shuold use `AvalancheDataset` for data
  or `DataAttribute` for attributes such as class and task labels.
  
  *Notes for subclassing*
  
  Cat/Sub operations are "flattened" if possible, which means that they will
  take the datasets and indices from their arguments and create a new dataset
  with them, avoiding the creation of large trees of dataset (what would
  happen with PyTorch datasets). Flattening is not always possible, for
  example if the data contains additional info (e.g. custom transformation
  groups), so subclasses MUST set `can_flatten` properly in order to avoid
  nasty bugs.
docstr: Concatenation operation.
  
  :param other: other dataset.
  :return:
docstr_tok: ["Concatenation","operation",".",":","param","other",":","other","dataset",".",":","return",":"]
code: def concat(self: TFlatData, other: TFlatData) -> TFlatData:
      """Concatenation operation.
  
      :param other: other dataset.
      :return:
      """
      if (not self._can_flatten) and (not other._can_flatten):
          return self.__class__(datasets=[self, other])
  
      # Case 1: one is a subset of the other
      if len(self._datasets) == 1 and len(other._datasets) == 1:
          if (
              self._can_flatten
              and self._datasets[0] is other
              and other._indices is None
          ):
              idxs = (
                  self._get_lazy_indices() + other._get_lazy_indices()
              )
              return other.subset(idxs)
          elif (
              other._can_flatten
              and other._datasets[0] is self
              and self._indices is None
          ):
              idxs = (
                  self._get_lazy_indices() + other._get_lazy_indices()
              )
              return self.subset(idxs)
          elif (
              self._can_flatten
              and other._can_flatten
              and self._datasets[0] is other._datasets[0]
          ):
              idxs = LazyIndices(
                  self._get_lazy_indices(), other._get_lazy_indices()
              )
              return self.__class__(
                  datasets=self._datasets, indices=idxs
              )
  
      # Case 2: at least one of them can be flattened
      if self._can_flatten and other._can_flatten:
          if self._indices is None and other._indices is None:
              new_indices = None
          else:
              if len(self._cumulative_sizes) == 0:
                  base_other = 0
              else:
                  base_other = self._cumulative_sizes[-1]
              other_idxs = LazyIndices(
                  other._get_lazy_indices(), offset=base_other
              )
              new_indices = self._get_lazy_indices() + other_idxs
          return self.__class__(
              datasets=self._datasets + other._datasets,
              indices=new_indices,
          )
      elif self._can_flatten:
          if self._indices is None and other._indices is None:
              new_indices = None
          else:
              if len(self._cumulative_sizes) == 0:
                  base_other = 0
              else:
                  base_other = self._cumulative_sizes[-1]
              other_idxs = LazyRange(0, len(other), offset=base_other)
              new_indices = self._get_lazy_indices() + other_idxs
          return self.__class__(
              datasets=self._datasets + [other], indices=new_indices
          )
      elif other._can_flatten:
          if self._indices is None and other._indices is None:
              new_indices = None
          else:
              base_other = len(self)
              self_idxs = LazyRange(0, len(self))
              other_idxs = LazyIndices(
                  other._get_lazy_indices(), offset=base_other
              )
              new_indices = self_idxs + other_idxs
          return self.__class__(
              datasets=[self] + other._datasets, indices=new_indices
          )
      else:
          assert False, "should never get here"
  
imports: import bisect
  import sys
  import numpy
  from avalanche.benchmarks.utils.dataset_utils import slice_alike_object_to_indices
  from typing import Iterable, List, Optional, Sequence, Tuple, TypeVar, Union, overload
  from torch.utils.data import ConcatDataset
  import itertools
  from avalanche.benchmarks.utils.dataset_definitions import IDataset
label_desc: Use image node_id 4 for calling the FlatData obj's underlying member method code with example usage: obj.concat(other) and returns: self, self, other, self, self, self, self