Swift Anagram checker
Asked Answered
M

13

10

I am attempting to build an anagram checker for swift. This is my code. In case you don't know an anagram checker checks if two strings have the same characters in them but, order does not matter.

func checkForAnagram(#firstString: String, #secondString: String) -> Bool {
    var firstStringArray: [Character] = []
    var secondStringArray: [Character] = []
    /* if case matters delete the next four lines
    and make sure your variables are not constants */
    var first = firstString
    var second = secondString
    first = first.lowercaseString
    second = second.lowercaseString
    for charactersOne in first {
        firstStringArray += [charactersOne]
    }
    for charactersTwo in second {
        secondStringArray += [charactersTwo]
    }
    if firstStringArray.count != secondStringArray.count {
        return false
    } else {
        for elements in firstStringArray {
            if secondStringArray.contains(elements){
                return true
            } else {
                return false
            }
        }

}
}


var a = "Hello"
var b = "oellh"
var c = "World"
checkForAnagram(firstString: a, secondString: b)

I am getting an error message of.

'[Character]' does not have a member 'contains'
Mcglone answered 25/8, 2015 at 16:54 Comment(2)
Which Xcode version are you using? The question is tagged swift2, but it seems that you are using Xcode 6. Compare #24102524. – In any case, there is a flaw in your logic :)Tetrapody
I was using 6.4. When I switch to the 7 beta 5 on both for...in loops I get 'String' does not have member 'Generator' thrown as an error. @MartinRMcglone
R
21

You should try

func checkForAnagram(firstString firstString: String, secondString: String) -> Bool {
    return firstString.lowercaseString.characters.sort() == secondString.lowercaseString.characters.sort()
}
Rhachis answered 25/8, 2015 at 17:7 Comment(4)
In Xcode 6.4. this code throws an error. In the 7 beta 5 version this is a much more compact version of my code and the correct answer. Thank you for your help @RhachisMcglone
in 6.4 this function could be return Array(firstString.lowercaseString).sorted(>) == Array(secondString.lowercaseString).sorted(>)Rhachis
characters currently not used. so can i use count?Woven
what if both are empty?Hoxsie
B
29

The accepted answer is compact and elegant, but very inefficient if compared to other solutions.

I'll now propose and discuss the implementation of a few variants of anagram checker. To measure performance, I'll use the different variants to find the anagrams of a given word out of an array of 50,000+ words.

// Variant 1: Sorting of Character
// Measured time: 30.46 s
func anagramCheck1(a: String, b: String) -> Bool {
    return a.characters.sorted() == b.characters.sorted()
}

This is essentially the solution of the accepted answer, written in Swift 3 syntax. It's very slow because Swift's String, unlike NSString, is based on Character, which handles Unicode characters properly.

A more efficient solution exploits the NSCountedSet class, which allows us to represent a string as a set of characters, each with its own count. Two strings are anagrams if they map to the same NSCountedSet. Note: checking string lengths as a precondition makes the implementation always more efficient.

// Variant 2: NSCountedSet of Character
// Measured time: 4.81 s
func anagramCheck2(a: String, b: String) -> Bool {
    guard a.characters.count == b.characters.count else { return false }
    let aSet = NSCountedSet()
    let bSet = NSCountedSet()
    for c in a.characters {
        aSet.add(c)
    }
    for c in b.characters {
        bSet.add(c)
    }
    return aSet == bSet
}

Better but not excellent. Here, one of the "culprits" is the use of the native Swift Character type (from Swift's String). Moving back to good old Objective-C types (NSString and unichar) makes things more efficient.

// Variant 3: NSCountedSet of unichar
// Measured time: 1.31 s
func anagramCheck3(a: String, b: String) -> Bool {
    let aString = a as NSString
    let bString = b as NSString
    let length = aString.length
    guard length == bString.length else { return false }
    let aSet = NSCountedSet()
    let bSet = NSCountedSet()
    for i in 0..<length {
        aSet.add(aString.character(at: i))
        bSet.add(bString.character(at: i))
    }
    return aSet == bSet
}

Using NSCountedSet is fine, but before we compare two NSCountedSet objects, we fully populate them. A useful alternative is to fully populate the NSCountedSet for only one of the two strings, and then, while we populate the NSCountedSet for the other string, we fail early if the other string contains a character that is not found in the NSCountedSet of the first string.

// Variant 4: NSCountedSet of unichar and early exit
// Measured time: 1.07 s
func anagramCheck4(a: String, b: String) -> Bool {
    let aString = a as NSString
    let bString = b as NSString
    let length = aString.length
    guard length == bString.length else { return false }
    let aSet = NSCountedSet()
    let bSet = NSCountedSet()
    for i in 0..<length {
        aSet.add(aString.character(at: i))
    }
    for i in 0..<length {
        let c = bString.character(at: i)
        if bSet.count(for: c) >= aSet.count(for: c) {
            return false
        }
        bSet.add(c)
    }
    return true
}

This is about the best timing we are going to get (with Swift). However, for completeness, let me discuss one more variant of this kind.

The next alternative exploits a Swift Dictionary of type [unichar: Int] to store the number of repetitions for each character instead of NSCountedSet. It's slightly slower than the previous two variants, but we can reuse it later to obtain a faster implementation.

// Variant 5: counting repetitions with [unichar:Int]
// Measured time: 1.36
func anagramCheck5(a: String, b: String) -> Bool {
    let aString = a as NSString
    let bString = b as NSString
    let length = aString.length
    guard length == bString.length else { return false }
    var aDic = [unichar:Int]()
    var bDic = [unichar:Int]()
    for i in 0..<length {
        let c = aString.character(at: i)
        aDic[c] = (aDic[c] ?? 0) + 1
    }
    for i in 0..<length {
        let c = bString.character(at: i)
        let count = (bDic[c] ?? 0) + 1
        if count > aDic[c] ?? 0 {
            return false
        }
        bDic[c] = count
    }
    return true
}

Note that a vanilla Objective-C implementation using NSCountedSet, corresponding to Variant 3, is faster than all the previous versions by a rather large margin.

// Variant 6: Objective-C and NSCountedSet
// Measured time: 0.65 s
- (BOOL)anagramChecker:(NSString *)a with:(NSString *)b {
    if (a.length != b.length) {
        return NO;
    }
    NSCountedSet *aSet = [[NSCountedSet alloc] init];
    NSCountedSet *bSet = [[NSCountedSet alloc] init];
    for (int i = 0; i < a.length; i++) {
        [aSet addObject:@([a characterAtIndex:i])];
        [bSet addObject:@([b characterAtIndex:i])];
    }
    return [aSet isEqual:bSet];
}

Another way we can improve upon the previous attempts is to observe that, if we need to find the anagram of a given word, we might as well consider that word as fixed, and thus we could build the corresponding structure (NSCountedSet, Dictionary, ...) for that word only once.

// Finding all the anagrams of word in words
// Variant 7: counting repetitions with [unichar:Int]
// Measured time: 0.58 s
func anagrams(word: String, from words: [String]) -> [String] {
    let anagrammedWord = word as NSString
    let length = anagrammedWord.length
    var aDic = [unichar:Int]()
    for i in 0..<length {
        let c = anagrammedWord.character(at: i)
        aDic[c] = (aDic[c] ?? 0) + 1
    }
    let foundWords = words.filter {
        let string = $0 as NSString
        guard length == string.length else { return false }
        var bDic = [unichar:Int]()
        for i in 0..<length {
            let c = string.character(at: i)
            let count = (bDic[c] ?? 0) + 1
            if count > aDic[c] ?? 0 {
                return false
            }
            bDic[c] = count
        }
        return true
    }
    return foundWords
}

Now, in the previous variant we have counted with a [unichar:Int] Dictionary. This proves slightly more efficient than using an NSCountedSet of unichar, either with early exit (0.60 s) or without (0.87 s).

Burrton answered 3/11, 2016 at 11:44 Comment(2)
You can make your function @inline(__always), I think it will improve performance. Anyway, very nice investigation and awesome answer.Symer
Also changing for to while will improve performance too. for under the hood using sequence protocol. With while we will avoid excessive functions calls.Symer
R
21

You should try

func checkForAnagram(firstString firstString: String, secondString: String) -> Bool {
    return firstString.lowercaseString.characters.sort() == secondString.lowercaseString.characters.sort()
}
Rhachis answered 25/8, 2015 at 17:7 Comment(4)
In Xcode 6.4. this code throws an error. In the 7 beta 5 version this is a much more compact version of my code and the correct answer. Thank you for your help @RhachisMcglone
in 6.4 this function could be return Array(firstString.lowercaseString).sorted(>) == Array(secondString.lowercaseString).sorted(>)Rhachis
characters currently not used. so can i use count?Woven
what if both are empty?Hoxsie
T
2
// This answer also would work
// Convert your parameters on Array, then sorted them and compare them
func ana(str1: String, str2: String)->Bool{

    let a = Array(str1)
    let b = Array(str2)

    if a.sorted() == b.sorted() {
        return true
    }
    return false
}
Touchline answered 13/6, 2018 at 13:25 Comment(0)
T
2
func checkAnagrams(str1: String, str2: String) -> Bool {
        guard str1.count == str2.count else { return false }
        var dictionary = Dictionary<Character, Int>()
        for index in 0..<str1.count {
            let value1 = str1[str1.index(str1.startIndex, offsetBy: index)]
            let value2 = str2[str2.index(str2.startIndex, offsetBy: index)]
            dictionary[value1] = (dictionary[value1] ?? 0) + 1
            dictionary[value2] = (dictionary[value2] ?? 0) - 1
        }
        return !dictionary.contains(where: {(_, value) in
            return value != 0
        })
}

Time complexity - O(n)

Theme answered 15/4, 2019 at 15:50 Comment(0)
T
1
// Make sure name your variables correctly so you won't confuse
// Mutate the constants parameter, lowercase to handle capital letters and the sorted them to compare both. Finally check is there are equal return true or false.

func anagram(str1: String, srt2: String)->Bool{

    let string1 = str1.lowercased().sorted()
    let string2 = srt2.lowercased().sorted()

    if string1 == string2 {
        return true
    }
    return false
}
Touchline answered 13/6, 2018 at 13:1 Comment(0)
E
1

Don't forget whitespaces

     func isAnagram(_ stringOne: String, stringTwo: String) -> Bool {
         return stringOne.lowercased().sorted().filter { $0 != " "} stringTwo.lowercased().sorted().filter { $0 != " "}

}

Eleanore answered 17/2, 2021 at 3:11 Comment(0)
T
0

We can use dictionary to construct a new data structure container. Then compare the value by key/character of the string.

func anagram(str1: String, str2 : String) -> Bool {
   var dict1 = [Character: Int]()
   var dict2 = [Character:Int]()

   for i in str1 {
       if let count = dict1[i] {
           dict1[i] = count + 1
       } else {
           dict1[i] = 1
       }
   }

   for j in str2 {
       if let count = dict2[j] {
          dict2[j] = count + 1
       } else {
          dict2[j] = 1
       }
    }
    return dict1 == dict2 ? true : false
}

// input -> "anna", "aann"
// The count will look like: 
// ["a": 2, "n": 2] & ["a": 2, "n": 2] 
// then return true
Touchline answered 14/3, 2019 at 13:42 Comment(0)
M
0

Swift 4.1 Function will give you 3 questions answer for Anagram :-

1. Input Strings (a,b) are Anagram ? //Bool

2. If not an Anagram then number of count require to change Characters in strings(a,b) to make them anagram ? // Int

3. If not an Anagram then list of Characters needs to be change in strings(a,b) to make them anagram ? // [Character]

STEP 1:- Copy and Paste below function in to your required class:-

  //MARK:-  Anagram checker
    func anagramChecker(a:String,b:String) -> (Bool,Int,[Character]) {
        var aCharacters = Array(a)
        var bCharacters = Array(b)
        var count = 0
        var isAnagram = true
        var replacementRequiredWords:[Character] = [Character]()
        if aCharacters.count == bCharacters.count {
            let listA = aCharacters.filter { !bCharacters.contains($0) }
            for i in 0 ..< listA.count {
                if !replacementRequiredWords.contains(listA[i]) {
                    count = count + 1
                    replacementRequiredWords.append(listA[i])
                    isAnagram = false
                }
            }
            let listB = bCharacters.filter { !aCharacters.contains($0) }
            for i in 0 ..< listB.count {
                if !replacementRequiredWords.contains(listB[i]) {
                    count = count + 1
                    replacementRequiredWords.append(listB[i])
                     isAnagram = false
                }
            }
        }else{
            //cant be an anagram
            count = -1
        }
         return (isAnagram,count,replacementRequiredWords)
    }

STEP 2 :- Make two Input Strings for test

// Input Strings
var a = "aeb"
var b = "abs"

STEP 3:- Print results :-

print("isAnagram : \(isAnagram(a: a, b: b).0)")

print("number of count require to change strings in anagram  : \(isAnagram(a: a, b: b).1)")//-1 will come in case of cant be a Anagram

print("list of  Characters needs to be change : \(isAnagram(a: a, b: b).2)")

Results of above exercise:-

isAnagram : false
number of count require to change strings in anagram  : 2
list of  Characters needs to be change : ["e", "s"]

Hope this 10 minutes exercise will give some support to my Swift family for solving Anagram related problems easily. :)

