Skip to content

Commit d0c57ac

Browse files
authored
Fix algorithm and tests for Strings/MinCostStringConversion (#29)
* Uncomment tests * Fix algorithm * Add new tests and attempt to fix old tests * fix computeTransformTables tests * remove trailing spaces * remove trailing spaces * Remove option type from internal implementation
1 parent d8ffc18 commit d0c57ac

File tree

2 files changed

+133
-74
lines changed

2 files changed

+133
-74
lines changed

Algorithms.Tests/Strings/MinCostStringConversionTests.fs

Lines changed: 95 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,100 @@
22

33
open Microsoft.VisualStudio.TestTools.UnitTesting
44
open Algorithms.Strings
5+
open MinCostStringConversion
6+
7+
[<TestClass>]
8+
type MinCostStringConversionTests () =
9+
10+
let validateAndApply (source: string ) (operations: Operation array) : string =
11+
operations
12+
|> Array.mapFold (fun sourcePosition op ->
13+
match op with
14+
| Operation.Copy s ->
15+
Assert.AreEqual(source.[sourcePosition], s)
16+
Some s, sourcePosition + 1
17+
| Operation.Replace (s, d) ->
18+
Assert.AreEqual(source.[sourcePosition], s)
19+
Some d, sourcePosition + 1
20+
| Operation.Delete s ->
21+
Assert.AreEqual(source.[sourcePosition], s)
22+
None, sourcePosition + 1
23+
| Operation.Insert c ->
24+
Some c, sourcePosition
25+
) 0
26+
|> fst
27+
|> Array.choose id
28+
|> Array.map string
29+
|> String.concat ""
30+
31+
let calculateCost (operations: Operation array, copyCost:int, replaceCost:int, deleteCost:int, insertCost:int) =
32+
operations
33+
|> Array.sumBy (function
34+
| Operation.Copy _ -> copyCost
35+
| Operation.Replace _ -> replaceCost
36+
| Operation.Delete _ -> deleteCost
37+
| Operation.Insert _ -> insertCost
38+
)
39+
40+
41+
[<TestMethod>]
42+
[<DataRow("", "", 1, 2, 3, 4)>]
43+
[<DataRow("github", "", 1, 2, 3, 4)>]
44+
[<DataRow("", "github", 1, 2, 3, 4)>]
45+
[<DataRow("github", "github", 1, 2, 3, 4)>]
46+
[<DataRow("banana", "apple", 1, 2, 3, 4)>]
47+
[<DataRow("banana", "apple", 3, 1, 2, 4)>]
48+
[<DataRow("banana", "apple", 3, 1, 2, 4)>]
49+
member this.validateResult (source: string, destination: string, copyCost:int, replaceCost:int, deleteCost:int, insertCost:int) =
50+
let costs, ops = computeTransformTables (source, destination, copyCost, replaceCost, deleteCost, insertCost)
51+
52+
for i = 0 to source.Length do
53+
for j = 0 to destination.Length do
54+
let sourceSubstring = source.Substring(0, i)
55+
let destinationSubstring = destination.Substring(0, j)
56+
let operations = assembleTransformation (ops, i, j)
57+
let actualDestinationSubstring = validateAndApply sourceSubstring operations
58+
let calculatedCost = calculateCost (operations, copyCost, replaceCost, deleteCost, insertCost)
59+
Assert.AreEqual (destinationSubstring, actualDestinationSubstring)
60+
Assert.AreEqual (costs.[i].[j], calculatedCost)
61+
62+
static member inputForComputeTransformTables =
63+
seq {
64+
yield [|
65+
"abbbaba" :> obj
66+
"ababa" :> obj
67+
1 :> obj
68+
2 :> obj
69+
3 :> obj
70+
3 :> obj
71+
([|
72+
[|0; 3; 6; 9; 12; 15|]
73+
[|3; 1; 4; 7; 10; 13|]
74+
[|6; 4; 2; 5; 8; 11|]
75+
[|9; 7; 5; 4; 6; 9|]
76+
[|12; 10; 8; 7; 5; 8|]
77+
[|15; 13; 11; 9; 8; 6|]
78+
[|18; 16; 14; 12; 10; 9|]
79+
[|21; 19; 17; 15; 13; 11|]
80+
|],
81+
[|
82+
[|Operation.Copy 'a'; Operation.Insert 'a'; Operation.Insert 'b'; Operation.Insert 'a'; Operation.Insert 'b'; Operation.Insert 'a'|]
83+
[|Operation.Delete 'a'; Operation.Copy 'a'; Operation.Insert 'b'; Operation.Copy 'a'; Operation.Insert 'b'; Operation.Copy 'a'|]
84+
[|Operation.Delete 'b'; Operation.Delete 'b'; Operation.Copy 'b'; Operation.Insert 'a'; Operation.Copy 'b'; Operation.Insert 'a'|]
85+
[|Operation.Delete 'b'; Operation.Delete 'b'; Operation.Copy 'b'; Operation.Replace ('b', 'a'); Operation.Copy 'b'; Operation.Insert 'a'|]
86+
[|Operation.Delete 'b'; Operation.Delete 'b'; Operation.Copy 'b'; Operation.Replace ('b', 'a'); Operation.Copy 'b'; Operation.Replace ('b', 'a')|]
87+
[|Operation.Delete 'a'; Operation.Copy 'a'; Operation.Delete 'a'; Operation.Copy 'a'; Operation.Delete 'a'; Operation.Copy 'a'|]
88+
[|Operation.Delete 'b'; Operation.Delete 'b'; Operation.Copy 'b'; Operation.Delete 'b'; Operation.Copy 'b'; Operation.Delete 'b'|]
89+
[|Operation.Delete 'a'; Operation.Copy 'a'; Operation.Delete 'a'; Operation.Copy 'a'; Operation.Delete 'a'; Operation.Copy 'a'|]
90+
|]) :> obj
91+
|]
92+
}
93+
94+
[<TestMethod>]
95+
[<DynamicData(nameof(MinCostStringConversionTests.inputForComputeTransformTables))>]
96+
member this.computeTransformTables (sourceString:string, destinationString:string, copyCost:int, replaceCost:int, deleteCost:int, insertCost:int, expected:int array array * Operation array array) =
97+
let actual = MinCostStringConversion.computeTransformTables(sourceString,destinationString,copyCost,replaceCost,deleteCost,insertCost)
98+
Assert.IsTrue((expected = actual))
99+
5100

6-
// FIXME
7-
// [<TestClass>]
8-
// type MinCostStringConversionTests () =
9-
10-
// [<TestMethod>]
11-
// [<DataRow("abbbaba", "abbba")>]
12-
// [<DataRow("ababa", "ababa")>]
13-
// member this.assembleTransformation (ops:string list, i:int, j:int, expected:string list) =
14-
// let actual = MinCostStringConversion.assembleTransformation(ops, i, j)
15-
// Assert.AreEqual(expected, actual)
16-
17-
// [<TestMethod>]
18-
// [<DataRow("abbbaba", "abbba")>]
19-
// [<DataRow("ababa", "ababa")>]
20-
// member this.assembleTransformation (sourceString:string, destinationString:string, copyCost:int, replaceCost:int, deleteCost:int, insertCost:int, expected:int list * string list) =
21-
// let actual = MinCostStringConversion.computeTransformTables(sourceString,destinationString,copyCost,replaceCost,deleteCost,insertCost)
22-
// Assert.AreEqual(expected, actual)
23101

Algorithms/Strings/MinCostStringConversion.fs

Lines changed: 38 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -9,90 +9,71 @@
99
namespace Algorithms.Strings
1010

1111
module MinCostStringConversion =
12+
13+
[<RequireQualifiedAccess>]
14+
type Operation =
15+
| Copy of char
16+
| Replace of Source: char * Target: char
17+
| Delete of char
18+
| Insert of char
19+
1220
let computeTransformTables
1321
(
14-
sourceString: string,
15-
destinationString: string,
22+
source: string,
23+
destination: string,
1624
copyCost: int,
1725
replaceCost: int,
1826
deleteCost: int,
1927
insertCost: int
20-
): list<int> * list<string> =
21-
let sourceSeq = [ sourceString ]
22-
let destinationSeq = [ destinationString ]
23-
let lenSourceSeq = sourceSeq.Length
24-
let lenDestinationSeq = destinationSeq.Length
28+
): array<array<int>> * array<array<Operation>> =
2529

