Ken Zhang
Ken Zhang

Reputation: 1474

Swift: how can String.join() work custom types?

for example:

var a = [1, 2, 3]    // Ints
var s = ",".join(a)  // EXC_BAD_ACCESS

Is it possible to make the join function return "1,2,3" ?

Extend Int (or other custom types) to conform to some protocols ?

Upvotes: 21

Views: 20073

Answers (6)

StraightEdge
StraightEdge

Reputation: 11

A Swift 3 solution

public extension Sequence where Iterator.Element: CustomStringConvertible {
    func joined(seperator: String) -> String {
        return self.map({ (val) -> String in
            "\(val)"
        }).joined(separator: seperator)
    }
}

Upvotes: 1

Jonauz
Jonauz

Reputation: 4133

From Xcode 7.0 beta 6 in Swift 2 now you should use [String].joinWithSeparator(",").
In your case you still need to change Int to String type, therefore I added map().

var a = [1, 2, 3]                                       // [1, 2, 3]
var s2 = a.map { String($0) }.joinWithSeparator(",")    // "1,2,3"

From Xcode 8.0 beta 1 in Swift 3 code slightly changes to [String].joined(separator: ",").

var s3 = a.map { String($0) }.joined(separator: ",")    // "1,2,3"

Upvotes: 43

Peter J. Nicholls
Peter J. Nicholls

Reputation: 165

And just to make your life more complete, starting from Xcode 8.0 beta 1 in Swift 3 you should NOW use [String].joined(separator: ",").

This is the new "ed/ing" naming rule for Swift APIs:

Name functions and methods according to their side-effects

  • Those without side-effects should read as noun phrases, e.g. x.distance(to: y), i.successor().
  • Those with side-effects should read as imperative verb phrases, e.g., print(x), x.sort(), x.append(y).
  • Name Mutating/nonmutating method pairs consistently. A mutating method will often have a nonmutating variant with similar semantics, but that returns a new value rather than updating an instance in-place. Swift: API Design Guidelines

Upvotes: 10

Antonio
Antonio

Reputation: 72750

The simplest way is a variation of @BryanChen's answer:

",".join(a.map { String($0) } )

Upvotes: 6

Bryan Chen
Bryan Chen

Reputation: 46578

try this

var a = [1, 2, 3]    // Ints
var s = ",".join(a.map { $0.description })

or add this extension

extension String {
    func join<S : SequenceType where S.Generator.Element : Printable>(elements: S) -> String {
        return self.join(map(elements){ $0.description })
    }

  // use this if you don't want it constrain to Printable
  //func join<S : SequenceType>(elements: S) -> String {
  //    return self.join(map(elements){ "\($0)" })
  //}
}

var a = [1, 2, 3]    // Ints
var s = ",".join(a)  // works with new overload of join

join is defined as

extension String {
    func join<S : SequenceType where String == String>(elements: S) -> String
}

which means it takes a sequence of string, you can't pass a sequence of int to it.

Upvotes: 29

sapi
sapi

Reputation: 10224

Even if you can't make join work for custom types, there's an easy workaround. All you have to do is define a method on your class (or extend a built-in class) to return a string, and then map that into the join.

So, for example, we could have:

extension Int {
    func toString() -> String {
        return "\(self)" // trivial example here, but yours could be more complex
    }

Then you can do:

let xs = [1, 2, 3]
let s = join(xs.map { $0.toString() })

I wouldn't recommend using .description for this purpose, as by default it will call .debugDescription, which is not particularly useful in production code.

In any case, it would be better to provide an explicit method for transforming into a string suitable for joining, rather than relying on a generic 'description' method which you may change at a later date.

Upvotes: 3

Related Questions