How to get the sum out of my switch in Swift? - swift

Hi I'm stuck trying to solve this:
class Classy, to represent how classy someone or something is. "Classy". If you add fancy-looking items, "classiness" increases!
Create a method, addItem() in Classy that takes a string as input, adds it to the "items" array and updates the classiness total.
Add another method, getClassiness() that returns the "classiness" value based on the items.
The following items have classiness points associated with them:
"tophat" = 2
"bowtie" = 4
"monocle" = 5
Everything else has 0 points.
The sum is not performing correctly.
The first problem is when it falls in te default case, everything is 0, I've tried in the default with:
default:
self.classiness += 0
and I got 2 for every case
I've tried to sum the total inside in each case, and return the total but got the same result.
This is my last version
class Classy {
var items: [String]
var classiness: Int
init() {
self.items = []
self.classiness = 0
}
func addItem(_ item: String) {
var total = 0
self.items.append(item)
total += classiness
}
func getClassiness() -> Int {
switch items {
case ["tophat"]:
self.classiness = 2
case ["bowtie"]:
self.classiness = 4
case ["monocle"]:
self.classiness = 5
default:
self.classiness = 0
}
return self.classiness
}
}
let me = Classy()
print(me.getClassiness())
me.addItem("tophat")
print(me.getClassiness())
me.addItem("bowtie")
me.addItem("jacket")
me.addItem("monocle")
print(me.getClassiness()) //This would be 11

Your switch case need update, it need to loop and the case is String not Array
func getClassiness() -> Int {
var total = 0
for item in items{
switch item {
case "tophat":
total += 2
case "bowtie":
total += 4
case "monocle":
total += 5
default:
total +=0
}
}
self.classiness = total
return self.classiness
}

Related

How to go through the elements of type string

I am writing a function that should return the number of duplicates. I can’t understand how to go through the elements of type string.
func countDuplicates(_ s:String) -> Int {
var duplicates = 0
for i in s {
duplicates += 1
}
return duplicates
}
If you want to find the total sum of duplicates, the code below could be helpful:
let text = "HelloooE"
func numberOfDuplicates(_ s: String) -> Int { s.count - Set(s).count }
print(numberOfDuplicates(text)) //Answer: 3 >> which says one "l" and two "o"s are duplicated.
But if the count of duplicated characters is required, this should be the answer:
let text = "HelloooE"
func countOfDuplicateChars(_ s: String) -> Int {
s.reduce(into: [:]) {result, word in result[word, default: 0] += 1 }
.filter { $0.value > 1 }
.count
}
print(countOfDuplicateChars(text)) //Answer: 2 >> which says "l" and "o" are duplicated characters.
The function below is also return the count of duplicated characters and also is case insensitive:
let text = "HelloooE"
func countOfDuplicateChars_CS(_ s: String) -> Int {
s.lowercased()
.reduce(into: [:]) {result, word in result[word, default: 0] += 1 }
.filter { $0.value > 1 }
.count
}
print(countOfDuplicateChars(text)) //Answer: 3 >> which says "l", "o" and "e" are duplicated characters.
You can try:
func countDuplicates(_ s:String) -> Int {
return s.count - Set(s).count
}
Where:
s.count // total number of characters
Set(s).count // number of unique characters
You don't need to go through the string to find the count of the duplicates. you can just subtract the count and the not duplicated version called Set.
func countDuplicates(_ s: String) -> Int { s.count - Set(s).count) }
Note
You should consider maybe there will be more than one duplicated character there. So you need to find all duplicates for example by grouping similar and then count them:
let duplications = Dictionary(grouping: text, by: {$0}).map { [$0.key: $0.value.count] }
print(duplications)
You can get the sum of the duplicated ones if you want:
let totalDuplicationCount = dups.filter {$0.count > 1}.reduce(into: 0) { $0 += $1.count - 1 }
print(totalDuplicationCount)

Sorting arrays based on number of matches

