github.com/opentofu/opentofu@v1.7.1/internal/plans/objchange/lcs.go (about)

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