diff --git a/Algorithms.Tests/Strings/MinCostStringConversionTests.fs b/Algorithms.Tests/Strings/MinCostStringConversionTests.fs index 015b405..10fd927 100644 --- a/Algorithms.Tests/Strings/MinCostStringConversionTests.fs +++ b/Algorithms.Tests/Strings/MinCostStringConversionTests.fs @@ -2,22 +2,100 @@ 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) + + 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.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)) + -// FIXME -// [] -// 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 (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) diff --git a/Algorithms/Strings/MinCostStringConversion.fs b/Algorithms/Strings/MinCostStringConversion.fs index 8a46615..72d304b 100644 --- a/Algorithms/Strings/MinCostStringConversion.fs +++ b/Algorithms/Strings/MinCostStringConversion.fs @@ -9,90 +9,71 @@ 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 _ -> 0)) let ops = - [| for i in 0 .. lenSourceSeq + 1 -> [| for i in 0 .. lenDestinationSeq + 1 -> "" |] |] + Array.init (source.Length + 1) (fun _ -> Array.init (destination.Length + 1) (fun _ -> Operation.Copy 'a')) - for i = 1 to lenSourceSeq + 1 do + for i = 1 to source.Length do costs.[i].[0] <- i * deleteCost - ops.[i].[0] <- sprintf "D%s" (sourceSeq.[i - 1]) + ops.[i].[0] <- Operation.Delete source.[i - 1] - for i = 1 to lenDestinationSeq + 1 do + for i = 1 to destination.Length do costs.[0].[i] <- i * insertCost - ops.[0].[i] <- sprintf "I%s" (destinationSeq.[i - 1]) + ops.[0].[i] <- Operation.Insert destination.[i - 1] - for i in 1 .. lenSourceSeq + 1 do - for j in 1 .. lenDestinationSeq + 1 do - if sourceSeq.[i - 1] = destinationSeq.[j - 1] then + 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] <- costs.[i - 1].[j - 1] + copyCost - ops.[i].[j] <- sprintf "C%s" (sourceSeq.[i - 1]) + ops.[i].[j] <- 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])) + ops.[i].[j] <- 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]) + ops.[i].[j] <- 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]) + ops.[i].[j] <- Operation.Insert destination.[j - 1] - costs |> Seq.cast |> Seq.toList, ops |> Seq.cast |> Seq.toList + costs, ops - 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 + | 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] |] - seq <- seq |> Array.append ch - seq |> List.ofArray