Swift: How Can String.Join() Work Custom Types

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

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.

join an array of custom Objects

you have tu use map function.

var t1 = Team(id: 1, name: "Adria", shortname: "Ad")
var t2 = Team(id: 2, name: "Roger", shortname: "Ro")
var t3 = Team(id: 3, name: "Raquel", shortname: "Ra")

var array: [Team] = [t1, t2, t3];

var arrayMap: Array = array.map(){ toString($0.id) }
var joinedString: String = ",".join(arrayMap)

println(joinedString) // 1,2,3

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

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"

How do I convert a Swift Array to a String?

If the array contains strings, you can use the String's join method:

var array = ["1", "2", "3"]

let stringRepresentation = "-".join(array) // "1-2-3"

In Swift 2:

var array = ["1", "2", "3"]

let stringRepresentation = array.joinWithSeparator("-") // "1-2-3"

This can be useful if you want to use a specific separator (hypen, blank, comma, etc).

Otherwise you can simply use the description property, which returns a string representation of the array:

let stringRepresentation = [1, 2, 3].description // "[1, 2, 3]"

Hint: any object implementing the Printable protocol has a description property. If you adopt that protocol in your own classes/structs, you make them print friendly as well

In Swift 3

  • join becomes joined, example [nil, "1", "2"].flatMap({$0}).joined()
  • joinWithSeparator becomes joined(separator:) (only available to Array of Strings)

In Swift 4

var array = ["1", "2", "3"]
array.joined(separator:"-")

How add separator to string at every N characters in swift?

Swift 5.2 • Xcode 11.4 or later

extension Collection {

func unfoldSubSequences(limitedTo maxLength: Int) -> UnfoldSequence<SubSequence,Index> {
sequence(state: startIndex) { start in
guard start < endIndex else { return nil }
let end = index(start, offsetBy: maxLength, limitedBy: endIndex) ?? endIndex
defer { start = end }
return self[start..<end]
}
}

func every(n: Int) -> UnfoldSequence<Element,Index> {
sequence(state: startIndex) { index in
guard index < endIndex else { return nil }
defer { let _ = formIndex(&index, offsetBy: n, limitedBy: endIndex) }
return self[index]
}
}

var pairs: [SubSequence] { .init(unfoldSubSequences(limitedTo: 2)) }
}


extension StringProtocol where Self: RangeReplaceableCollection {

mutating func insert<S: StringProtocol>(separator: S, every n: Int) {
for index in indices.every(n: n).dropFirst().reversed() {
insert(contentsOf: separator, at: index)
}
}

func inserting<S: StringProtocol>(separator: S, every n: Int) -> Self {
.init(unfoldSubSequences(limitedTo: n).joined(separator: separator))
}
}

Testing

let str = "112312451"

let final0 = str.unfoldSubSequences(limitedTo: 2).joined(separator: ":")
print(final0) // "11:23:12:45:1"

let final1 = str.pairs.joined(separator: ":")
print(final1) // "11:23:12:45:1"

let final2 = str.inserting(separator: ":", every: 2)
print(final2) // "11:23:12:45:1\n"

var str2 = "112312451"
str2.insert(separator: ":", every: 2)
print(str2) // "11:23:12:45:1\n"

var str3 = "112312451"
str3.insert(separator: ":", every: 3)
print(str3) // "112:312:451\n"

var str4 = "112312451"
str4.insert(separator: ":", every: 4)
print(str4) // "1123:1245:1\n"

Swift 3: Getting error while joining the reversed() array

The problem is that the joined function requires that the separator be the same data type as the values being joined. Since self.array is an array of T, you can't pass a String as the separator. This mismatch is causing the error.

Since this is your description property and your goal is to get a string representation of the array, one solution would be to map the array of T to an array of String. Then you can join that String array with your newline separator.

One way to convert something to a String is to call the description method on it. But to do that, you need to limit your T to only those types that provide the description method which comes from CustomStringConvertible.

Change your class declaration to:

class Stack<T:CustomStringConvertible>: CustomStringConvertible {

and then your description property to:

var description: String {
let topDivider = "### STACK ###\n"
let bottomDivider = "\n############\n"
let stackElements = self.array.reversed().map { $0.description }.joined(separator: "\n")
return topDivider + stackElements + bottomDivider
}

How can I convert an Int array into a String? array in Swift

Airspeed Velocity gave you the answer:

var arr: [Int] = [1,2,3,4,5]

var stringArray = arr.map { String($0) }

Or if you want your stringArray to be of type [String?]

var stringArray = arr.map  { Optional(String($0)) }

This form of the map statement is a method on the Array type. It performs the closure you provide on every element in the array, and assembles the results of all those calls into a new array. It maps one array into a result array.
The closure you pass in should return an object of the type of the objects in your output array.

We could write it in longer form:

var stringArray = arr.map {
(number: Int) -> String in
return String(number)
}

EDIT:

If you just need to install your int values into custom table view cells, you probably should leave the array as ints and just install the values into your cells in your cellForRowAtIndexPath method.

func tableView(tableView: UITableView, 
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell",
forIndexPath: indexPath) as! MyCustomCellType
cell.textLabel?.text = "\(arr[indexPath.row])"
return cell
}

Edit #2:

If all you want to to is print the array, you'd be better off leaving it as an array of Int objects, and simply printing them:

arr.forEach { print($0) }

Merge objects of the same type

Ultimately, the most efficient way in the fewest lines of code is probably exactly what you'd expect:

extension Coin {
func merge(with: Coin) -> Coin {
var new = Coin()
new.value = value ?? with.value
new.country = country ?? with.country
new.color = color ?? with.color
return new
}
}

let coinC = coinA.merge(with: coinB)

Note that in the above scenario, the resulting value will always be coinA's, and will only be coinB's if coinA's value for a given key is nil. Whenever you change, add, or delete a property on Coin, you'll have to update this method, too. However, if you care more about future-proofing against property changes and don't care as much about writing more code and juggling data around into different types, you could have some fun with Codable:

struct Coin: Codable {
var value: Float?
var country: String?
var color: String?

func merge(with: Coin, uniquingKeysWith conflictResolver: (Any, Any) throws -> Any) throws -> Coin {
let encoder = JSONEncoder()
let selfData = try encoder.encode(self)
let withData = try encoder.encode(with)

var selfDict = try JSONSerialization.jsonObject(with: selfData) as! [String: Any]
let withDict = try JSONSerialization.jsonObject(with: withData) as! [String: Any]

try selfDict.merge(withDict, uniquingKeysWith: conflictResolver)

let final = try JSONSerialization.data(withJSONObject: selfDict)
return try JSONDecoder().decode(Coin.self, from: final)
}
}

With that solution, you can call merge on your struct like you would any dictionary, though note that it returns a new instance of Coin instead of mutating the current one:

let coinC = try coinA.merge(with: coinB) { (_, b) in b }


Related Topics



Leave a reply



Submit