Method: Diff::LCS.traverse_balanced

Defined in:
lib/diff/lcs.rb

.traverse_balanced(seq1, seq2, callbacks = Diff::LCS::BalancedCallbacks) ⇒ Object

#traverse_balanced is an alternative to #traverse_sequences. It uses a different algorithm to iterate through the entries in the computed longest common subsequence. Instead of viewing the changes as insertions or deletions from one of the sequences, #traverse_balanced will report changes between the sequences.

The arguments to #traverse_balanced are the two sequences to traverse and a callback object, like this:

traverse_balanced(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new)

#sdiff is implemented with #traverse_balanced.

Callback Methods

Optional callback methods are emphasized.

callbacks#match

Called when a and b are pointing to common elements in A and B.

callbacks#discard_a

Called when a is pointing to an element not in B.

callbacks#discard_b

Called when b is pointing to an element not in A.

callbacks#change

Called when a and b are pointing to the same relative position, but A[a] and B[b] are not the same; a change has occurred.

#traverse_balanced might be a bit slower than #traverse_sequences, noticeable only while processing huge amounts of data.

Algorithm

a---+
    v
A = a b c e h j l m n p
B = b c d e f j k l m r s t
    ^
b---+

Matches

If there are two arrows (a and b) pointing to elements of sequences A and B, the arrows will initially point to the first elements of their respective sequences. #traverse_sequences will advance the arrows through the sequences one element at a time, calling a method on the user-specified callback object before each advance. It will advance the arrows in such a way that if there are elements A[i] and B[j] which are both equal and part of the longest common subsequence, there will be some moment during the execution of #traverse_sequences when arrow a is pointing to A[i] and arrow b is pointing to B[j]. When this happens, #traverse_sequences will call callbacks#match and then it will advance both arrows.

Discards

Otherwise, one of the arrows is pointing to an element of its sequence that is not part of the longest common subsequence. #traverse_sequences will advance that arrow and will call callbacks#discard_a or callbacks#discard_b, depending on which arrow it advanced.

Changes

If both a and b point to elements that are not part of the longest common subsequence, then #traverse_sequences will try to call callbacks#change and advance both arrows. If callbacks#change is not implemented, then callbacks#discard_a and callbacks#discard_b will be called in turn.

The methods for callbacks#match, callbacks#discard_a, callbacks#discard_b, and callbacks#change are invoked with an event comprising the action (“=”, “+”, “-”, or “!”, respectively), the indexes i and j, and the elements A[i] and B[j]. Return values are discarded by #traverse_balanced.

Context

Note that i and j may not be the same index position, even if a and b are considered to be pointing to matching or changed elements.



476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
# File 'lib/diff/lcs.rb', line 476

def traverse_balanced(seq1, seq2, callbacks = Diff::LCS::BalancedCallbacks)
  matches = Diff::LCS::Internals.lcs(seq1, seq2)
  a_size = seq1.size
  b_size = seq2.size
  ai = bj = mb = 0
  ma = -1
  string = seq1.is_a?(String)

  # Process all the lines in the match vector.
  loop do
    # Find next match indexes +ma+ and +mb+
    loop do
      ma += 1
      break unless ma < matches.size && matches[ma].nil?
    end

    break if ma >= matches.size # end of matches?

    mb = matches[ma]

    # Change(seq2)
    while (ai < ma) || (bj < mb)
      ax = string ? seq1[ai, 1] : seq1[ai]
      bx = string ? seq2[bj, 1] : seq2[bj]

      case [(ai < ma), (bj < mb)]
      when [true, true]
        if callbacks.respond_to?(:change)
          event = Diff::LCS::ContextChange.new("!", ai, ax, bj, bx)
          event = yield event if block_given?
          callbacks.change(event)
          ai += 1
        else
          event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx)
          event = yield event if block_given?
          callbacks.discard_a(event)
          ai += 1
          ax = string ? seq1[ai, 1] : seq1[ai]
          event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx)
          event = yield event if block_given?
          callbacks.discard_b(event)
        end

        bj += 1
      when [true, false]
        event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx)
        event = yield event if block_given?
        callbacks.discard_a(event)
        ai += 1
      when [false, true]
        event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx)
        event = yield event if block_given?
        callbacks.discard_b(event)
        bj += 1
      end
    end

    # Match
    ax = string ? seq1[ai, 1] : seq1[ai]
    bx = string ? seq2[bj, 1] : seq2[bj]
    event = Diff::LCS::ContextChange.new("=", ai, ax, bj, bx)
    event = yield event if block_given?
    callbacks.match(event)
    ai += 1
    bj += 1
  end

  while (ai < a_size) || (bj < b_size)
    ax = string ? seq1[ai, 1] : seq1[ai]
    bx = string ? seq2[bj, 1] : seq2[bj]

    case [(ai < a_size), (bj < b_size)]
    when [true, true]
      if callbacks.respond_to?(:change)
        event = Diff::LCS::ContextChange.new("!", ai, ax, bj, bx)
        event = yield event if block_given?
        callbacks.change(event)
        ai += 1
      else
        event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx)
        event = yield event if block_given?
        callbacks.discard_a(event)
        ai += 1
        ax = string ? seq1[ai, 1] : seq1[ai]
        event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx)
        event = yield event if block_given?
        callbacks.discard_b(event)
      end

      bj += 1
    when [true, false]
      event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx)
      event = yield event if block_given?
      callbacks.discard_a(event)
      ai += 1
    when [false, true]
      event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx)
      event = yield event if block_given?
      callbacks.discard_b(event)
      bj += 1
    end
  end
end