2630
let costs =
27-
[| for i in 0 .. (lenSourceSeq + 1) -> [| for i in 0 .. lenDestinationSeq + 1 -> 0 |] |]
31+
Array.init (source.Length + 1) (fun _ -> Array.init (destination.Length + 1) (fun _ -> 0))
2832

2933
let ops =
30-
[| for i in 0 .. lenSourceSeq + 1 -> [| for i in 0 .. lenDestinationSeq + 1 -> "" |] |]
34+
Array.init (source.Length + 1) (fun _ -> Array.init (destination.Length + 1) (fun _ -> Operation.Copy 'a'))
3135

32-
for i = 1 to lenSourceSeq + 1 do
36+
for i = 1 to source.Length do
3337
costs.[i].[0] <- i * deleteCost
34-
ops.[i].[0] <- sprintf "D%s" (sourceSeq.[i - 1])
38+
ops.[i].[0] <- Operation.Delete source.[i - 1]
3539

36-
for i = 1 to lenDestinationSeq + 1 do
40+
for i = 1 to destination.Length do
3741
costs.[0].[i] <- i * insertCost
38-
ops.[0].[i] <- sprintf "I%s" (destinationSeq.[i - 1])
42+
ops.[0].[i] <- Operation.Insert destination.[i - 1]
3943