I am trying to find the number of array item matches between multiple test arrays and one control array. After finding the number of matches, I want to append the test arrays to another array, sorted by number of matches between the control array and test array. For example, a test array with 3 matches would be at index 0, 2 matches at index 1, and so on.
let controlArray = ["milk", "honey"]
let test1 = ["honey", "water"]
let test2 = ["milk", "honey", "eggs"]
var sortedArrayBasedOnMatches = [[String]]()
/*I want to append test1 and test2 to sortedArrayBasedOnMatches based on how many items
test1 and test2 have in common with controlArray*/
/*in my example above, I would want sortedArrayBasedOnMatches to equal
[test2, test1] since test 2 has two matches and test 1 only has one*/
This can be done in a very functional and Swiftish way by writing a pipeline to process the input arrays:
let sortedArrayBasedOnMatches = [test1, test2] // initial unsorted array
.map { arr in (arr, arr.filter { controlArray.contains($0) }.count) } // making pairs of (array, numberOfMatches)
.sorted { $0.1 > $1.1 } // sorting by the number of matches
.map { $0.0 } // getting rid of the match count, if not needed
Update As #Carpsen90 pointed out, Switf 5 comes with support for count(where:) which reduces the amount of code needed in the first map() call. A solution that makes use of this could be written along the lines of
// Swift 5 already has this, let's add it for current versions too
#if !swift(>=5)
extension Sequence {
// taken from the SE proposal
// https://github.com/apple/swift-evolution/blob/master/proposals/0220-count-where.md#detailed-design
func count(where predicate: (Element) throws -> Bool) rethrows -> Int {
var count = 0
for element in self {
if try predicate(element) {
count += 1
}
}
return count
}
}
#endif
let sortedArrayBasedOnMatches = [test1, test2] // initial unsorted array
.map { (arr: $0, matchCount: $0.count(where: controlArray.contains)) } // making pairs of (array, numberOfMatches)
.sorted { $0.matchCount > $1.matchCount } // sorting by the number of matches
.map { $0.arr } // getting rid of the match count, if not needed
Another change in style from the original solution is to use labels for the tuple components, this makes the code a little bit clearer, but also a little bit more verbose.
One option is to convert each array to a Set and find the count of elements in the intersection with controlArray.
let controlArray = ["milk", "honey"]
let test1 = ["honey", "water"]
let test2 = ["milk", "honey", "eggs"]
var sortedArrayBasedOnMatches = [ test1, test2 ].sorted { (arr1, arr2) -> Bool in
return Set(arr1).intersection(controlArray).count > Set(arr2).intersection(controlArray).count
}
print(sortedArrayBasedOnMatches)
This will cover the case where elements are not unique in your control array(such as milk, milk, honey...) and with any number of test arrays.
func sortedArrayBasedOnMatches(testArrays:[[String]], control: [String]) -> [[String]]{
var final = [[String]].init()
var controlDict:[String: Int] = [:]
var orderDict:[Int: [[String]]] = [:] // the value is a array of arrays because there could be arrays with the same amount of matches.
for el in control{
if controlDict[el] == nil{
controlDict[el] = 1
}
else{
controlDict[el] = controlDict[el]! + 1
}
}
for tArr in testArrays{
var totalMatches = 0
var tDict = controlDict
for el in tArr{
if tDict[el] != nil && tDict[el] != 0 {
totalMatches += 1
tDict[el] = tDict[el]! - 1
}
}
if orderDict[totalMatches] == nil{
orderDict[totalMatches] = [[String]].init()
}
orderDict[totalMatches]?.append(tArr)
}
for key in Array(orderDict.keys).sorted(by: >) {
for arr in orderDict[key]! {
final.append(arr)
}
}
return final
}

Loops in didSet

