From c835b74bff29be90649a6dd2a2de56c53c3f396c Mon Sep 17 00:00:00 2001 From: Mahdi Hasnat Siyam Date: Fri, 22 Nov 2024 18:58:00 +0600 Subject: [PATCH 1/7] Uncomment tests --- .../Strings/MinCostStringConversionTests.fs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/Algorithms.Tests/Strings/MinCostStringConversionTests.fs b/Algorithms.Tests/Strings/MinCostStringConversionTests.fs index 015b405..0d6e60b 100644 --- a/Algorithms.Tests/Strings/MinCostStringConversionTests.fs +++ b/Algorithms.Tests/Strings/MinCostStringConversionTests.fs @@ -3,21 +3,20 @@ open Microsoft.VisualStudio.TestTools.UnitTesting open Algorithms.Strings -// FIXME -// [] -// type MinCostStringConversionTests () = +[] +type MinCostStringConversionTests () = -// [] -// [] -// [] -// member this.assembleTransformation (ops:string list, i:int, j:int, expected:string list) = -// let actual = MinCostStringConversion.assembleTransformation(ops, i, j) -// Assert.AreEqual(expected, actual) + [] + [] + [] + member this.assembleTransformation (ops:string list, i:int, j:int, expected:string list) = + let actual = MinCostStringConversion.assembleTransformation(ops, i, j) + Assert.AreEqual(expected, actual) -// [] -// [] -// [] -// member this.assembleTransformation (sourceString:string, destinationString:string, copyCost:int, replaceCost:int, deleteCost:int, insertCost:int, expected:int list * string list) = -// let actual = MinCostStringConversion.computeTransformTables(sourceString,destinationString,copyCost,replaceCost,deleteCost,insertCost) -// Assert.AreEqual(expected, actual) + [] + [] + [] + member this.assembleTransformation (sourceString:string, destinationString:string, copyCost:int, replaceCost:int, deleteCost:int, insertCost:int, expected:int list * string list) = + let actual = MinCostStringConversion.computeTransformTables(sourceString,destinationString,copyCost,replaceCost,deleteCost,insertCost) + Assert.AreEqual(expected, actual) From 325023c2b3c07efdfbd17066e54b2ce2dda94b10 Mon Sep 17 00:00:00 2001 From: Mahdi Hasnat Siyam Date: Fri, 22 Nov 2024 21:18:26 +0600 Subject: [PATCH 2/7] Fix algorithm --- Algorithms/Strings/MinCostStringConversion.fs | 127 ++++++++---------- 1 file changed, 56 insertions(+), 71 deletions(-) diff --git a/Algorithms/Strings/MinCostStringConversion.fs b/Algorithms/Strings/MinCostStringConversion.fs index 8a46615..875e91c 100644 --- a/Algorithms/Strings/MinCostStringConversion.fs +++ b/Algorithms/Strings/MinCostStringConversion.fs @@ -9,90 +9,75 @@ namespace Algorithms.Strings module MinCostStringConversion = + + [] + type Operation = + | Copy of char + | Replace of Source: char * Target: char + | Delete of char + | Insert of char + let computeTransformTables ( - sourceString: string, - destinationString: string, + source: string, + destination: string, copyCost: int, replaceCost: int, deleteCost: int, insertCost: int - ): list * list = - let sourceSeq = [ sourceString ] - let destinationSeq = [ destinationString ] - let lenSourceSeq = sourceSeq.Length - let lenDestinationSeq = destinationSeq.Length - + ): array> * array> = + let costs = - [| for i in 0 .. (lenSourceSeq + 1) -> [| for i in 0 .. lenDestinationSeq + 1 -> 0 |] |] - + Array.init (source.Length + 1) (fun _ -> Array.init (destination.Length + 1) (fun _ -> None)) + let ops = - [| for i in 0 .. lenSourceSeq + 1 -> [| for i in 0 .. lenDestinationSeq + 1 -> "" |] |] - - for i = 1 to lenSourceSeq + 1 do - costs.[i].[0] <- i * deleteCost - ops.[i].[0] <- sprintf "D%s" (sourceSeq.[i - 1]) - - for i = 1 to lenDestinationSeq + 1 do - costs.[0].[i] <- i * insertCost - ops.[0].[i] <- sprintf "I%s" (destinationSeq.[i - 1]) - - for i in 1 .. lenSourceSeq + 1 do - for j in 1 .. lenDestinationSeq + 1 do - if sourceSeq.[i - 1] = destinationSeq.[j - 1] then - costs.[i].[j] <- costs.[i - 1].[j - 1] + copyCost - ops.[i].[j] <- sprintf "C%s" (sourceSeq.[i - 1]) + Array.init (source.Length + 1) (fun _ -> Array.init (destination.Length + 1) (fun _ -> None)) + + costs.[0].[0] <- Some 0 + ops.[0].[0] <- Some (Operation.Copy 'a') // There is no operation to perform, assigning dummy operation to satisfy compiler + + + for i = 1 to source.Length do + costs.[i].[0] <- Some (i * deleteCost) + ops.[i].[0] <- Some (Operation.Delete source.[i - 1]) + + for i = 1 to destination.Length do + costs.[0].[i] <- Some (i * insertCost) + ops.[0].[i] <- Some (Operation.Insert destination.[i - 1]) + + for i in 1 .. source.Length do + for j in 1 .. destination.Length do + if source.[i - 1] = destination.[j - 1] then + costs.[i].[j] <- Some (costs.[i - 1].[j - 1].Value + copyCost) + ops.[i].[j] <- Some (Operation.Copy (source.[i - 1])) else - costs.[i].[j] <- costs.[i - 1].[j - 1] + replaceCost - - ops.[i].[j] <- - sprintf - "R%s" - (sourceSeq.[i - 1] - + (string) (destinationSeq.[j - 1])) + costs.[i].[j] <- Some (costs.[i - 1].[j - 1].Value + replaceCost) + ops.[i].[j] <- Some (Operation.Replace (source.[i - 1], destination.[j - 1])) - if costs.[i - 1].[j] + deleteCost < costs.[i].[j] then - costs.[i].[j] <- costs.[i - 1].[j] + deleteCost - ops.[i].[j] <- sprintf "D%s" (sourceSeq.[i - 1]) + if costs.[i - 1].[j].Value + deleteCost < costs.[i].[j].Value then + costs.[i].[j] <- Some (costs.[i - 1].[j].Value + deleteCost) + ops.[i].[j] <- Some (Operation.Delete (source.[i - 1])) - if costs.[i].[j - 1] + insertCost < costs.[i].[j] then - costs.[i].[j] <- costs.[i].[j - 1] + insertCost - ops.[i].[j] <- sprintf "I%s" (destinationSeq.[j - 1]) + if costs.[i].[j - 1].Value + insertCost < costs.[i].[j].Value then + costs.[i].[j] <- Some (costs.[i].[j - 1].Value + insertCost) + ops.[i].[j] <- Some (Operation.Insert destination.[j - 1]) - costs |> Seq.cast |> Seq.toList, ops |> Seq.cast |> Seq.toList + costs |> Array.map (Array.map Option.get), ops |> Array.map (Array.map Option.get) - let rec assembleTransformation (ops: list, i: int, j: int): list = + let rec assembleTransformation (ops: array>, i: int, j: int): array = + printfn $"i={i},j={j},%A{ops}" if i = 0 && j = 0 then - List.empty + Array.empty else match ops.[i].[j] with - | o when o = 'C' || o = 'R' -> - let mutable seq = - assembleTransformation (ops, i - 1, j - 1) - |> List.toArray - - let ch = - [ ((string) ops.[i].[j]) ] |> List.toArray - - seq <- seq |> Array.append ch - seq |> List.ofArray - | 'D' -> - let mutable seq = - assembleTransformation (ops, i - 1, j) - |> List.toArray - - let ch = - [ ((string) ops.[i].[j]) ] |> List.toArray - - seq <- seq |> Array.append ch - seq |> List.ofArray - | _ -> - let mutable seq = - assembleTransformation (ops, i, j - 1) - |> List.toArray - - let ch = - [ ((string) ops.[i].[j]) ] |> List.toArray - - seq <- seq |> Array.append ch - seq |> List.ofArray + | Operation.Replace _ + | Operation.Copy _ -> + let seq = assembleTransformation (ops, i - 1, j - 1) + Array.append seq [| ops[i][j] |] + | Operation.Delete _ -> + let seq = assembleTransformation (ops, i - 1, j) + Array.append seq [| ops[i][j] |] + | Operation.Insert _ -> + let seq = assembleTransformation (ops, i , j - 1) + Array.append seq [| ops[i][j] |] + From 99616dfe88316468c92f5be6a36b5b4f9a4cbaf7 Mon Sep 17 00:00:00 2001 From: Mahdi Hasnat Siyam Date: Fri, 22 Nov 2024 21:19:10 +0600 Subject: [PATCH 3/7] Add new tests and attempt to fix old tests --- .../Strings/MinCostStringConversionTests.fs | 99 ++++++++++++++++++- 1 file changed, 94 insertions(+), 5 deletions(-) diff --git a/Algorithms.Tests/Strings/MinCostStringConversionTests.fs b/Algorithms.Tests/Strings/MinCostStringConversionTests.fs index 0d6e60b..0e80eba 100644 --- a/Algorithms.Tests/Strings/MinCostStringConversionTests.fs +++ b/Algorithms.Tests/Strings/MinCostStringConversionTests.fs @@ -2,21 +2,110 @@ open Microsoft.VisualStudio.TestTools.UnitTesting open Algorithms.Strings +open MinCostStringConversion [] type MinCostStringConversionTests () = - + + let validateAndApply (source: string ) (operations: Operation array) : string = + operations + |> Array.mapFold (fun sourcePosition op -> + match op with + | Operation.Copy s -> + Assert.AreEqual(source.[sourcePosition], s) + Some s, sourcePosition + 1 + | Operation.Replace (s, d) -> + Assert.AreEqual(source.[sourcePosition], s) + Some d, sourcePosition + 1 + | Operation.Delete s -> + Assert.AreEqual(source.[sourcePosition], s) + None, sourcePosition + 1 + | Operation.Insert c -> + Some c, sourcePosition + ) 0 + |> fst + |> Array.choose id + |> Array.map string + |> String.concat "" + + let calculateCost (operations: Operation array, copyCost:int, replaceCost:int, deleteCost:int, insertCost:int) = + operations + |> Array.sumBy (function + | Operation.Copy _ -> copyCost + | Operation.Replace _ -> replaceCost + | Operation.Delete _ -> deleteCost + | Operation.Insert _ -> insertCost + ) + + + [] + [] + [] + [] + [] + [] + [] + [] + member this.validateResult (source: string, destination: string, copyCost:int, replaceCost:int, deleteCost:int, insertCost:int) = + let costs, ops = computeTransformTables (source, destination, copyCost, replaceCost, deleteCost, insertCost) + + for i = 0 to source.Length do + for j = 0 to destination.Length do + let sourceSubstring = source.Substring(0, i) + let destinationSubstring = destination.Substring(0, j) + let operations = assembleTransformation (ops, i, j) + let actualDestinationSubstring = validateAndApply sourceSubstring operations + let calculatedCost = calculateCost (operations, copyCost, replaceCost, deleteCost, insertCost) + Assert.AreEqual (destinationSubstring, actualDestinationSubstring) + Assert.AreEqual (costs.[i].[j], calculatedCost) + [] [] [] - member this.assembleTransformation (ops:string list, i:int, j:int, expected:string list) = + member this.assembleTransformation (ops:Operation array array, i:int, j:int, expected:Operation array) = let actual = MinCostStringConversion.assembleTransformation(ops, i, j) Assert.AreEqual(expected, actual) + + static member inputForComputeTransformTables = + seq { + yield [| + "abbbaba" :> obj + "ababa" :> obj + 1 :> obj + 2 :> obj + 3 :> obj + 3 :> obj + ([| + [|0; 3; 6; 9; 12; 15|] + [|3; 1; 4; 7; 10; 13|] + [|6; 4; 2; 5; 8; 11|] + [|9; 7; 5; 4; 6; 9|] + [|12; 10; 8; 7; 5; 8|] + [|15; 13; 11; 9; 8; 6|] + [|18; 16; 14; 12; 10; 9|] + [|21; 19; 17; 15; 13; 11|] + |], + [| + [|Operation.Copy 'a'; Operation.Insert 'a'; Operation.Insert 'b'; Operation.Insert 'a'; Operation.Insert 'b'; Operation.Insert 'a'|] + [|Operation.Delete 'a'; Operation.Copy 'a'; Operation.Insert 'b'; Operation.Copy 'a'; Operation.Insert 'b'; Operation.Copy 'a'|] + [|Operation.Delete 'b'; Operation.Delete 'b'; Operation.Copy 'b'; Operation.Insert 'a'; Operation.Copy 'b'; Operation.Insert 'a'|] + [|Operation.Delete 'b'; Operation.Delete 'b'; Operation.Copy 'b'; Operation.Replace ('b', 'a'); Operation.Copy 'b'; Operation.Insert 'a'|] + [|Operation.Delete 'b'; Operation.Delete 'b'; Operation.Copy 'b'; Operation.Replace ('b', 'a'); Operation.Copy 'b'; Operation.Replace ('b', 'a')|] + [|Operation.Delete 'a'; Operation.Copy 'a'; Operation.Delete 'a'; Operation.Copy 'a'; Operation.Delete 'a'; Operation.Copy 'a'|] + [|Operation.Delete 'b'; Operation.Delete 'b'; Operation.Copy 'b'; Operation.Delete 'b'; Operation.Copy 'b'; Operation.Delete 'b'|] + [|Operation.Delete 'a'; Operation.Copy 'a'; Operation.Delete 'a'; Operation.Copy 'a'; Operation.Delete 'a'; Operation.Copy 'a'|] + |]) :> obj + |] + } [] - [] - [] - member this.assembleTransformation (sourceString:string, destinationString:string, copyCost:int, replaceCost:int, deleteCost:int, insertCost:int, expected:int list * string list) = + [] + member this.computeTransformTables (sourceString:string, destinationString:string, copyCost:int, replaceCost:int, deleteCost:int, insertCost:int, expected:int array array * Operation array array) = let actual = MinCostStringConversion.computeTransformTables(sourceString,destinationString,copyCost,replaceCost,deleteCost,insertCost) + printfn $"%A{actual}" + printfn "-----" + printfn $"%A{expected}" Assert.AreEqual(expected, actual) + + From 9da15d05c74f1fe4d1944dadb224c084ddf79258 Mon Sep 17 00:00:00 2001 From: Mahdi Hasnat Siyam Date: Fri, 22 Nov 2024 21:24:00 +0600 Subject: [PATCH 4/7] fix computeTransformTables tests --- .../Strings/MinCostStringConversionTests.fs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/Algorithms.Tests/Strings/MinCostStringConversionTests.fs b/Algorithms.Tests/Strings/MinCostStringConversionTests.fs index 0e80eba..9f27e04 100644 --- a/Algorithms.Tests/Strings/MinCostStringConversionTests.fs +++ b/Algorithms.Tests/Strings/MinCostStringConversionTests.fs @@ -58,13 +58,6 @@ type MinCostStringConversionTests () = let calculatedCost = calculateCost (operations, copyCost, replaceCost, deleteCost, insertCost) Assert.AreEqual (destinationSubstring, actualDestinationSubstring) Assert.AreEqual (costs.[i].[j], calculatedCost) - - [] - [] - [] - member this.assembleTransformation (ops:Operation array array, i:int, j:int, expected:Operation array) = - let actual = MinCostStringConversion.assembleTransformation(ops, i, j) - Assert.AreEqual(expected, actual) static member inputForComputeTransformTables = seq { @@ -102,10 +95,7 @@ type MinCostStringConversionTests () = [] member this.computeTransformTables (sourceString:string, destinationString:string, copyCost:int, replaceCost:int, deleteCost:int, insertCost:int, expected:int array array * Operation array array) = let actual = MinCostStringConversion.computeTransformTables(sourceString,destinationString,copyCost,replaceCost,deleteCost,insertCost) - printfn $"%A{actual}" - printfn "-----" - printfn $"%A{expected}" - Assert.AreEqual(expected, actual) + Assert.IsTrue((expected = actual)) From 6d025d14121ac4ad728e3ac612fd051dc42b2e2d Mon Sep 17 00:00:00 2001 From: Mahdi Hasnat Siyam Date: Fri, 22 Nov 2024 21:32:22 +0600 Subject: [PATCH 5/7] remove trailing spaces --- Algorithms/Strings/MinCostStringConversion.fs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Algorithms/Strings/MinCostStringConversion.fs b/Algorithms/Strings/MinCostStringConversion.fs index 875e91c..1539d4c 100644 --- a/Algorithms/Strings/MinCostStringConversion.fs +++ b/Algorithms/Strings/MinCostStringConversion.fs @@ -9,14 +9,14 @@ namespace Algorithms.Strings module MinCostStringConversion = - + [] type Operation = | Copy of char | Replace of Source: char * Target: char | Delete of char | Insert of char - + let computeTransformTables ( source: string, @@ -26,17 +26,17 @@ module MinCostStringConversion = deleteCost: int, insertCost: int ): array> * array> = - + let costs = Array.init (source.Length + 1) (fun _ -> Array.init (destination.Length + 1) (fun _ -> None)) - + let ops = Array.init (source.Length + 1) (fun _ -> Array.init (destination.Length + 1) (fun _ -> None)) - + costs.[0].[0] <- Some 0 - ops.[0].[0] <- Some (Operation.Copy 'a') // There is no operation to perform, assigning dummy operation to satisfy compiler - - + ops.[0].[0] <- Some (Operation.Copy 'a') // There is no operation to perform, assigning dummy operation to satisfy compiler + + for i = 1 to source.Length do costs.[i].[0] <- Some (i * deleteCost) ops.[i].[0] <- Some (Operation.Delete source.[i - 1]) @@ -73,11 +73,11 @@ module MinCostStringConversion = | Operation.Replace _ | Operation.Copy _ -> let seq = assembleTransformation (ops, i - 1, j - 1) - Array.append seq [| ops[i][j] |] + Array.append seq [| ops[i][j] |] | Operation.Delete _ -> let seq = assembleTransformation (ops, i - 1, j) - Array.append seq [| ops[i][j] |] + Array.append seq [| ops[i][j] |] | Operation.Insert _ -> let seq = assembleTransformation (ops, i , j - 1) - Array.append seq [| ops[i][j] |] - + Array.append seq [| ops[i][j] |] + From 896183d2a02cdefdcb76a6f3c277d0e8d271a50b Mon Sep 17 00:00:00 2001 From: Mahdi Hasnat Siyam Date: Fri, 22 Nov 2024 21:32:41 +0600 Subject: [PATCH 6/7] remove trailing spaces --- .../Strings/MinCostStringConversionTests.fs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Algorithms.Tests/Strings/MinCostStringConversionTests.fs b/Algorithms.Tests/Strings/MinCostStringConversionTests.fs index 9f27e04..10fd927 100644 --- a/Algorithms.Tests/Strings/MinCostStringConversionTests.fs +++ b/Algorithms.Tests/Strings/MinCostStringConversionTests.fs @@ -6,7 +6,7 @@ open MinCostStringConversion [] type MinCostStringConversionTests () = - + let validateAndApply (source: string ) (operations: Operation array) : string = operations |> Array.mapFold (fun sourcePosition op -> @@ -27,7 +27,7 @@ type MinCostStringConversionTests () = |> Array.choose id |> Array.map string |> String.concat "" - + let calculateCost (operations: Operation array, copyCost:int, replaceCost:int, deleteCost:int, insertCost:int) = operations |> Array.sumBy (function @@ -36,8 +36,8 @@ type MinCostStringConversionTests () = | Operation.Delete _ -> deleteCost | Operation.Insert _ -> insertCost ) - - + + [] [] [] @@ -48,7 +48,7 @@ type MinCostStringConversionTests () = [] member this.validateResult (source: string, destination: string, copyCost:int, replaceCost:int, deleteCost:int, insertCost:int) = let costs, ops = computeTransformTables (source, destination, copyCost, replaceCost, deleteCost, insertCost) - + for i = 0 to source.Length do for j = 0 to destination.Length do let sourceSubstring = source.Substring(0, i) @@ -58,7 +58,7 @@ type MinCostStringConversionTests () = let calculatedCost = calculateCost (operations, copyCost, replaceCost, deleteCost, insertCost) Assert.AreEqual (destinationSubstring, actualDestinationSubstring) Assert.AreEqual (costs.[i].[j], calculatedCost) - + static member inputForComputeTransformTables = seq { yield [| @@ -96,6 +96,6 @@ type MinCostStringConversionTests () = member this.computeTransformTables (sourceString:string, destinationString:string, copyCost:int, replaceCost:int, deleteCost:int, insertCost:int, expected:int array array * Operation array array) = let actual = MinCostStringConversion.computeTransformTables(sourceString,destinationString,copyCost,replaceCost,deleteCost,insertCost) Assert.IsTrue((expected = actual)) - - + + From 7731d580f300139df07d0b4e8b71abdb21d44421 Mon Sep 17 00:00:00 2001 From: Mahdi Hasnat Siyam Date: Sat, 23 Nov 2024 16:50:47 +0600 Subject: [PATCH 7/7] Remove option type from internal implementation --- Algorithms/Strings/MinCostStringConversion.fs | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/Algorithms/Strings/MinCostStringConversion.fs b/Algorithms/Strings/MinCostStringConversion.fs index 1539d4c..72d304b 100644 --- a/Algorithms/Strings/MinCostStringConversion.fs +++ b/Algorithms/Strings/MinCostStringConversion.fs @@ -28,41 +28,37 @@ module MinCostStringConversion = ): array> * array> = let costs = - Array.init (source.Length + 1) (fun _ -> Array.init (destination.Length + 1) (fun _ -> None)) + Array.init (source.Length + 1) (fun _ -> Array.init (destination.Length + 1) (fun _ -> 0)) let ops = - Array.init (source.Length + 1) (fun _ -> Array.init (destination.Length + 1) (fun _ -> None)) - - costs.[0].[0] <- Some 0 - ops.[0].[0] <- Some (Operation.Copy 'a') // There is no operation to perform, assigning dummy operation to satisfy compiler - + Array.init (source.Length + 1) (fun _ -> Array.init (destination.Length + 1) (fun _ -> Operation.Copy 'a')) for i = 1 to source.Length do - costs.[i].[0] <- Some (i * deleteCost) - ops.[i].[0] <- Some (Operation.Delete source.[i - 1]) + costs.[i].[0] <- i * deleteCost + ops.[i].[0] <- Operation.Delete source.[i - 1] for i = 1 to destination.Length do - costs.[0].[i] <- Some (i * insertCost) - ops.[0].[i] <- Some (Operation.Insert destination.[i - 1]) + costs.[0].[i] <- i * insertCost + ops.[0].[i] <- Operation.Insert destination.[i - 1] for i in 1 .. source.Length do for j in 1 .. destination.Length do if source.[i - 1] = destination.[j - 1] then - costs.[i].[j] <- Some (costs.[i - 1].[j - 1].Value + copyCost) - ops.[i].[j] <- Some (Operation.Copy (source.[i - 1])) + costs.[i].[j] <- costs.[i - 1].[j - 1] + copyCost + ops.[i].[j] <- Operation.Copy (source.[i - 1]) else - costs.[i].[j] <- Some (costs.[i - 1].[j - 1].Value + replaceCost) - ops.[i].[j] <- Some (Operation.Replace (source.[i - 1], destination.[j - 1])) + costs.[i].[j] <- costs.[i - 1].[j - 1] + replaceCost + ops.[i].[j] <- Operation.Replace (source.[i - 1], destination.[j - 1]) - if costs.[i - 1].[j].Value + deleteCost < costs.[i].[j].Value then - costs.[i].[j] <- Some (costs.[i - 1].[j].Value + deleteCost) - ops.[i].[j] <- Some (Operation.Delete (source.[i - 1])) + if costs.[i - 1].[j] + deleteCost < costs.[i].[j] then + costs.[i].[j] <- costs.[i - 1].[j] + deleteCost + ops.[i].[j] <- Operation.Delete (source.[i - 1]) - if costs.[i].[j - 1].Value + insertCost < costs.[i].[j].Value then - costs.[i].[j] <- Some (costs.[i].[j - 1].Value + insertCost) - ops.[i].[j] <- Some (Operation.Insert destination.[j - 1]) + if costs.[i].[j - 1] + insertCost < costs.[i].[j] then + costs.[i].[j] <- costs.[i].[j - 1] + insertCost + ops.[i].[j] <- Operation.Insert destination.[j - 1] - costs |> Array.map (Array.map Option.get), ops |> Array.map (Array.map Option.get) + costs, ops let rec assembleTransformation (ops: array>, i: int, j: int): array = printfn $"i={i},j={j},%A{ops}"