You must have come across this little fun stuff that does the rounds on Social Media that adds the alphabets in a word to get a value. This is generally used for Motivational purposes or to prove points. For example, ATTITUDE = 100, BULLSHIT = 103, you get the idea. Let us use Swift to write a simple number generator from a word
Theory of solving this
For any given word, we add each letter (non accented, just the simple English alphabet 26 characters) where the value of each is its position, A=1, B=2, and so on up to Z=26. Next we iterate through each letter in the word and add its value to the total. It’s that simple really.
First Generate a Table of values
The first thing we need is to create an table of values, which is a Dictionary. Generally developers create a literal dictionary by actually typing in the code for each of the 26 characters or creating a string containing all of the 26 characters and then splitting them and transforming them into a dictionary. That would be easy but it would not be the right way to create a dynamic table.
It would be nice if Swift had a range with characters or strings like
let letters = "a"..."z"
If it did exist, then getting the index of the character would give us the value and that’s all that we would have needed. However, that is not how things are and we cannot use this. However we can use numeric ranges. We know (if you didn’t now you do) that the lower case ‘a’ has the ascii value of 97 and lower case ‘z’ has a value of 122. So we could use the range 97…122 to generate our list.
With languages like Basic it was easy to simply use the functions ASC or CHR to get the ascii value or the character. Swift has something similar but in a little complicated way. We have Strings, then we have Characters and then there are Scalar values because you cannot initialise a Character with an integer value like let char = Character(97) It will result in an error as it requires a scalar value.
UnicodeScalar is not a variable type which means you cannot simply assign
let a:UnicodeScalar = 106
. However it is a structure, which means that it can be instantiated as let a = UnicodeScalar(106). However remember that this function returns an optional value, so you will need to unwrap that before you can use it with the Character function like let char = Character(a)
With that knowledge, let us generate our characters as,
for i in (97...122) { if let character = Character(UnicodeScalar(i)) { print(character) } }
What we get is lowercase a to z printed out onto the console.
Generating a Reference Table
With the code above, we an easily generate our table (i.e. Dictionary) so that when we try to find the value instead of using indexOf we can simply use the letter to get the value associated with that key.
var letter_values:[String:Int] = [:] for i in (97...122) { let character = Character(UnicodeScalar(i)!) letter_values[character] = i - 96 }
Evaluating the Value by summing the letters
This is easy as, all we need is a way to loop though all the letters and sum their values.
In normal cases this would be done with a for loop as
var total = 0 for letter in word { total += letter_values[letter] } print(total)
The compiler will throw an error expecting that the value be unwrapped. Since letter_values is a dictionary it returns an optional Int that requires unwrapping before it it added to the total. The simple fix that many developers do is add a ! at the end like so,
total += letter_values[letter]!
Instead, you could use a default value using the nil coalescing operator. In fact this is even proposed in the compiler fixes fix-it messages. Though this cannot be used in all cases and a better way would be to use the if let or the guard option.
The Functional way
For loops have been the solution for most things, however when you consider functional languages, for loops are replaced with other functions, namely in this case we could use the reduce function as
let total = _word.reduce(0, {return (letter_values[$1] ?? 0) + $0})
Here’s how the entire code looks
var letter_values:[String:Int] = [:] for i in (97...122) { let character = Character(UnicodeScalar(i)!) letter_values[character] = i - 96 } func getTotal(forWord word: String) -> Int { let _word = word.replacingOccurrences(of: "-", with: "").lowercased() let total = _word.reduce(0, {return (letter_values[$1] ?? 0) + $0}) return total } //example usage print(getTotal(forWord: "Attitude")) // 100 print(getTotal(forWord: "Hard-work")) // 98
There is an additional line here that we did not use earlier, all it does is remove all hypens for hypenated words like “stand-up” and convert all words into lowercase so that the code does not crash.
Closing Extensions
The chances are that you would run this code either on a Unix Box or a Mac OS X, and in either case there is a special file that can be found in the location /usr/share/dict/words this contains a list of English words. A take away challenge is to read all the words and using the getTotal function print all words that total up to 100.
Update
Majority search articles for updated code and due to the changing nature of Swift, the code keeps getting outdated quite fast. Here’s the code above updated and made even more compact
var letters:[String:Int] = [:] var arr = Array(97...122) arr.forEach { number in letters[String(UnicodeScalar(number)!)] = number - 96 } func getTotal(forWord word: String) -> Int { let total = word.lowercased().reduce(0, {(Int(letters[String($1)] ?? 0)) + $0} ) return total } let wordTotal = getTotal(forWord: "AttiTude") // 100 let wordTotal = getTotal(forWord: "Hard12w0orK") // 98
It will ignore the characters that are not between a and z.