Miculek answered 30/5, 2019 at 19:46 Comment(0)
H
0

Another easy that I just realise doing an Anagram function in Swift 5.X

func checkForAnagram(firstString firstString: String, secondString: String) -> Bool {
    return !firstString.isEmpty && firstString.sorted() == secondString.sorted()
}
Hoxsie answered 19/11, 2020 at 10:11 Comment(0)
R
0
class Solution {
    func isAnagram(_ s: String, _ t: String) -> Bool {
        guard s.count == t.count else { return false }
        let dictS = s.reduce(into: [Character: Int]()) { $0[$1, default: 0] += 1 }
        let dictT = t.reduce(into: [Character: Int]()) { $0[$1, default: 0] += 1 }
        
        for letter in s {
            if let count = dictS[letter] {
                guard count == dictT[letter] else { return false }
            }
        }
        
        return true
    }
}
Repossess answered 31/8, 2022 at 5:41 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Baptlsta
F
0

As described in the code, I use dictionary with chars as keys and the count of this char in the String as value. And I use this count to check if the two strings are Anagram.

The Time complexity of this solution is O(n)

func isAnagram(a: String, b: String) -> Bool {
    
    guard a.count == b.count else { return false }
    
    var dic: [Character: Int] = [:]
    
    for char in a.lowercased() {
        dic[char, default: 0] += 1
    }
    
    for char in b.lowercased() {
        dic[char, default: 0] -= 1
    }
    
    for (_, value) in dic {
        if value != 0 { return false }
    }
    
    return true 
}
Fabien answered 9/6, 2023 at 14:5 Comment(1)
I’m pretty sure you need to test for value != 0 rather than value > 0 to handle the case where the second string contains more occurrences of a character than the first string. When that happens, the dictionary will have a negative value.Landlubber
P
-1

Check two strings are anagram using inout method in Swift

func checkAnagramString(str1: inout String, str2: inout String)-> Bool{

  var result:Bool = false
  str1 = str1.lowercased().trimmingCharacters(in: .whitespace)
  str2 = str2.lowercased().trimmingCharacters(in: .whitespaces)

  if (str1.count != str2.count) {
     return result
  }

  for c in str1 {
     if str2.contains(c){
         result = true
    }
    else{
        result = false
        return result
    }
  }
   return result
}

Call function to check strings are anagram or not

 var str1 = "tommarvoloriddle"
 var str2 = "iamlordvoldemort"

 print(checkAnagramString(str1: &str1, str2: &str2)) //Output = true.
Pluperfect answered 18/7, 2020 at 15:9 Comment(1)
It is wrong solution var str1 = "aaba" var str2 = "aabb" try it with these string it should return false but it returns falseDuncandunce
S
-2
    func isAnagram(word1: String, word2: String) -> Bool {
        let set1 = Set(word1)
        let set2 = Set(word2)
        return set1 == set2
    }

or

    func isAnagram(word1: String,word2: String) -> Bool {
        return word1.lowercased().sorted() == word2.lowercased().sorted()
    }
Studer answered 19/4, 2019 at 22:30 Comment(1)
The first one won't work, try with "settt" : "tes", it will return true. and is not an Anagram, since Set doesn't have duplicated itemsHoxsie

© 2022 - 2024 — McMap. All rights reserved.