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 }