We came across this odd behaviour when using loops in a didSet. The idea was that we have a data type with a tree structure and in each element we wanted to store the level that item is on. So in the didSet of the level attribute we would also set the level attribute of the children. However we realised that this does only work if one uses forEach and not when using for .. in. Here a short example:
class Item {
var subItems: [Item] = []
var depthA: Int = 0 {
didSet {
for item in subItems {
item.depthA = depthA + 1
}
}
}
var depthB: Int = 0 {
didSet {
subItems.forEach({ $0.depthB = depthB + 1 })
}
}
init(depth: Int) {
self.depthA = 0
if depth > 0 {
for _ in 0 ..< 2 {
subItems.append(Item(depth: depth - 1))
}
}
}
func printDepths() {
print("\(depthA) - \(depthB)")
subItems.forEach({ $0.printDepths() })
}
}
let item = Item(depth: 3)
item.depthA = 0
item.depthB = 0
item.printDepths()
When I run this I get the following output:
0 - 0
1 - 1
0 - 2
0 - 3
0 - 3
0 - 2
0 - 3
0 - 3
1 - 1
0 - 2
0 - 3
0 - 3
0 - 2
0 - 3
0 - 3
It seems like it will not call the didSet of the subItems attribute when it's called from an for .. in loop. Does anyone know why this is the case?
UPDATE:
The problem is not that the didSet is not called from the init. We change the attribute afterwards (see last 4 lines of code) and only one of the two depth attribute will propagate the new value to the children
If you use defer, for updating any optional properties or further updating non-optional properties that you've already initialized and after you've called any super init methods, then your willSet, didSet, etc. will be called.
for item in subItems {
defer{
item.depthA = depthA + 1
}
}
When you use the forEach it makes kindOf "contract" with the elements and because it's an instance method unlike for .. in loop, it triggers the didSet of the variable. The above case applies where we use the loop, we have to trigger the didSet manually
This Solves the problem I think. Hope it helps!!
It seems that in the init, didSet is not called.
Tried this line on the Swift REPL
class A { var a: Int { didSet { print("A") } }; init() { a = 5 } }
Then called A()
didSet is NOT called
But
A().a = 7
Found that didSet is called
So, the solution is to make a function (prefered to be final), that makes your effect you need to put in didSet. Then call it both from didSet and from init. You can put this in your class.
final func updateA() {
// Do your "didSet" here
}
var a: Int {
didSet {
updateA()
}
}
init() {
a = 5
updateA()
}
So in your case:
func updateDepthA() {
for item in subItems {
item.depthA = depthA + 1
}
}
var depthA: Int = 0 {
didSet {
updateDepthA()
}
}
...
init(depth: Int) {
self.depthA = 0
updateDepthA()
...

How to compare characters in Swift efficiently

I have a function in Swift that computes the hamming distance of two strings and then puts them into a connected graph if the result is 1.
For example, read to hear returns a hamming distance of 2 because read[0] != hear[0] and read[3] != hear[3].
At first, I thought my function was taking a long time because of the quantity of input (8,000+ word dictionary), but I knew that several minutes was too long. So, I rewrote my same algorithm in Java, and the computation took merely 0.3s.
I have tried writing this in Swift two different ways:
Way 1 - Substrings
extension String {
subscript (i: Int) -> String {
return self[Range(i ..< i + 1)]
}
}
private func getHammingDistance(w1: String, w2: String) -> Int {
if w1.length != w2.length { return -1 }
var counter = 0
for i in 0 ..< w1.length {
if w1[i] != w2[i] { counter += 1 }
}
return counter
}
Results: 434 seconds
Way 2 - Removing Characters
private func getHammingDistance(w1: String, w2: String) -> Int {
if w1.length != w2.length { return -1 }
var counter = 0
var c1 = w1, c2 = w2 // need to mutate
let length = w1.length
for i in 0 ..< length {
if c1.removeFirst() != c2.removeFirst() { counter += 1 }
}
return counter
}
Results: 156 seconds
Same Thing in Java
Results: 0.3 seconds
Where it's being called
var graph: Graph
func connectData() {
let verticies = graph.canvas // canvas is Array<Node>
// Node has key that holds the String
for vertex in 0 ..< verticies.count {
for compare in vertex + 1 ..< verticies.count {
if getHammingDistance(w1: verticies[vertex].key!, w2: verticies[compare].key!) == 1 {
graph.addEdge(source: verticies[vertex], neighbor: verticies[compare])
}
}
}
}
156 seconds is still far too inefficient for me. What is the absolute most efficient way of comparing characters in Swift? Is there a possible workaround for computing hamming distance that involves not comparing characters?
Edit
Edit 1: I am taking an entire dictionary of 4 and 5 letter words and creating a connected graph where the edges indicate a hamming distance of 1. Therefore, I am comparing 8,000+ words to each other to generate edges.
Edit 2: Added method call.
Unless you chose a fixed length character model for your strings, methods and properties such as .count and .characters will have a complexity of O(n) or at best O(n/2) (where n is the string length). If you were to store your data in an array of character (e.g. [Character] ), your functions would perform much better.
You can also combine the whole calculation in a single pass using the zip() function
let hammingDistance = zip(word1.characters,word2.characters)
.filter{$0 != $1}.count
but that still requires going through all characters of every word pair.
...
Given that you're only looking for Hamming distances of 1, there is a faster way to get to all the unique pairs of words:
The strategy is to group words by the 4 (or 5) patterns that correspond to one "missing" letter. Each of these pattern groups defines a smaller scope for word pairs because words in different groups would be at a distance other than 1.
Each word will belong to as many groups as its character count.
For example :
"hear" will be part of the pattern groups:
"*ear", "h*ar", "he*r" and "hea*".
Any other word that would correspond to one of these 4 pattern groups would be at a Hamming distance of 1 from "hear".
Here is how this can be implemented:
// Test data 8500 words of 4-5 characters ...
var seenWords = Set<String>()
var allWords = try! String(contentsOfFile: "/usr/share/dict/words")
.lowercased()
.components(separatedBy:"\n")
.filter{$0.characters.count == 4 || $0.characters.count == 5}
.filter{seenWords.insert($0).inserted}
.enumerated().filter{$0.0 < 8500}.map{$1}
// Compute patterns for a Hamming distance of 1
// Replace each letter position with "*" to create patterns of
// one "non-matching" letter
public func wordH1Patterns(_ aWord:String) -> [String]
{
var result : [String] = []
let fullWord : [Character] = aWord.characters.map{$0}
for index in 0..<fullWord.count
{
var pattern = fullWord
pattern[index] = "*"
result.append(String(pattern))
}
return result
}
// Group words around matching patterns
// and add unique pairs from each group
func addHamming1Edges()
{
// Prepare pattern groups ...
//
var patternIndex:[String:Int] = [:]
var hamming1Groups:[[String]] = []
for word in allWords
{
for pattern in wordH1Patterns(word)
{
if let index = patternIndex[pattern]
{
hamming1Groups[index].append(word)
}
else
{
let index = hamming1Groups.count
patternIndex[pattern] = index
hamming1Groups.append([word])
}
}
}
// add edge nodes ...
//
for h1Group in hamming1Groups
{
for (index,sourceWord) in h1Group.dropLast(1).enumerated()
{
for targetIndex in index+1..<h1Group.count
{ addEdge(source:sourceWord, neighbour:h1Group[targetIndex]) }
}
}
}
On my 2012 MacBook Pro, the 8500 words go through 22817 (unique) edge pairs in 0.12 sec.
[EDIT] to illustrate my first point, I made a "brute force" algorithm using arrays of characters instead of Strings :
let wordArrays = allWords.map{Array($0.unicodeScalars)}
for i in 0..<wordArrays.count-1
{
let word1 = wordArrays[i]
for j in i+1..<wordArrays.count
{
let word2 = wordArrays[j]
if word1.count != word2.count { continue }
var distance = 0
for c in 0..<word1.count
{
if word1[c] == word2[c] { continue }
distance += 1
if distance > 1 { break }
}
if distance == 1
{ addEdge(source:allWords[i], neighbour:allWords[j]) }
}
}
This goes through the unique pairs in 0.27 sec. The reason for the speed difference is the internal model of Swift Strings which is not actually an array of equal length elements (characters) but rather a chain of varying length encoded characters (similar to the UTF model where special bytes indicate that the following 2 or 3 bytes are part of a single character. There is no simple Base+Displacement indexing of such a structure which must always be iterated from the beginning to get to the Nth element.
Note that I used unicodeScalars instead of Character because they are 16 bit fixed length representations of characters that allow a direct binary comparison. The Character type isn't as straightforward and take longer to compare.
Try this:
extension String {
func hammingDistance(to other: String) -> Int? {
guard self.characters.count == other.characters.count else { return nil }
return zip(self.characters, other.characters).reduce(0) { distance, chars in
distance + (chars.0 == chars.1 ? 0 : 1)
}
}
}
print("read".hammingDistance(to: "hear")) // => 2
The following code executed in 0.07 secounds for 8500 characters:
func getHammingDistance(w1: String, w2: String) -> Int {
if w1.characters.count != w2.characters.count {
return -1
}
let arr1 = Array(w1.characters)
let arr2 = Array(w2.characters)
var counter = 0
for i in 0 ..< arr1.count {
if arr1[i] != arr2[i] { counter += 1 }
}
return counter
}
After some messing around, I found a faster solution to #Alexander's answer (and my previous broken answer)
extension String {
func hammingDistance(to other: String) -> Int? {
guard !self.isEmpty, !other.isEmpty, self.characters.count == other.characters.count else {
return nil
}
var w1Iterator = self.characters.makeIterator()
var w2Iterator = other.characters.makeIterator()
var distance = 0;
while let w1Char = w1Iterator.next(), let w2Char = w2Iterator.next() {
distance += (w1Char != w2Char) ? 1 : 0
}
return distance
}
}
For comparing strings with a million characters, on my machine it's 1.078 sec compared to 1.220 sec, so roughly a 10% improvement. My guess is this is due to avoiding .zip and the slight overhead of .reduce and tuples
As others have noted, calling .characters repeatedly takes time. If you convert all of the strings once, it should help.
func connectData() {
let verticies = graph.canvas // canvas is Array<Node>
// Node has key that holds the String
// Convert all of the keys to utf16, and keep them
let nodesAsUTF = verticies.map { $0.key!.utf16 }
for vertex in 0 ..< verticies.count {
for compare in vertex + 1 ..< verticies.count {
if getHammingDistance(w1: nodesAsUTF[vertex], w2: nodesAsUTF[compare]) == 1 {
graph.addEdge(source: verticies[vertex], neighbor: verticies[compare])
}
}
}
}
// Calculate the hamming distance of two UTF16 views
func getHammingDistance(w1: String.UTF16View, w2: String.UTF16View) -> Int {
if w1.count != w2.count {
return -1
}
var counter = 0
for i in w1.startIndex ..< w1.endIndex {
if w1[i] != w1[i] {
counter += 1
}
}
return counter
}
I used UTF16, but you might want to try UTF8 depending on the data. Since I don't have the dictionary you are using, please let me know the result!
*broken*, see new answer
My approach:
private func getHammingDistance(w1: String, w2: String) -> Int {
guard w1.characters.count == w2.characters.count else {
return -1
}
let countArray: Int = w1.characters.indices
.reduce(0, {$0 + (w1[$1] == w2[$1] ? 0 : 1)})
return countArray
}
comparing 2 strings of 10,000 random characters took 0.31 seconds
To expand a bit: it should only require one iteration through the strings, adding as it goes.
Also it's way more concise 🙂.

Extracting values from a reversed linked list

Question:
You have two numbers represented by a linked list, where each node contains a single digit. The digits are stored in reverse order, such that the 1 's digit is at the head of the list. Write a function that adds the two numbers and returns the sum as a linked list.
An Example:
Input: (7-> 1 -> 6) + (5 -> 9 -> 2).
That is: 617 + 295.
Output: 2 -> 1 -> 9.
That is: 912.
In order to begin with this question, I first created a class that would define what a linked list:
Step 1: Defining the linked list
class Node: CustomStringConvertible{
var value: Int
var next: Node?
var description: String{
if next != nil {
return "\(value) -> \(next!)"
}
else{
return "\(value) -> \(next)"
}
}
init(value: Int) {
self.value = value
}
}
Step: 2 - Generated the linked list, from user input of integer values
func generateList (num: Int) -> Node {
var stringNum = Array(String(num).characters)
let head = Node.init(value:Int(String(stringNum.first!))!)
var current = head
for i in 1..<stringNum.count{
let num = Int(String(stringNum[i]))
current.next = Node.init(value: num!)
current = current.next!
}
return head
}
let list = generateList(num: 716)
// The following prints out: 7 -> 1 -> 6 -> nil
Then I proceeded over to reverse the linked list using following function.
Step 3: Reverse the linked list
func reverseLinkedList (head: Node?) -> Node?{
var current = head
var prev: Node?
var next: Node?
while current != nil {
next = current?.next
current?.next = prev
prev = current
current = next
}
return prev
}
let reversedList = reverseLinkedList(head: list)
// The following prints out is: 6 -> 1 -> 7 -> nil
Step 4: The idea behind this step is to extract the values on each of the nodes, cast them as a string and then concatenate them to a string variable and then lastly cast the string value into an Int and then use that Int value and eventually add them.
func getValuesFrom (head: Node?) -> Int {
var string = ""
var current = head
while current != nil {
var stringVal = String(describing: current?.value)
string += stringVal
current = current?.next
}
return Int(string)!
}
Here is where I am having a problem:
When I plug in the following into this function like so:
getValuesFrom(head: reversedList)
I get the following error:
fatal error: unexpectedly found nil while unwrapping an Optional value
And I can't seem to figure out why I having a problem and would really appreciate any sort of insight.
There is no need to convert back and forth between String and the linked list, except to print it for results. This is done simply like this:
class Node {
var value: Int
var next: Node?
// init and generator can be the same method
init(value: Int) {
// store ones place and divide by 10
self.value = value % 10
var nextValue = value / 10
// set up for loop
var currentNode = self
while nextValue > 0 {
// create a new Node
// store ones place and divide by 10
let next = Node(value: nextValue % 10)
nextValue /= 10
// make the new Node the next Node
currentNode.next = next
// set up for next iteration
currentNode = next
}
}
}
// make the list printable
extension Node: CustomStringConvertible {
var description: String{
if let next = next {
return "\(value) -> \(next)"
}
else {
return "\(value) -> nil"
}
}
}
Now you can do:
print(Node(value: 671)) // prints "7 -> 1 -> 6 -> nil"
There is also no need to reverse the lists, given your question.
To sum the lists you can do just as you've said, convert to an Int, add them, then generate a new list:
extension Node {
func toValue() -> Int {
var place = 10
var current = self
// add each value and multiply by the place value
// first is 1, second 10, third 100, etc.
var result = current.value
while let next = current.next {
result += next.value * place
place *= 10
current = next
}
return result
}
}
Then all you need is to overload the addition operator...
func +(lhs: Node, rhs: Node) -> Node {
return Node(value: lhs.toValue() + rhs.toValue())
}
and test...
let first = Node(value: 617)
let second = Node(value: 295)
print(first)
print(second)
print(first + second)
Result:
7 -> 1 -> 6 -> nil
5 -> 9 -> 2 -> nil
2 -> 1 -> 9 -> nil