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 }