7

Given is the following:

var theArray: [String] = ["uncool", "chill", "nifty", "precooled", "dandy", "cool"]

I want to sort the array by how similar the words are to the key word.

var keyWord: String = "cool"

The wanted result would be:

print// ["cool", "uncool", "precooled", ...] and then it does not matter anymore. But the words that are the key or contain it should be the very first objects.

My closest tryout so far has been:

let _theArray = entries.sorted { element1, element2 in

     return element1.contains(keyWord) && !element2.contains(keyWord)
}

But that results in uncool being the first item, then precooled and the most related item cool even comes after nifty .

What am I missing?

2
  • your sort is in descending for details please check this sorted function link developer.apple.com/documentation/swift/array/2905744-sorted Commented Nov 6, 2017 at 6:31
  • 2
    Your code produces ["uncool", "precooled", "cool", "chill", "nifty", "dandy"], with all strings containing "cool" coming first in the array. Commented Nov 6, 2017 at 6:37

2 Answers 2

14

You can define your own similarity sorting method. Note that I have also added a hasPrefix priority over the ones which only contains the keyword which you can just remove if you don't want it:

var theArray = ["chill", "nifty", "precooled", "cooldaddy", "cool", "coolguy", "dandy", "uncool"]
let key = "cool"

let sorted = theArray.sorted {
    if $0 == key && $1 != key {
        return true
    }
    else if $0.hasPrefix(key) && !$1.hasPrefix(key)  {
        return true
    }
    else if !$0.hasPrefix(key) && $1.hasPrefix(key)  {
        return false
    }
    else if $0.hasPrefix(key) && $1.hasPrefix(key)
        && $0.count < $1.count  {
        return true
    }
    else if $0.contains(key) && !$1.contains(key) {
        return true
    }
    else if !$0.contains(key) && $1.contains(key) {
        return false
    }
    else if $0.contains(key) && $1.contains(key)
        && $0.count < $1.count {
        return true
    }
    return false
}

print(sorted)   // ["cool", "coolguy", "cooldaddy", "uncool", "precooled", "chill", "nifty", "dandy"]

You can also extend Sequence and create a sorted by key similarity method:

extension Sequence where Element: StringProtocol {
    func sorted<S>(by key: S) -> [Element] where S: StringProtocol {
        sorted {
            if $0 == key && $1 != key {
                return true
            }
            else if $0.hasPrefix(key) && !$1.hasPrefix(key)  {
                return true
            }
            else if !$0.hasPrefix(key) && $1.hasPrefix(key)  {
                return false
            }
            else if $0.hasPrefix(key) && $1.hasPrefix(key)
                && $0.count < $1.count  {
                return true
            }
            else if $0.contains(key) && !$1.contains(key) {
                return true
            }
            else if !$0.contains(key) && $1.contains(key) {
                return false
            }
            else if $0.contains(key) && $1.contains(key)
                && $0.count < $1.count {
                return true
            }
            return false
        }
    }
}

let sorted = theArray.sorted(by: key)  // "cool", "coolguy", "cooldaddy", "uncool", "precooled", "chill", "nifty", "dandy"]

And the mutating version as well:

extension MutableCollection where Element: StringProtocol, Self: RandomAccessCollection {
    mutating func sort<S>(by key: S) where S: StringProtocol {
        sort {
            if $0 == key && $1 != key {
                return true
            }
            else if $0.hasPrefix(key) && !$1.hasPrefix(key)  {
                return true
            }
            else if !$0.hasPrefix(key) && $1.hasPrefix(key)  {
                return false
            }
            else if $0.hasPrefix(key) && $1.hasPrefix(key)
                && $0.count < $1.count  {
                return true
            }
            else if $0.contains(key) && !$1.contains(key) {
                return true
            }
            else if !$0.contains(key) && $1.contains(key) {
                return false
            }
            else if $0.contains(key) && $1.contains(key)
                && $0.count < $1.count {
                return true
            }
            return false
        }
    }
}

Sign up to request clarification or add additional context in comments.

9 Comments

By using .localizedCaseInsensitiveContains(key) eligible with CaseInsensitive pattern matching. :)
@Jack The question is how to do a custom sort. The input provided by the OP it is all lowercase and has no mentions about it. So you don't even know if thats what OP needs.
@Jack Case Insensitive (not localized) sample dropbox.com/s/bhc5fr3x5q4bvgl/…
@Jack Case Insensitive (Localized) sample dropbox.com/s/eiml3zpxs45lp9t/…
@HassanTaleb yes there was a missing condition. Check my last edit.
|
3

First you need a measure of how similar two strings are. Here's a simple example:

extension String {
    func equalityScore(with string: String) -> Double {
        if self == string {
            return 2     // the greatest equality score this method can give
        } else if self.contains(string) {
            return 1 + 1 / Double(self.count - string.count)   // contains our term, so the score will be between 1 and 2, depending on number of letters.
        } else {
            // you could of course have other criteria, like string.contains(self)
            return 1 / Double(abs(self.count - string.count))
        }
    }
}

Once you have that, you can use it to sort the array:

var theArray: [String] = ["uncool", "chill", "nifty", "precooled", "dandy", "cool"]

var compareString = "cool"

theArray.sort { lhs, rhs in
    return lhs.equalityScore(with: compareString) > rhs.equalityScore(with: compareString)
}

Result: ["cool", "uncool", "precooled", "chill", "nifty", "dandy"]

1 Comment

if the array is : ["chill", "nifty", "precooled", "cooldaddy", "cool", "coolguy", "dandy", "uncool"] the result is incorrect: ["cool", "uncool", "coolguy", "precooled", "cooldaddy", "chill", "nifty", "dandy"]

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.