

String Calculator Kata in F# - happy end
source link: https://blog.ciechowski.net/string-calculator-kata-in-f-happy-end
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

String Calculator Kata in F# - happy end
Our calculator works pretty well so far. We were also able to use a few functional techniques in the process. Railway programming and composition is one of them.
This is a second part of a series:
Today we are going to focus on different types of delimiters:
- a delimiter can be of any length
- user can pass multiple delimiters of any size as an input
We are going to write a bit more code to implement these cases. Furthermore, we will do some domain modeling.
As always, in the beginning, there is a test:
[<Fact>]
let ``Delimiter can be of any length`` =
let result = Add "//[***]\n1***2***3"
Assert.Equal(Ok 6, result)
The first thing we will do is introduce a new type called DelimiterType
type DelimiterType =
| Default
| OneCharacterLong
| MoreThanOneCharacterLong
We model our delimiter in a more granular way and explicitly define all available options with this type. Which, in turn, improves our domain.
With the introduction of a different kind of separators, we have to refine our algorithm. Instead of only deciding if a custom delimiter was passed, we now have to recognize its type. We do that with a new function:
let findDelimiterType (numbers: Input) =
match numbers.StartsWith "//" with
| false -> Default
| true ->
match numbers.Contains "[" && numbers.Contains "]" with
| false -> OneCharacterLong
| true -> MoreThanOneCharacterLong
I'm not satisfied with this approach. This double pattern matching seems clumsy. But I don't know what to do with it, and it works. Refactoring remarks are more than welcome.
It also forces us to add a new case in both extractNumbers and extractDelimiter:
let extractNumbers (numbers: Input, delimiterType: DelimiterType) =
match delimiterType with
| Default -> numbers
| OneCharacterLong -> numbers.[4..numbers.Length]
| MoreThanOneCharacterLong ->
let delimiterEnd = numbers.IndexOf "]" + 2
numbers.[delimiterEnd..numbers.Length]
let extractDelimiter (numbers: Input, delimiterType: DelimiterType) =
let defaultDelimiters = [| ","; "\n" |]
match delimiterType with
| Default -> defaultDelimiters
| OneCharacterLong ->
let customDelimiter = [| string numbers.[2] |]
Array.concat [| defaultDelimiters
customDelimiter |]
| MoreThanOneCharacterLong ->
let delimiterStart = numbers.IndexOf "[" + 1
let delimiterEnd = numbers.IndexOf "]" - 1
let delimiter =
[| numbers.[delimiterStart..delimiterEnd] |]
Array.concat [| defaultDelimiters
delimiter |]
One more change in parseInput function, where we extract delimiter type:
let parseInput (input: Input): InputWithDelimiters =
let delimiterType = findDelimiterType input
let numbers = extractNumbers (input, delimiterType)
let delimiters = extractDelimiter (input, delimiterType)
(numbers, delimiters)
And all our tests pass!
There is only one more requirement left. It's passing multiple separators of any length at once. Translate this into a test:
[<Theory>]
[<InlineData("//[*][%]\n1*2%3", 6)>]
[<InlineData("//[!!][..]\n1!!2..3", 6)>]
let ``Multiple delimiters can be passed at once`` (input, expected) =
let result = Add input
Assert.Equal(Ok expected, result)
Similar to the previous step, we have to change extracting numbers and delimiters functions. Luckily, we don't have to change a lot of code. Working code looks in extracting delimiter looks like so:
| MoreThanOneCharacterLong ->
let delimitersStart = numbers.IndexOf "["
let delimitersEnd = numbers.IndexOf "\n" - 1
let delimiters = numbers.[delimitersStart..delimitersEnd]
let customDelimiters =
delimiters.Split([| "["; "]" |],
StringSplitOptions.RemoveEmptyEntries)
Array.concat [| defaultDelimiters
customDelimiters |]
and extracting numbers:
MoreThanOneCharacterLong ->
let delimiterEnd = numbers.LastIndexOf "]" + 2
numbers.[delimiterEnd..numbers.Length]
And that's all! String calculator kata in F# is done! Full code is on my github.
I have to say I've enjoyed it a lot. There were times when I was stuck even though this kata looks relatively simple, right? F# also seems like an interesting language. Not only because of a different paradigm but also for being succinct. I've also loved modeling with types. For a C# developer, it's just so easy.
The best part? It only made me more hungry! Expect more F# code from me. If you have any ideas about the next steps send me a tweet.
Recommend
-
63
README.md
-
34
Rethinking the n queens problem for SQL This is a version of the classic n queens problem (https://en.wikipedia.org/w...
-
10
What we learned from leading a TDD Code Kata lunch and learn I’ve been wanting to try out a code kata for a long time. I’ve also been wanting to get better at Test Driven Development (TDD). I decide...
-
12
The Maître d' kata by Mark Seemann A programming kata. I recently wrote about doing programming katas. You can find k...
-
8
10 lessons learnt from the Ruby Refactoring Kata - Tennis Game Over the last ~2 months, I’ve been scheduling some time to work on a specific Ruby code which is designed to be a good starting point for a refactoring. Those...
-
13
0:00 / 26:40 ...
-
78
Codewars Codewars is a platform that helps you learn, train, and improve your coding skills by solving programming tasks of many types and difficulty levels. Here is the first Kata I've sol...
-
9
Tennis kata using the State pattern by Mark Seemann An example of using the State design pattern. Regular readers of this blog will know that I keep coming back to the
-
7
Diamond kata with FsCheck by Mark Seemann This post is a walk-through of doing the Diamond kata with FsCheck. Recently, Nat Pryce tweeted...
-
7
Guitar String Tension Calculator Available for online use here: https://rodrigocfd.github.io/string-tension-calc Features Compares...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK