Eric Phillips
Eric Phillips

Reputation: 507

Join string array with separator ", " and add ", and " to join the last element in Swift

I'm messing around with parsing JSON with SwiftyJSON on a swift playground. My code is as follows:

import UIKit
import SwiftyJSON

var partyList: [String] = []
var firstPresidentList: [String] = []

if let url = URL(string:"http://mysafeinfo.com/api/data?list=presidents&format=json") {
    if let data = try? Data(contentsOf: url) {
        let json = JSON(data: data)
        for i in 1...43 {
            let party = json[i]["pp"].stringValue
            let president = json[i]["nm"].stringValue
            if partyList.contains(party) {
                print("\n")
            } else {
                partyList.append(party)
                firstPresidentList.append(president)
            }
        }
        print("All the different parties of U.S. presidents included "+partyList.joined(separator: ", ")+", in that order. The first presidents of those parties were (repectively) "+firstPresidentList.joined(separator: ", ")+".")
    }
}

On the print line, I was wondering how I could join the arrays with a comma and space like I have, but add "and" before the last one.

Thank you!

Upvotes: 13

Views: 6659

Answers (2)

Leo Dabus
Leo Dabus

Reputation: 236315

Add a condition to check if your String collection has less than or is equal to 2 elements, if true just return the two elements joined by " and " otherwise drop the last element of your collection, join the elements with a separator ", " then re add the last element with the final separator ", and ".

You can extend BidirectionalCollection protocol constraining its elements to the StringProtocol:

Bidirectional collections offer traversal backward from any valid index, not including a collection’s startIndex. Bidirectional collections can therefore offer additional operations, such as a last property that provides efficient access to the last element and a reversed() method that presents the elements in reverse order.

Xcode 11.4 • Swift 5.2 or later

extension BidirectionalCollection where Element: StringProtocol {
    var sentence: String {
        count <= 2 ?
            joined(separator: " and ") :
            dropLast().joined(separator: ", ") + ", and " + last!
    }
}

let elements = ["a", "b", "c"]
let sentenceFromElements = elements.sentence   // "a, b, and c"

edit/update

Xcode 13+ • iOS15+

You can use the new generic structure ListFormatStyle with the new instance methods of Sequence called formatted:

let elements = ["a", "b", "c"]
let formatted = elements.formatted()  // "a, b, and c"

let names = ["Steve Jobs", "Wozniak", "Tim Cook", "Jony Ive"]
let formatted2and = names.formatted(.list(type: .and, width: .short))  // "Steve Jobs, Wozniak, Tim Cook, & Jony Ive"
let formatted2or = names.formatted(.list(type: .or, width: .short))    // "Steve Jobs, Wozniak, Tim Cook, or Jony Ive"

If you need a specific locale (fixed) like Portuguese Brasil:

let localeBR = Locale(identifier: "pt_BR")
let formatted2e = names.formatted(
    .list(type: .and, width: .short)
    .locale(localeBR)
)  // "Steve Jobs, Wozniak, Tim Cook e Jony Ive"
let formatted2ou = names.formatted(
    .list(type: .or, width: .short)
    .locale(localeBR)
)  // "Steve Jobs, Wozniak, Tim Cook ou Jony Ive"

Upvotes: 34

freytag
freytag

Reputation: 4819

Since iOS 13.0+ / macOS 10.15+ Apple provides the ListFormatter. See also here for details.

Arrays can be formatted as easy as:

let elements = ["a", "b", "c"]
result = ListFormatter.localizedString(byJoining: elements)

As the function name suggests, you also get the localization for free.

Upvotes: 21

Related Questions