def join! datastream
      left = self.data
      right = datastream.data
      columns = self.headers & datastream.headers

      raise NullJoin, 'Datastreams do not share any column' if columns.empty?

      left_groups  = Hash.new { |hash, key| hash[key] = [] }
      right_groups = Hash.new { |hash, key| hash[key] = [] }

      left.each do |row|
        values = columns.map { |column| row[column] }

        left_groups[values] << row unless values.include? nil
      end

      right.each do |row|
        values = columns.map { |column| row[column] }

        right_groups[values] << row unless values.include? nil
      end

      output = []

      left_groups.each do |key, left_group|

        if right_group = right_groups[key]

          left_group.each do |left_tuple|
            right_group.each do |right_tuple|
              output << left_tuple.merge(right_tuple)
            end
          end

        end
      end

      # TODO: I do not like this. Why a datastream's data could be
      # modified directly? Anyway, #join! is likely to be reimplemented
      # many times as more features are added. It will improve.
      self.data = output
      self
    end
def join! datastream
      left = self.data
      right = datastream.data
      columns = self.headers & datastream.headers

      raise NullJoin, 'Datastreams do not share any column' if columns.empty?

      left_groups  = Hash.new { |hash, key| hash[key] = [] }
      right_groups = Hash.new { |hash, key| hash[key] = [] }

      left.each do |row|
        values = columns.map { |column| row[column] }

        left_groups[values] << row unless values.include? nil
      end

      right.each do |row|
        values = columns.map { |column| row[column] }

        right_groups[values] << row unless values.include? nil
      end

      output = []

      left_groups.each do |key, left_group|

        if right_group = right_groups[key]

          left_group.each do |left_tuple|
            right_group.each do |right_tuple|
              output << left_tuple.merge(right_tuple)
            end
          end

        end
      end

      # TODO: I do not like this. Why a datastream's data could be
      # modified directly? Anyway, #join! is likely to be reimplemented
      # many times as more features are added. It will improve.
      self.data = output
      self
    end
TODO: join! creates new rows. It should not.