مرور کاتای اعداد رومی1:55
You're not going to have that. Instead you'd have X. X would be 20, but we need 1 less than that. So again, X for 10, and I. X for 9, which lines up there. And yeah, this would be true all the way up to if you have 900, well, you're not going to do D for 500 and then 4 Cs because you can't have 4. So you'd have 100 less than 1,000. M preceded by C. It's just something you have to memorize, really. But again, you probably learned the basics way back in school. So for this kata, let's write a Roman numeral converter using TDD. Our final class will be called RomanNumerals, and the test is RomanNumeralsTest.
First TDD test2:27
So for this kata, let's write a Roman numeral converter using TDD. Our final class will be called RomanNumerals, and the test is RomanNumeralsTest. Okay, so for our first one, what is the most basic example we can write? Well, how about it generates the Roman numeral for 1? Okay, so let's say if I had a class called RomanNumerals, I don't imagine there's any side effects here, so I'll make it static. And if we generate the Roman numeral for 1, if we run that, it should equal, as we learned, the letter I. Okay, so let's give it a run, and it fails because that class does not exist. All right, we have our next step.
Create class and method3:02
Okay, so let's give it a run, and it fails because that class does not exist. All right, we have our next step. Let's create the class. All right, switch back. I will import that and clean it up. Okay, run the test again. Let's see. Now there's no method called generate. So here's what I'll do. I'll create another little panel down here so we can easily switch back and forth.
So here's what I'll do. I'll create another little panel down here so we can easily switch back and forth. Okay, and this represents my real workflow a little bit more. Okay, so we're going to generate a number that will be a static method. We'll run the test again. Okay, so now the method, of course, returns null, but we expect it I. So the simplest thing we can do is just force it to pass. You might hear this referred to as sliming sometimes. I like that term. And it passes.
I like that term. And it passes. So again, we know this is trivial. We know it won't last, but we're trying to get into the TDD cycle. Okay, so let's write another test. It generates the Roman numeral for 2, and we know that should be II. Okay, so we run that, and of course it fails. So maybe now we'll say, well, if the number is greater than 1, then let's just return 2. Again, the simplest thing we can do to bring it to green.
Use data provider4:17
2. Again, the simplest thing we can do to bring it to green. Okay, let's do another test, but instead of repeating ourselves, let's do the same as the previous episode where we set up a data provider. That way for a new test, I only have to append to an array. So for example, if you give us 1, that should be I, and let's see, if you give us 2, that'll be II. Okay, so now I can get rid of this second test entirely and switch over to using a data provider. And it's called Checks.
If you give us 3, it'll be II. We run the test again, and it fails. Okay, so at this point, it really just comes down to your workflow, how often you want to continue this sliming, so to speak. So yeah, you could say, well, if it's greater than 2, we could go another round, but the idea is at some point you're going to realize, okay, I need to take a moment, all my tests are passing, and clean things up. So what you might start with is a simple while statement, because if you think about it, ultimately, we are building up the Roman numerals, right? So we could have a result here.
Refactor with loop6:06
Okay. So if we run it, let's say you give us 3, 3 is greater than 0, so we append I. Now it's 2, now it's 1, now it's 0. This check no longer passes, so we move on and we return I, I, I. So let's run everything again, and our tests are still passing. Okay, so that's the beginnings of a refactor. But we know, as soon as we add another one here, 4 would be IV, that's going to fail. Okay, so here's what I'm thinking. I already have I referenced in the code here. Do we have to find some place to do IV and V as well?
I already have I referenced in the code here. Do we have to find some place to do IV and V as well? That's going to get confusing really quickly. So what I'd like to do is bring this test to green and then do a second layer of refactoring. How about this? We'll just duplicate this, and we'll say, okay, well, while the number is greater than 3, then append IV, and then subtract 4. Okay, so if I run that code, all of those tests are passing. Okay, so now we're at a point to refactor. I'm not going to repeat this while statement over and over.
Add lookup table7:05
Okay, so now we're at a point to refactor. I'm not going to repeat this while statement over and over. So you'll remember at the beginning of the video, we set up a lookup table in our heads, right? We said, okay, I corresponds to 1, V corresponds to 5, but we don't really have that clear in our code. So let's fix that by storing it in a constant. We'll call it numerals, and it's a constant because it should never change. So our lookup is I corresponds to 1. IV corresponds to 4, and we'll continue working on this as we add more tests.
So iterate over the numerals for each one, while the number that you gave us is greater than the item in this array. So greater than the Arabic form, then to our result, we are going to append the numeral associated with it, and then we will subtract the Arabic form. All right, a little confusing. Let's go over it together. Imagine you give us the number 4. So this is equal to 4. So we're going to loop over this array, and for each one, we'll start. Okay, you gave us the number 4.
So we're going to loop over this array, and for each one, we'll start. Okay, you gave us the number 4. Is 4 greater than or equal to the current item in the loop? And the answer is yes. All right, then to this variable, we will push the Roman numeral associated with it. So now the result is currently equal to IV. Then we take the number you gave us, which is 4, and we subtract that current item there. So now 4 equals 0. And it runs again. While 0 is greater than 4, that's not going to run.
So let's just make it crystal clear. Now we could do it programmatically, and a lot of developers want to do that. They want to figure out the exact algorithm to figure out when you subtract 1 or when you subtract 10. But from my point of view, it's just not necessary. It makes the code quite a bit more confusing, and you're not really getting anything else. Your code's still generating exactly the same as mine. So let's just make it crystal clear. IX corresponds to 9. Okay, so now if I run the code, it all just passes.
IX corresponds to 9. Okay, so now if I run the code, it all just passes. Okay, let's do a few more, and then we'll do another layer of refactoring. And we'll just do this randomly at this point. Let's say if you give us 40, let's say if you give us 50, that should be L. And actually, if you give us 40, that should be XL, right, 10 less than 50. Okay, we run the code. Of course it fails because we haven't told it what L translates to. That's 50. Run it.
Final refactor and edge cases13:18
And all we had to do is set up the lookup table. So if you want, if you want to be super fancy, you could write a bunch of logic to figure out when to subtract and to divide your thousands from your hundreds and all that. I just don't necessarily think it makes for more readable code. I think this ends up being the most readable solution. Okay, so at this point, everything's passing. The only remaining step is to refactor this code and handle any exceptional cases. Okay, so let's start by, well, we have the same thing as the previous episode, right? We have a while statement that ultimately deducts a different number or subtracts from a number.
We have a while statement that ultimately deducts a different number or subtracts from a number. So in these cases, we could switch over to a for statement. So we don't have any initialization, but while number is greater than or equal to Arabic, ultimately, we will subtract that Arabic number from what you provided. So that allows us to remove this. Same thing, just changes the code a little bit. And if I rerun it, it still passes. And actually, that looks pretty clean to me. So the only remaining step is to handle a few edge cases.
Or I'm sorry, it returns false. And that works as well. Okay. So here is our completed code. I want you to take a look at that. And with that, your code kata number two is now complete.
