How to Compare Two Strings to Check If They Have Same Characters Swift 4

How to compare two Strings to check if they have same Characters Swift 4?

If “multiplicity” counts (i.e. "aab" has the same characters as "aba",
but not the same characters as "abb"), then

s1.sorted() == s2.sorted()

does the trick. If you don't care about the multiplicity, then just

Set(s1) == Set(s2)

Example:

let firstArray = ["sumit", "ambuj", "abhi", "aba"]
let secondArray = ["mitsu", "jumba", "hibb", "abb"]

for (s1, s2) in zip(firstArray, secondArray) {
print(s1.sorted() == s2.sorted())
}

// true, true, false, false

for (s1, s2) in zip(firstArray, secondArray) {
print(Set(s1) == Set(s2))
}

// true, true, false, true

For longer strings it might be more efficient to maintain a
dictionary with the number of occurrences of each character in
a string (similar to a NSCountedSet):

func characterCounts(_ s: String) -> [Character: Int] {
return s.reduce(into: [:], { $0[$1, default: 0] += 1 })
}

and then compare the dictionaries:

characterCounts(s1) == characterCounts(s2)

Swift get the different characters in two strings

UPDATE for Swift 4.0 or greater

Because of the change of String to also be a collection, this answer can be shortened to

let difference = zip(x, y).filter{ $0 != $1 }

For Swift version 3.*

let difference = zip(x.characters, y.characters).filter{$0 != $1}

Sample Image

How to compare two strings in swift?

You can try

if !isText.contains("Schreibe etwas...") {
shareButton.isEnabled = true
abortButton.isEnabled = true
}

How to compare string equality which contains unicode characters in swift?

Doplňky k Apple TV string looks like that it comes from the Apple website. When I checked it on their website, this string, it contains NO-BREAK SPACE (U+00A0) between Apple & TV. It's a white space character, but it doesn't equal to a normal SPACE (U+0020).

"Doplňky k Apple\u{00a0}TV" == "Doplňky k Apple TV" // false

First thing to specify - does it matter? Should we treat it as equal or not?

Then you have Apple TVtilbehør & *Apple*TV*Tilbehør* strings. Is it intentional typo? Or Apple TVtilbehør should be Apple TV Tilbehør? Let's assume it's intentional typo to test your comparison.

Next, these * (at the beginning/end) in the *Apple*TV*Tilbehør* string are ...? Second thing to specify - should we ignore them? Do they represent a whitespace?

Next thing is the Unicode equivalence. How would you like to compare these two strings? Swift helps you here (source):

Comparing strings for equality using the equal-to operator (==) or a relational operator (like < or >=) is always performed using Unicode canonical representation. As a result, different representations of a string compare as being equal.

"Cafe\u{301}" == "Café" // true

What about other countries? Like Germany where Straße equals to Strasse? Third thing to specify - how we should treat these strings?

As you can see, there's a lot of things one should think about. Do you have a specification? Follow it. No specification? Your algorithm will stop working sooner or later.

Playground

I took the liberty to specify all these things by myself:

  • All whitespaces do equal
  • * at the beginning/end doesn't matter (ignored)
  • Straße does not equal to Strasse

Sample code:

import Foundation

let json = [
// U+00A0 is NO-BREAK SPACE which looks like a normal space (U+0020)
"cz": "Doplňky k Apple\u{00a0}TV",
"dk": "Apple TV Tilbehør",
"en": "Hello",
"de": "Straße",
"fr": "Expos\u{00E9}" // Exposé
]

let plist = [
"cz": "Doplňky*k*Apple*TV",
"dk": "*Apple*TV*Tilbehør*",
"es": "Hola",
"de": "Strasse",
"fr": "Expose\u{0301}" // Exposé
]

let jsonKeys = Set(json.keys)
let plistKeys = Set(plist.keys)
let commonKeys = jsonKeys.intersection(plistKeys)
let keysMissingInJson = plistKeys.subtracting(jsonKeys)
let keysMissingInPlist = jsonKeys.subtracting(plistKeys)

print("Languages missing in JSON: \(keysMissingInJson.count)")
keysMissingInJson.forEach { key in
print(" - \(key)")
}

print("Languages missing in PLIST: \(keysMissingInPlist.count)")
keysMissingInPlist.forEach { key in
print(" - \(key)")
}

let differentValueKeys: [String] = commonKeys.compactMap { key in
guard let initialJsonValue = json[key], let initialPlistValue = plist[key] else {
fatalError("Fix commonKeys")
}

// Replace all whitespace characters with a normal space
let jsonValue = String(
initialJsonValue.map { $0.isWhitespace ? " " : $0 }
)

let plistValue = initialPlistValue
// Replace all * with a normal whitespace
.replacingOccurrences(of: "*", with: " ")
// Trim all whitespace characters from the beginning/end
.trimmingCharacters(in: .whitespaces)

return jsonValue == plistValue ? nil : key
}

print("Different values: \(differentValueKeys.count)")
differentValueKeys.forEach { key in
print(" - \(key): JSON(\(json[key]!)) PLIST(\(plist[key]!))")
}

Output:

Languages missing in JSON: 1
- es
Languages missing in PLIST: 1
- en
Different values: 1
- de: JSON(Straße) PLIST(Strasse)

How to compare characters in Swift efficiently

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.

What is the Swift equivalent of isEqualToString in Objective-C?

With Swift you don't need anymore to check the equality with isEqualToString

You can now use ==

Example:

let x = "hello"
let y = "hello"
let isEqual = (x == y)

now isEqual is true.

How to find the number of matching character between two string in swift?

You can use zip to find out the number of matching characters in the same position.

let x = "1010"
let y = "1100"

let intersection = zip(x, y).filter{ $0 == $1 }
let numberOfMatches = intersection.count

How to check if two [String: Any] are identical?

For Xcode 7.3, swift 2.2
A dictionary is of type : [String:AnyObject] or simply put NSDictionary

let actual: [String: AnyObject] = ["id": 12345, "name": "Rahul Katariya"]

var expected: [String: AnyObject] = ["id": 12346, "name": "Aar Kay"]

print(NSDictionary(dictionary: actual).isEqualToDictionary(expected))//False

For Xcode 8.beta 6, Swift 3

Dictionary is defined as:

struct Dictionary<Key : Hashable, Value> : Collection, ExpressibleByDictionaryLiteral

NSDictionary has the following convenience initializer:

convenience init(dictionary otherDictionary: [AnyHashable : Any])

So you can use AnyHashable type for Key and Any type for Value

let actual: [String: Any] = ["id": 12345, "name": "Rahul Katariya"]

var expected: [String: Any] = ["id": 12346, "name": "Aar Kay"]

print(NSDictionary(dictionary: actual).isEqual(to: expected))//False


Related Topics



Leave a reply



Submit