github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/plans/objchange/lcs.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package objchange
     5  
     6  import (
     7  	"github.com/zclconf/go-cty/cty"
     8  )
     9  
    10  // ValueEqual provides an implementation of the equals function that can be
    11  // passed into LongestCommonSubsequence when comparing cty.Value types.
    12  func ValueEqual(x, y cty.Value) bool {
    13  	unmarkedX, xMarks := x.UnmarkDeep()
    14  	unmarkedY, yMarks := y.UnmarkDeep()
    15  	eqV := unmarkedX.Equals(unmarkedY)
    16  	if len(xMarks) != len(yMarks) {
    17  		eqV = cty.False
    18  	}
    19  	if eqV.IsKnown() && eqV.True() {
    20  		return true
    21  	}
    22  	return false
    23  }
    24  
    25  // LongestCommonSubsequence finds a sequence of values that are common to both
    26  // x and y, with the same relative ordering as in both collections. This result
    27  // is useful as a first step towards computing a diff showing added/removed
    28  // elements in a sequence.
    29  //
    30  // The approached used here is a "naive" one, assuming that both xs and ys will
    31  // generally be small in most reasonable Terraform configurations. For larger
    32  // lists the time/space usage may be sub-optimal.
    33  //
    34  // A pair of lists may have multiple longest common subsequences. In that
    35  // case, the one selected by this function is undefined.
    36  func LongestCommonSubsequence[V any](xs, ys []V, equals func(x, y V) bool) []V {
    37  	if len(xs) == 0 || len(ys) == 0 {
    38  		return make([]V, 0)
    39  	}
    40  
    41  	c := make([]int, len(xs)*len(ys))
    42  	eqs := make([]bool, len(xs)*len(ys))
    43  	w := len(xs)
    44  
    45  	for y := 0; y < len(ys); y++ {
    46  		for x := 0; x < len(xs); x++ {
    47  			eq := false
    48  			if equals(xs[x], ys[y]) {
    49  				eq = true
    50  				eqs[(w*y)+x] = true // equality tests can be expensive, so cache it
    51  			}
    52  			if eq {
    53  				// Sequence gets one longer than for the cell at top left,
    54  				// since we'd append a new item to the sequence here.
    55  				if x == 0 || y == 0 {
    56  					c[(w*y)+x] = 1
    57  				} else {
    58  					c[(w*y)+x] = c[(w*(y-1))+(x-1)] + 1
    59  				}
    60  			} else {
    61  				// We follow the longest of the sequence above and the sequence
    62  				// to the left of us in the matrix.
    63  				l := 0
    64  				u := 0
    65  				if x > 0 {
    66  					l = c[(w*y)+(x-1)]
    67  				}
    68  				if y > 0 {
    69  					u = c[(w*(y-1))+x]
    70  				}
    71  				if l > u {
    72  					c[(w*y)+x] = l
    73  				} else {
    74  					c[(w*y)+x] = u
    75  				}
    76  			}
    77  		}
    78  	}
    79  
    80  	// The bottom right cell tells us how long our longest sequence will be
    81  	seq := make([]V, c[len(c)-1])
    82  
    83  	// Now we will walk back from the bottom right cell, finding again all
    84  	// of the equal pairs to construct our sequence.
    85  	x := len(xs) - 1
    86  	y := len(ys) - 1
    87  	i := len(seq) - 1
    88  
    89  	for x > -1 && y > -1 {
    90  		if eqs[(w*y)+x] {
    91  			// Add the value to our result list and then walk diagonally
    92  			// up and to the left.
    93  			seq[i] = xs[x]
    94  			x--
    95  			y--
    96  			i--
    97  		} else {
    98  			// Take the path with the greatest sequence length in the matrix.
    99  			l := 0
   100  			u := 0
   101  			if x > 0 {
   102  				l = c[(w*y)+(x-1)]
   103  			}
   104  			if y > 0 {
   105  				u = c[(w*(y-1))+x]
   106  			}
   107  			if l > u {
   108  				x--
   109  			} else {
   110  				y--
   111  			}
   112  		}
   113  	}
   114  
   115  	if i > -1 {
   116  		// should never happen if the matrix was constructed properly
   117  		panic("not enough elements in sequence")
   118  	}
   119  
   120  	return seq
   121  }