Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How do you compare two alphanumeric Strings with the conditions described below?

I’ll try to define the problem in words, but this is easier explained with the use of the example test cases.

"b" > "a"
"3" > "1"
"ac" > "ab"
"32" > "25"
"abcd1" > "abc2"
"abc123a" > "abc2a"
"abc123a" < "abc1234a"
"abc123" < "abc123a"
"abc12a49" > "abc12a39"
"123ab3" = "123ab3"

I need to compare alphanumeric strings. It is trivial when there are only numbers or only characters in the string, but when they are combined, it becomes more complicated. Basically I need to compare the number groups as one whole number instead of individual numbers, if I make sense.

The following is what I tried. It is a brute force approach to split the string into groups of alphabets and numbers.
I think this works but it is not very performant nor elegant. Is there a better way to achieve this. I’m pretty much dumping all the dependent extensions and methods needed for this to run on the playground, so very sorry for the lack of formatting.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

extension RangeReplaceableCollection {
    
    mutating func removeFirstSafe() -> Element? {
        
        guard !isEmpty else { return nil }
        return removeFirst()
    }
}

extension String {
    
    func split(grouping charSets: [[Character]]) -> [(CharacterType, String)] {
        
        var subStrings = [(CharacterType, String)]()
        
        var copy = self
        copy.__split(grouping: charSets, groupedSubstrings: &subStrings)
        
        return subStrings
    }
    
    mutating func __split(grouping charSets: [[Character]], groupedSubstrings: inout [(CharacterType, String)]) {
        
        guard let previousCharacter: Character = removeFirstSafe() else {
            return
        }
        var groupedSubString = String(previousCharacter)
        
        let prevCharSetGroup: CharacterType = charSets.indexOfCharSetGroupContainingCharacter(previousCharacter)

        while let currentCharacter = first, charSets.indexOfCharSetGroupContainingCharacter(currentCharacter) == prevCharSetGroup {
            
            groupedSubString.append(currentCharacter)
            removeFirst()
        }
        
        groupedSubstrings.append((prevCharSetGroup, groupedSubString))
        __split(grouping: charSets, groupedSubstrings: &groupedSubstrings)
    }
}

enum CharacterType: Int, Comparable {
    
    case alphabet = 1
    case number = 0
    case unknown = -1
    
    static func < (lhs: CharacterType, rhs: CharacterType) -> Bool {
        lhs.rawValue < rhs.rawValue
    }
}

extension Array where Element == [Character] {
    
    func indexOfCharSetGroupContainingCharacter(_ char: Character) -> CharacterType {
        
        for (index, group) in self.enumerated() {
            
            if group.contains(char) { return CharacterType(rawValue: index) ?? .unknown }
        }
        return .unknown
    }
}

extension Collection {

    subscript (safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

struct AlphanumericString {
    var string: String
}

func <(lhs: (CharacterType, String), rhs: (CharacterType, String)) -> Bool {
    
    if lhs.0 > rhs.0 { return false }
    else if lhs.0 < rhs.0 { return true }
    else {
        if lhs.0 == .alphabet || lhs.0 == .unknown { return lhs.1 < rhs.1 }
        else { return (Int(lhs.1) ?? 0) < (Int(rhs.1) ?? 0) }
    }
}

extension AlphanumericString: Comparable {
    static func < (lhs: AlphanumericString, rhs: AlphanumericString) -> Bool {
        
        if lhs.string == rhs.string { return false }
        
        let lhsGrouped = lhs.string.split(grouping: [Array("12345"), Array("abcde")])
        let rhsGrouped = rhs.string.split(grouping: [Array("12345"), Array("abcde")])
        
        for idx in lhsGrouped.indices {
            
            let lhsItem = lhsGrouped[idx]
            guard let rhsItem = rhsGrouped[safe: idx] else { return false }
            
            if lhsItem < rhsItem { return true }
            else if rhsItem < lhsItem { return false }
        }
        
        return true
    }
}

print(AlphanumericString(string: "abcd1") < AlphanumericString(string: "abc2"))

>Solution :

It is trivial

It is indeed, because there is an API: localizedStandardCompare(_:)

The description is Compares strings as sorted by the Finder.

func numericCompare(lhs: String, rhs: String, mode: ComparisonResult) -> Bool {
   lhs.localizedStandardCompare(rhs) == mode
}

numericCompare(lhs: "b" ,rhs: "a", mode: .orderedDescending) // true
numericCompare(lhs: "3" ,rhs: "1", mode: .orderedDescending) // true
numericCompare(lhs: "ac" ,rhs: "ab", mode: .orderedDescending) // true
numericCompare(lhs: "32" ,rhs: "25", mode: .orderedDescending) // true
numericCompare(lhs: "abcd1" ,rhs: "abc2", mode: .orderedDescending) // true
numericCompare(lhs: "abc123a" ,rhs: "abc2a", mode: .orderedDescending) // true
numericCompare(lhs: "abc123a" ,rhs: "abc1234a", mode: .orderedAscending) // true
numericCompare(lhs: "abc123" ,rhs: "abc123a", mode: .orderedAscending) // true
numericCompare(lhs: "abc12a49" ,rhs: "abc12a39", mode: .orderedDescending) // true
numericCompare(lhs: "123ab3" ,rhs: "123ab3", mode: .orderedSame) // true

An alternative is lhs.compare(rhs, options: .numeric) which does the same

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading