Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift extension for [String]?

Tags:

swift

I'm trying to write an extension method for [String].

It seems you can't extend [String] directly ("Type 'Element' constrained to non-protocol type 'String'"), though I came across this trick:

protocol StringType { }
extension String: StringType { }

But I still can't quite make the Swift type system happy with this:

extension Array where Element: StringType {
    // ["a","b","c","d","e"] -> "a, b, c, d, or e".
    func joinWithCommas() -> String {
        switch count {
        case 0, 1, 2:
            return joinWithSeparator(" or ")
        default:
            return dropLast(1).joinWithSeparator(", ") + ", or " + last!
        }
    }
}

The joinWithSeparator calls are "Ambiguous". I've tried everything I could think of, like using (self as! [String]) (and a bunch of similar variants), but nothing seems to work.

How can I make the Swift compiler happy with this?

like image 858
J. Cocoe Avatar asked Jul 23 '16 19:07

J. Cocoe


1 Answers

edit/update

Swift 4 or later it is better to constrain the collection elements to StringProtocol which will cover Substrings as well.

extension BidirectionalCollection where Element: StringProtocol {
    var joinedWithCommas: String {
        guard let last = last else { return "" }
        return count > 2 ? dropLast().joined(separator: ", ") + ", or " + last : joined(separator: " or ")
    }
}

And if all elements are just Characters we can simply extend StringProtocol:

extension StringProtocol {
    func joined(with separator: String = ",", conector: String = "") -> String {
        guard let last = last else { return "" }
        if count > 2 {
            return dropLast().map(String.init).joined(separator: separator + " ") + separator + " " + conector + " " + String(last)
        }
        return map(String.init).joined(separator: " " + conector + " ")
    }
}

let elements = "abc"
let elementsJoined = elements.joined()                   // "a, b, c"
let elementsSeparated = elements.joined(conector: "or")  // "a, b, or c"
let elementsConected = elements.joined(conector: "and")  // "a, b, and c"


Original answer

In Swift 3.1 (Xcode 8.3.2) you can simply extend Array constraining element type equal to String

extension Array where Element == String {
    var joinedWithCommas: String {
        guard let last = last else { return "" }
        return count > 2 ? dropLast().joined(separator: ", ") + ", or " + last : joined(separator: " or ")
    }
}

["a","b","c"].joinedWithCommas    // "a, b, or c"
like image 95
Leo Dabus Avatar answered Jan 01 '23 06:01

Leo Dabus