40-
for i in 1 .. lenSourceSeq + 1 do
41-
for j in 1 .. lenDestinationSeq + 1 do
42-
if sourceSeq.[i - 1] = destinationSeq.[j - 1] then
44+
for i in 1 .. source.Length do
45+
for j in 1 .. destination.Length do
46+
if source.[i - 1] = destination.[j - 1] then
4347
costs.[i].[j] <- costs.[i - 1].[j - 1] + copyCost
44-
ops.[i].[j] <- sprintf "C%s" (sourceSeq.[i - 1])
48+
ops.[i].[j] <- Operation.Copy (source.[i - 1])
4549
else
4650
costs.[i].[j] <- costs.[i - 1].[j - 1] + replaceCost
47-
48-
ops.[i].[j] <-
49-
sprintf
50-
"R%s"
51-
(sourceSeq.[i - 1]
52-
+ (string) (destinationSeq.[j - 1]))
51+
ops.[i].[j] <- Operation.Replace (source.[i - 1], destination.[j - 1])
5352

5453
if costs.[i - 1].[j] + deleteCost < costs.[i].[j] then
5554
costs.[i].[j] <- costs.[i - 1].[j] + deleteCost
56-
ops.[i].[j] <- sprintf "D%s" (sourceSeq.[i - 1])
55+
ops.[i].[j] <- Operation.Delete (source.[i - 1])
5756

5857
if costs.[i].[j - 1] + insertCost < costs.[i].[j] then
5958
costs.[i].[j] <- costs.[i].[j - 1] + insertCost
60-
ops.[i].[j] <- sprintf "I%s" (destinationSeq.[j - 1])
59+
ops.[i].[j] <- Operation.Insert destination.[j - 1]
6160

62-
costs |> Seq.cast<int> |> Seq.toList, ops |> Seq.cast<string> |> Seq.toList
61+
costs, ops
6362

64-
let rec assembleTransformation (ops: list<string>, i: int, j: int): list<string> =
63+
let rec assembleTransformation (ops: array<array<Operation>>, i: int, j: int): array<Operation> =
64+
printfn $"i={i},j={j},%A{ops}"
6565
if i = 0 && j = 0 then
66-
List.empty
66+
Array.empty
6767
else
6868
match ops.[i].[j] with
69-
| o when o = 'C' || o = 'R' ->
70-
let mutable seq =
71-
assembleTransformation (ops, i - 1, j - 1)
72-
|> List.toArray
73-
74-
let ch =
75-
[ ((string) ops.[i].[j]) ] |> List.toArray
76-
77-
seq <- seq |> Array.append ch
78-
seq |> List.ofArray
79-
| 'D' ->
80-
let mutable seq =
81-
assembleTransformation (ops, i - 1, j)
82-
|> List.toArray
83-
84-
let ch =
85-
[ ((string) ops.[i].[j]) ] |> List.toArray
86-
87-
seq <- seq |> Array.append ch
88-
seq |> List.ofArray
89-
| _ ->
90-
let mutable seq =
91-
assembleTransformation (ops, i, j - 1)
92-
|> List.toArray
93-
94-
let ch =
95-
[ ((string) ops.[i].[j]) ] |> List.toArray
69+
| Operation.Replace _
70+
| Operation.Copy _ ->
71+
let seq = assembleTransformation (ops, i - 1, j - 1)
72+
Array.append seq [| ops[i][j] |]
73+
| Operation.Delete _ ->
74+
let seq = assembleTransformation (ops, i - 1, j)
75+
Array.append seq [| ops[i][j] |]
76+
| Operation.Insert _ ->
77+
let seq = assembleTransformation (ops, i , j - 1)
78+
Array.append seq [| ops[i][j] |]
9679

97-
seq <- seq |> Array.append ch
98-
seq |> List.ofArray

0 commit comments

Comments
 (0)