4.7 Demo: Sentence Analyzer
We didn't have a demo application in the previous chapter, because I wanted to combine both collections and conditionals into a single application, and now we have the chance. The purpose of this application is to take a sentence from the end user and count how many times each character is found within the string. It's a simple example, but once again, there are a few dangers lurking around the corners.
1.Introduction3 lessons, 15:05
2.Foundational Concepts5 lessons, 43:51
3.Working With Collections3 lessons, 26:26
4.Controlling Flow7 lessons, 1:22:24
5.Functions5 lessons, 51:54
6.Creating Types6 lessons, 1:08:10
7.Conclusion1 lesson, 01:15
4.7 Demo: Sentence Analyzer
With all of this control flow information in hand, right now would be a good time to take a little break and go through and create a demo application. So here's the basic specs. It's fairly simple, but it's a very interesting project and there's a lot of different ways where you can get from point A to point B. So the basic concept here is I wanna create a sentence analyzer. So what I wanna do is I want to take the input from the end user, which is going to be a sentence or a string of characters of all sorts of different types. And what I would like to do is I would like to go through the sentence, and I would like to count the number of instances of each character that's found in that string or in that sentence. And a little bit of a monkey wrench to throw in there is, I want to skip a particular character. I wanna skip whitespace or the space character. So that one, I don't wanna count. I wanna leave that alone and just kinda skip over that one. So now, the interesting thing about this problem is that there are several different ways to go about it. And the one that I choose may not be the one that you choose. So what I'm gonna urge you to do is pause the video and go ahead and give this a shot on your own. And think of the different things that you've learned so far, think of the different types that you've learned to work with so far. Think of the different control flow statements you've learned to work with so far, and try to put this together, how it makes sense and as logically as possible for you. Now the nice thing about any sort of programming in Swift is part of this category as well that there's not a single way to get from point A to point B. Now we can definitely take different routes to get there. Some may be more optimal than others, but it really doesn't make that much of a difference. As long as you get there, there's always time to go back and take a look and tweak this and tweak that to make sure that it's a reusable block of code or maybe something that is going to be optimal when it comes to its usage. So if you would like to, now would be the time to pause the video and then come back when you are done. So what I'd like to do now is start to go through my thought process on how I would solve a problem like this. So the first thing I wanna do is I wanna obviously get the input from the user. So I'm going to create a constant, and I'm gonna call this arguments. Now I'm going to go into my process struct again like we did before I'm gonna grab out the arguments. And so now, we're gonna make the assumption at this point that the end user's going to be nice and knows how to use this application because you are the end user at the end of the day. And I've kind of explained what we're looking for here but you could definitely find lots of ways and opportunities to enhance this to make it a little bit more flexible. But for now, we're just going to take in a single argument, which is going to be that sentence. Now in order for us to properly be able to parse this using the arguments here, it's going to have to be a quotation mark enclosed string. Because if you have multiple words in there, it could get interpreted as several different arguments. So what we would like to do is make sure that we are providing the correct input to the application. And then, once again if you wanted to enhance this, which would not be a terrible idea. If you detected that you either didn't get enough arguments or got too many arguments, then maybe you could spit out a little bit of a usage blurb on the console to show the end user how to use the actual application. So now we're gonna go ahead and grab our sentence. Our sentence, remember, is going to be within our arguments array and it's not going to be the first argument, it's going to be the second one. Because remember, the first one at index 0 is going to be the application name. So it's going to be the full path to the application, in my case, sentence analyzer, so we're going to wanna get the first input. So once we have our sentence, the next thing we're gonna need to do is we're gonna need to go through and take a look at all the characters that are found within that sentence or within that string as far as types are concerned with Swift. Now I did mention that strings have changed a little bit in Swift 2.0. And it's not enough to be able to say I want a loop through sentence. Now I have to start talking about sentence if I wanna loop through it anyway with respect to its character collection, which is a collection of type character view. So let's go ahead and start to go down the process of how we could solve this problem. Now there's a few different ways we could do this. I can think of at least two. So let's start off with the first one and see how it goes. So I'm going to create a simple for loop and I'm gonna say for each character c in, and then we'll go into sentence.character. So we're going to loop through all the different characters in this. So as you can see here, c is going to be a character. So now, I can start to check to see if c is a valid character. Cuz remember I wanna skip over the whitespace. I wanna skip over the spaces. Now there's a couple different ways you can do that. You could go the positive case and you could say, if c is equal to, and you could start to list out the different characters that are valid. But that could take a long time and wind up being a very ugly looking piece of code. So I'm gonna go the negative route. So I'm gonna say, if c is not equal to whatever value we wanna skip over. Now a nice little optimization here might be actually to say I wanna let the separator be equal to something. So I'm defining this separator constant up above. So if I ever needed to change it later on, it's gonna be up here at the top and I can just modify this to say, you know what? I don't wanna skip white spaces, I wanna skip tabs, or I wanna skip As, or lower case Bs, or upper case Cs, or whatever have you. And you can kind of play around with that a little bit as it makes for some interesting opportunities for enhancement. So now, we'll go ahead and come into our if block. And now we're gonna need to use a particular data structure that will allow us to maintain two pieces of information. So we wanna maintain the character that we're dealing with and be able to count the number of instances that we would find. And now that kind of the first thing, that description kind of triggers something in my mind to think you know what, I think a dictionary might be helpful for something like this. Now later on, where you're going to learn about creating custom types through classes instruction, so that might be an interesting opportunity for enhancement later on. But we haven't quite gotten that far yet. So the next thing I'm gonna do is I'm gonna create a variable. And we're gonna call this charDict. We're gonna set this equal to [ ]. Our key, remember, is going to be a character, and then we'll use a colon. And our value is going to be an integer, so we can count the number of instances we have found there. I would like to increment the counter stored in the value of this dictionary for this particular character. But the first thing that I have to be wary of is that if the character does not exist in the dictionary and I try to increment it, I could end up with problems with optionals and things of that nature. So the first thing that I wanna do is I actually wanna check to see if that key exists. If that information already exists in the dictionary, then I can just increment it. But if it doesn't exist, then I need to insert it. So the first thing that I'm going to do here is I'm gonna check if charDict. Now the nice thing about this particular type is I can go in, and I can take a look at the collection of keys that are already existent within my dictionary. So I could say, if the charDict.keys.contains, and we can check for a specific element, so we can see if it contains the character that we're dealing with. Then I want to go ahead and go into my character dictionary. I wanna grab the value associated with that key, and I wanna increment it. But the interesting thing here, if you recall, when we started to talk about dictionaries, is that when I retrieve a value or when I attempt to retrieve a key, I'm going to get back an optional, an optional of an integer type or whatever value I've specified. And that's what we're squawking about here. Now even though we've already checked and we've done a little bit of defensive programming here, we know that it exists and that's how it fell into this block. So in order for us to unwrap this and actually increment it very quickly and easily, we're just going to use the exclamation point here after that. And then everything will go away and work just fine. So now, if this key does not exist, we'll go ahead and go into an else block. And at this point, we're going to wanna go into our character dictionary, and we're going to want to insert a key associated with that character. And we're gonna wanna set that value associated with that key equal to 1. So that's going to initialize our counter. So that's the basic process that we're gonna follow. Now as I've been going along here, you may have noticed this little exclamation point up here. And if we take a look at this, we're going to see that it says, a binary operator of not equal to cannot be applied to operands of character and string, and that seems a little odd. Why is that happening? Well, because c, in actuality, as we go through here, is of character type, but we are using implicit or inferred typing here. Swift and the compiler believe that separator right now is actually a full string so if we wanted to kind of coerce this into being a character, we would have to explicitly type it. And then this little error would go away. So now we've gotten to this point. Let's go ahead and see if we can output our results so far. So I wanna be able to loop through all the values found within this dictionary. So there's a couple different ways to do this as well. So what I could do is I could come in here and say for each of the keys that are found in charDict.keys. And then I could come in here and just print out what the value is, so I could say print. And we'll go ahead and say key. And we'll just put a little colon here, and it will print out the count that's found within the character dictionary for that particular key. So we could definitely do this. And if we save this and in order for us to remember provide input to our console application, we will have to actually come up to Product and go into our Scheme and edit our Scheme. And remember, we're going to edit the run, as you can see, I've already put one in here just as a sample string. This is my string. So now if I were to go ahead and run this application, the build succeeds and the output is going to look like this. Now, this seems to be a bit of a problem that we actually are dealing with optionals here, which is not a bad thing. But at the end of the day, we wanna actually get the actual pieces of data that are coming out. And if we wanted to do that, since we know that they're already going to be value because we inserted all these keys. Let's go ahead and put an exclamation point after our retrieval of that value and go ahead and run our application again. And now you'll see that we have all the characters on the left hand side and the number of instances of them on the right hand side. So as you can see, we've succeeded. Now there's a couple different ways that we can modify this and maybe enhance it a little bit. So one of the other interesting ways that we can actually output the contents of a dictionary like this, or we can loop through or iterate over a collection this way, is to work with tuples. As we started to talk about before and use some value binding, like we did before as well. So what we're going to do here is instead of digging into the keys, I'm just going to say, for each something in my dictionary, I wanna output this. And since a dictionary is a key value pair, I can say for, and I can provide a tuple here. And I could say for the key and the name. So what I can do is I can provide basically a tuple here. And it's going to do some value binding against the key value pairs here and map each one of them into my two variables found here. So now I can print key, and I can also print name. So I can not have to worry about going into my dictionary and grabbing that particular value. And I can go ahead and run this. The other nice thing about this change is that I don't have to use the exclamation point and unwrap that optional anymore. Because the binding process is going to do that for me. So each of the valid key value pairs in this dictionary are gonna be placed correctly into this tuple that I can then use to ultimately print out. So that's pretty nice. Now the other option we have here is for this block here. And so what I wanna do is I'm actually going to comment this out and show you another option. So what you could do here as well is, instead of going through each individual character like this, we could maybe optimize this a little bit or maybe just use another technique. So once again, we're going to say for c in, and we'll once again dig into our sentence characters. Now what if we wanted to use a switch statement? So I could switch on this c, this character each time. And then I could provide cases to catch all the different variations of what we're doing here. So I could say case. Now what I can do here is remember, I can use ranges and I can use pattern matching to find where this character lives. So what I could do is I could say for the case where the value of c is between a and z. So these would be a list of valid characters or a range of valid characters that I'm gonna deal with. And so what I could do now is I could come down and continue to do my logic here. And I could do something similar to what I did before, so I could actually just grab this, so I could copy this down here, paste it in. So that will handle that basic scenario, but it's not exhaustive. And that's what you're going to see here on this particular error, it says the switch must be exhaustive. So we might need to use a default because it would be very exhausting [LAUGH] to actually have to provide the inputs for all the different case statements here. So what I could do then is I could come down and just create my, I could create a, my default here if I fixed the location of my curly brackets. I could put in a default here, and for my default, I could just say, break. And so what break is going to do is it's going to allow me to just say you know what, break out of this particular enclosure, which in this case happens to be a switch. So that means we're gonna break out of here and then just go ahead and continue processing our for loop. And now everything seems to be looking better so let's go ahead and run this. But the interesting thing here is that we're gonna miss some stuff here. So if you recall before, because my sentence has a capital T in the beginning, that's logged in here. It has its own separate character because technically speaking, in the world of ASCII characters and values, uppercase letters and lowercase letters are different from each other. So it's not getting bundled in here with a lower case t, so we missed that uppercase letter, so that could be another case here. So what I could do is, I could create another case statement down here that would handle all the upper cases. But then I'd wind up duplicating this block of code here, which is not very optimal, and I don't really wanna do that. So what I could do instead is I could come up to this case statement, cuz remember, I can provide multiple values comma separated in a single case statement and have that particular matching flow through the same logic. And so what I can do here is do another comma, and I'll say also, A through Z should fall into this category as well. So if I save this now and I run it again, now we're going to get that uppercase T in here as well. So there's a couple different ways that you can solve this particular problem. And hopefully you've come up with a solution on your own that seems somewhat similar to this, and if not, great. If you found another way to solve it, then that's absolutely fine as well. But now you've at least had a couple different opportunities to take some of the skills that you've learned and write a very simple application.