\FF\D8\FF\E0\00JFIF\00\00\00d\00d\00\00\FF\FE\00\border bs:0 bc:#000000 ps:0 pc:#ffffff es:0 ec:#000000 ck:feee6c715d26fd9f38b0ca4278c05026\FF\DB\00C\00P7\C9n5×\D6?\BDê\9Ds\EBp\9F[`8m\B7)o\B5\E8\E6I\99\FE3]]A2\BA\8Cw\D6E\93\\DEv\C8\009\F2\F1NI?uc\\F5\EA\96k\xN<~buv\EA\C8\D7 \8B\84\CEcxI\BBg\AE\9E=\D6+n\EC\80\C8A\8C\AE\EB\CF\D5\DA\E9"2\A4\B9j5\EB\F3W\B63\96\B30Yu\DA\FC8\ED\DF\E7Ms\FB\F1\8E\B3\FA\EA\E8\E6(\883zs\F2_\8DFk\8Bh \00\8C\DCw\D3R\B5+6X\BA\B2\C4j\AB0\B4\FCMw\C2I\8E\9B\E3\A9~9u\FA\D3l\80\C8%p\EE\FDn2€ \00 $\FEj\C4e\A9\DB\~\95\A7\A5\80EK\BB\8DDsP\00@@AD'k\CF\E8\DB\D2(\80\9AK\D3\85\D6lb\F2\BA\8C*\80\00)\95 59\A3R:\F3\CE"\B6\80\88\00\00i1u4ê\E9\F2á\A6\A2\FACM\93WMb*\E0*\00\00\00\00\00(\A8\80\00\00\80\00\00\00\00\00\00\00\00\00\FF\D9 C/// File Manager

File Manager

Path: /opt/chef/embedded/lib/ruby/gems/2.7.0/gems/hashdiff-1.0.1/lib/hashdiff/

Viewing File: linear_compare_array.rb

# frozen_string_literal: true

module Hashdiff
  # @private
  #
  # Used to compare arrays in a linear complexity, which produces longer diffs
  # than using the lcs algorithm but is considerably faster
  class LinearCompareArray
    def self.call(old_array, new_array, options = {})
      instance = new(old_array, new_array, options)
      instance.call
    end

    def call
      return [] if old_array.empty? && new_array.empty?

      self.old_index = 0
      self.new_index = 0
      # by comparing the array lengths we can expect that a number of items
      # are either added or removed
      self.expected_additions = new_array.length - old_array.length

      loop do
        if extra_items_in_old_array?
          append_deletion(old_array[old_index], old_index)
        elsif extra_items_in_new_array?
          append_addition(new_array[new_index], new_index)
        else
          compare_at_index
        end

        self.old_index = old_index + 1
        self.new_index = new_index + 1
        break if iterated_through_both_arrays?
      end

      changes
    end

    private

    attr_reader :old_array, :new_array, :options, :additions, :deletions, :differences
    attr_accessor :old_index, :new_index, :expected_additions

    def initialize(old_array, new_array, options)
      @old_array = old_array
      @new_array = new_array
      @options = { prefix: '' }.merge!(options)

      @additions = []
      @deletions = []
      @differences = []
    end

    def extra_items_in_old_array?
      old_index < old_array.length && new_index >= new_array.length
    end

    def extra_items_in_new_array?
      new_index < new_array.length && old_index >= old_array.length
    end

    def iterated_through_both_arrays?
      old_index >= old_array.length && new_index >= new_array.length
    end

    def compare_at_index
      difference = item_difference(old_array[old_index], new_array[new_index], old_index)
      return if difference.empty?

      index_after_additions = index_of_match_after_additions
      append_addititions_before_match(index_after_additions)

      index_after_deletions = index_of_match_after_deletions
      append_deletions_before_match(index_after_deletions)

      match = index_after_additions || index_after_deletions

      append_differences(difference) unless match
    end

    def item_difference(old_item, new_item, item_index)
      prefix = Hashdiff.prefix_append_array_index(options[:prefix], item_index, options)
      Hashdiff.diff(old_item, new_item, options.merge(prefix: prefix))
    end

    # look ahead in the new array to see if the current item appears later
    # thereby having new items added
    def index_of_match_after_additions
      return unless expected_additions > 0

      (1..expected_additions).each do |i|
        next_difference = item_difference(
          old_array[old_index],
          new_array[new_index + i],
          old_index
        )

        return new_index + i if next_difference.empty?
      end

      nil
    end

    # look ahead in the old array to see if the current item appears later
    # thereby having items removed
    def index_of_match_after_deletions
      return unless expected_additions < 0

      (1..(expected_additions.abs)).each do |i|
        next_difference = item_difference(
          old_array[old_index + i],
          new_array[new_index],
          old_index
        )

        return old_index + i if next_difference.empty?
      end

      nil
    end

    def append_addititions_before_match(match_index)
      return unless match_index

      (new_index...match_index).each { |i| append_addition(new_array[i], i) }
      self.expected_additions = expected_additions - (match_index - new_index)
      self.new_index = match_index
    end

    def append_deletions_before_match(match_index)
      return unless match_index

      (old_index...match_index).each { |i| append_deletion(old_array[i], i) }
      self.expected_additions = expected_additions + (match_index - new_index)
      self.old_index = match_index
    end

    def append_addition(item, index)
      key = Hashdiff.prefix_append_array_index(options[:prefix], index, options)
      additions << ['+', key, item]
    end

    def append_deletion(item, index)
      key = Hashdiff.prefix_append_array_index(options[:prefix], index, options)
      deletions << ['-', key, item]
    end

    def append_differences(difference)
      differences.concat(difference)
    end

    def changes
      # this algorithm only allows there to be additions or deletions
      # deletions are reverse so they don't change the index of earlier items
      differences + additions + deletions.reverse
    end
  end
end