Swift: for loop, search dictionary, set variables from keys - swift

Error is: Cannot convert value of type (String: Int)? to specified type 'String'.
Obviously my dictionary 'roster' is 'String: Int' and the local variables ('lastPlayer' and 'firstPlayer') inside function 'printRoster' is String. But I've tried every variation with no luck. It worked fine before I made roster a dictionary, and it was just a String.
What it should do: function concatenates a string so that it has correct punctuation. First string is "The roster includes", then we add on each player located in the Key of the dictionary 'roster'. But the first key in the roster gets no comma and the last key gets an ', and'.
Also, can I add the dictionary 'roster' to the protocol, didn't see the syntax in any of the Apple manuals
import UIKit
protocol basketballTeams {
var teamName: String? { get }
var numberOfPlayers: Int? { get }
var locatedIn: String? { get }
func printTeamLocation()
}
class BasketballTeam {
var teamName: String?
var numberOfPlayers: Int?
var locatedIn: String?
var roster = [String: Int]()
init(teamName: String) {
self.teamName = teamName
}
func printTeamLocation() -> String {
return "The \(teamName) are located in \(locatedIn)"
}
func printRoster() -> String {
var rosterPrint = "The roster includes"
for player in roster.keys {
rosterPrint += player
var firstPlayer: String = roster.first
var lastPlayer = roster.last
if (player == firstPlayer) {
rosterPrint += " \(player)"
}
else if (player == lastPlayer) {
rosterPrint += ", and \(player)."
}
else {
rosterPrint += ", \(player)"
}
}
return rosterPrint
}
}
enum NumberedPosition: Int {
case PointGuard = 1
case ShootingGuard, SmallForward, PowerForward, Center
func labelOfPosition() -> String {
switch self {
case .PointGuard:
return "Point Guard"
case .ShootingGuard:
return "Shooting Guard"
case .SmallForward:
return "Small Forward"
case .PowerForward:
return "Power Forward"
case .Center:
return "Center"
default:
"Position doesn't exit"
}
}
}
let goldenStateWarriors = BasketballTeam(teamName: "Golden State Warriors")
goldenStateWarriors.numberOfPlayers = 12
goldenStateWarriors.locatedIn = "Oakland, California"
goldenStateWarriors.roster = ["Stephen Curry" : 1, "Andrew Bogut" : 5, "Klay Thompson" : 2]
goldenStateWarriors.roster["Harrison Barnes"] = 3

How about this:
let roster = ["Stephen Curry":1, "Andrew Bogu": 2, "Klay Thompson":3]
let players = roster.keys.joined(separator: ", ")
let all = "Roster contains: \(players)."

If you still want to add the "and" to the last row you can use this function:
func printRoster() -> String {
var rosterPrint = "The roster includes"
let arr = Array(roster.keys)
let firstPlayer: String = arr.first!
let lastPlayer: String = arr.last!
for (player, _ ) in roster {
switch player {
case firstPlayer:
rosterPrint += " \(player)"
case lastPlayer:
rosterPrint += ", and \(player)."
default:
rosterPrint += ", \(player)"
}
}
return rosterPrint
}
and call it like so:
print(goldenStateWarriors.printRoster())

Given this dictionary
let roster = ["Stephen Curry":1, "Andrew Bogu": 2, "Klay Thompson":3]
you can
let elms = roster
.keys
.reduce("") { $0.1 + ", " + $0.0 }
.characters
.dropLast()
.dropLast()
let res = "The roster includes " + String(elms)
Result
"The roster includes Andrew Bogu, Stephen Curry, Klay Thompson"
Update #1: Using the FOR LOOP
Since you want to use the for loop here's the code
let roster = ["Stephen Curry":1, "Andrew Bogu": 2, "Klay Thompson":3]
var result = "The roster includes "
for (index, elm) in roster.keys.enumerate() {
result += elm
if index < roster.keys.count - 1 {
result += ", "
}
}
print(result) // The roster includes Klay Thompson, Stephen Curry, Andrew Bogu
Update #2: Using the FOR LOOP with first and last
let roster = ["Stephen Curry":1, "Andrew Bogu": 2, "Klay Thompson":3]
func printRoster() -> String {
var rosterPrint = "The roster includes"
let players = Array(roster.keys)
guard let
first = players.first,
last = players.last
else { return "" } // this is ugly, it should return nil instead...
for player in players {
switch player {
case first: rosterPrint += " \(player)"
case last: rosterPrint += ", and \(player)."
default: rosterPrint += ", \(player)"
}
}
return rosterPrint
}
printRoster() // "The roster includes Klay Thompson, Stephen Curry, and Andrew Bogu."

This seems to work well, at least until they allow .first and .last with dictionaries (if that's even possible).
class BasketballTeam {
var teamName: String?
var numberOfPlayers: Int?
var locatedIn: String?
var roster = [String: Int]()
init(teamName: String) {
self.teamName = teamName
}
func printTeamLocation() -> String {
return "The \(teamName) are located in \(locatedIn)"
}
func printRoster() -> String {
var rosterPrint = "The roster includes"
let rosterArray = Array(roster.keys)
for player in rosterArray {
let firstPlayer = rosterArray.first
let lastPlayer = rosterArray.last
switch player {
case firstPlayer!:
rosterPrint += " \(player)"
case lastPlayer!:
rosterPrint += ", and \(player)."
default:
rosterPrint += ", \(player)"
}
}
return rosterPrint
}
}
Full code, you can paste into a playground if you're trying to learn swift and familiar with basketball
import UIKit
protocol basketballTeams {
var teamName: String { get }
var numberOfPlayers: Int { get }
var locatedIn: String { get }
var roster: [String: Int] { get }
func printTeamLocation()
}
class BasketballTeam {
var teamName: String?
var numberOfPlayers: Int?
var locatedIn: String?
var roster = [String: Int]()
init(teamName: String) {
self.teamName = teamName
}
func printTeamLocation() -> String {
return "The \(teamName) are located in \(locatedIn)"
}
func printRoster() -> String {
var rosterPrint = "The roster includes"
let rosterArray = Array(roster.keys)
for player in rosterArray {
let firstPlayer = rosterArray.first
let lastPlayer = rosterArray.last
switch player {
case firstPlayer!:
rosterPrint += " \(player)"
case lastPlayer!:
rosterPrint += ", and \(player)."
default:
rosterPrint += ", \(player)"
}
}
return rosterPrint
}
}
enum NumberedPosition: Int {
case PointGuard = 1
case ShootingGuard, SmallForward, PowerForward, Center
func labelOfPosition() -> String {
switch self {
case .PointGuard:
return "Point Guard"
case .ShootingGuard:
return "Shooting Guard"
case .SmallForward:
return "Small Forward"
case .PowerForward:
return "Power Forward"
case .Center:
return "Center"
default:
"Position doesn't exist"
}
}
}
let goldenStateWarriors = BasketballTeam(teamName: "Golden State Warriors")
goldenStateWarriors.numberOfPlayers = 12
goldenStateWarriors.locatedIn = "Oakland, California"
goldenStateWarriors.roster = ["Stephen Curry" : 1, "Andrew Bogut" : 5, "Klay Thompson" : 2]
goldenStateWarriors.roster["Harrison Barnes"] = 3
goldenStateWarriors.printRoster()
goldenStateWarriors.printTeamLocation()
var num = 3
if let numberToPositionConv = NumberedPosition(rawValue: num) {
let description = numberToPositionConv.labelOfPosition()
}

Related

Generic type for Associative Enum

I have an enum like below:
public enum MyErrorEnum: LocalizedError {
case FileNotFound(String = "Failed to find file.", file: String)
public var errorDescription: String? {
if let current = Mirror(reflecting: self).children.first {
let mirror = Mirror(reflecting: current.value);
// Initial error description.
let message = mirror.children.first?.value as? String
?? current.label ?? "Unknown-case";
var context = "";
// Iterate additional context.
var i = 0;
for associated in mirror.children {
if i >= 1 {
if let text = associated.value as? String {
context += "\n ";
if let label: String = associated.label {
context += "\(label): "
}
context += text;
}
}
i += 1;
}
return context.isEmpty ? message : (
message + " {" + context + "\n}"
);
}
return "\(self)";
}
}
Usage:
do {
let path = "/path/to/file.txt";
throw MyErrorEnum.FileNotFound(
file: path
);
} catch {
print(error.localizedDescription);
}
Output:
Failed to find file. {
file: /path/to/file.txt
}
Now I would like to reuse errorDescription in any enum that implements LocalizedError, simply by moving errorDescription's logic into extension.
How should I change below to have working extension (for said matter)?
public enum MyErrorEnum: LocalizedError {
case FileNotFound(String = "Failed to find file.", file: String)
}
extension DONT_KNOW_WHAT_GOES_HERE where Self: LocalizedError {
public var errorDescription: String? {
// ... imagine all above logic moved here.
}
}
As suggested in comments, Swift does not seem to provide Generic-Type for associative-enums yet (while it has RawRepresentable for Raw-Value enums).
Solution; to not affect everything which implements LocalizedError, create your own protocol like:
public protocol LocalizedErrorEnum: LocalizedError {
var errorDescription: String? { get }
}
extension LocalizedErrorEnum {
public var errorDescription: String? {
if let current = Mirror(reflecting: self).children.first {
let mirror = Mirror(reflecting: current.value);
// Initial error description.
let message = mirror.children.first?.value as? String
?? current.label ?? "Unknown-case";
var context = "";
// Iterate additional context.
var i = 0;
for associated in mirror.children {
if i >= 1 {
if let text = associated.value as? String {
context += "\n ";
if let label: String = associated.label {
context += "\(label): "
}
context += text;
}
}
i += 1;
}
return context.isEmpty ? message : (
message + " {" + context + "\n}"
);
}
return "\(self)";
}
}
Usage:
public enum MyErrorEnum: LocalizedErrorEnum {
case FileNotFound(String = "Failed to find file.", file: String)
}

Find the length of Cycle in Linked List in Swift Playground - does it get stuck?

I couldn't see why playground is not showing certain prints() in the findCycleLength function - I'm trying to test if the class functions can produce prints() before the find length of cycle process. Here is what I'm seeing in the console.
Hello
am in class of Helloworld
[1, 2]
[1, 2, 3]
[1, 2, 3, 4, 5, 6]
Here is the code I built -
import Foundation
print("Hello")
class Solution {
class func helloWorld() {
print("am in class of Helloworld")
}
class func findCycleLength(head : Node<Int>?) -> Int {
var slow = head
var fast = head
print("BeforeCycle")
while fast != nil && slow?.next != nil {
slow = slow?.next
fast = fast?.next?.next
print("During Cycle")
if slow === fast {
return calcuateLength(head: slow!)
}
}
return 0
}
class func calcuateLength(head : Node<Int>) -> Int {
var lengthOfCycle = 1
var current = head.next
while head !== current {
current = current!.next
lengthOfCycle += 1
print(lengthOfCycle)
}
return lengthOfCycle
}
}
Solution.helloWorld()
var head = Node(value : 1)
head.next = Node(value: 2)
print(head)
head.next?.next = Node(value: 3)
print(head)
head.next?.next?.next = Node(value: 4)
head.next?.next?.next?.next = Node(value: 5)
head.next?.next?.next?.next?.next = Node(value: 6)
print(head)
head.next?.next?.next?.next?.next?.next = head.next?.next
let cycle = Solution.findCycleLength(head: head)
print("lengthOfCycle: \(lengthOfCycle)")
Here's the implementation of Node -
import Foundation
public class Node<Element> {
public var value : Element
public var next : Node?
public weak var previous : Node?
public init(value : Element) {
self.value = value
}
}
extension Node: CustomStringConvertible {
public var description: String {
var text = "["
var node = self
while node != nil {
text += "\(node.value)"
if let nextNode = node.next {
text += ", "
node = nextNode
}
else {
return text + "]"
}
}
return text + "]"
}
}
Revision -
extension Node: CustomStringConvertible {
public var description: String {
var text = "["
var node : Node?
node = self
while node != nil {
text += "\(node!.value)"
if node!.next != nil {
text += ", "
}
else {
return text + "]"
}
node = node!.next
}
return text + "]"
}
}

Swift: How to print a tree level by level without using join or higher order functions?

Tree class
class TreeNode<T>{
weak var parent: TreeNode?
var children: [TreeNode] = []
var value: T
init(value: T){
self.value = value
}
func add(_ child: TreeNode){
self.children.append(child)
print(children)
child.parent = self
}
}
print Tree
extension TreeNode{
func printTree() -> String{
var returnString = "\(value)"
if children.isEmpty == false{
returnString = "{" + returnString + "{"
for child in children{
print("child count: \(children.count)")
print("before: \(returnString)")
returnString = returnString + "," + child.printTree()
}
returnString = returnString + "}"
}
if children.isEmpty == false{
returnString += "}"
}
return returnString
}
}
Example with problem
let world = TreeNode(value: "World")
let america = TreeNode(value: "America")
let asia = TreeNode(value: "Asia")
let northAmerica = TreeNode(value: "North America")
let southAmerica = TreeNode(value: "South America")
world.add(america)
america.add(northAmerica)
america.add(southAmerica)
print(world.printTree())
{World{,{America{,North America,South America}}}}
Problem
The , after every { is unnecessary. But it's needed in between two elements. I'm thinking of removing it from the first element by knowing the index, but don't think that's a pretty solution. Is there any other alternative?
I know I can just do something like:
extension TreeNode: CustomStringConvertible {
public var description: String {
var s = "\(value)"
if !children.isEmpty {
s += " {" + children.map { $0.description }.joined(separator: ", ") + "}"
}
return s
}
}
☝️copied from RW
But I don't want to use higher order functions nor I want to use .joined
extension TreeNode: CustomStringConvertible {
var description: String {
if children.isEmpty { return "\(value)" }
var string = "{\(value){"
var separator = ""
for child in children {
string += separator
separator = ","
string += child.description
}
string += "}}"
return string
}
}
My solution is a little bit more wordy :) where I replaced printTree with my own two methods
func describeNode() -> String {
var string: String
switch children.count{
case 0: string = ""
case 1:
let child = children[0]
string = "{\(extractNode(child, andAppend: "}"))"
default:
string = "{"
let max = children.count - 1
for i in 0..<max {
let child = children[i]
string.append(extractNode(child, andAppend: ","))
}
string.append("\(children[max].value)}")
}
return "\(string)"
}
private func extractNode(_ node: TreeNode, andAppend suffix: String) -> String {
var string = "\(node.value)"
if !node.children.isEmpty {
string.append(node.describeNode())
}
string.append(suffix)
return string
}
And I used the following test case
let world = TreeNode(value: "World")
let america = TreeNode(value: "America")
let northAmerica = TreeNode(value: "North America")
let southAmerica = TreeNode(value: "South America")
world.add(america)
america.add(northAmerica)
america.add(southAmerica)
america.add(TreeNode(value: "Central America"))
northAmerica.add(TreeNode(value: "Canada"))
northAmerica.add(TreeNode(value: "USA"))
print(world.describeNode())
which printed {America{North America{Canada,USA},South America,Central America}}

How to filter objects by string comparison in Swift

I'm having an array of models:
struct Contact {
var givenName: String!
var familyName: String!
var organizationName: String!
}
I want to filter those contacts using ONE UITextField. My current problem is to define between words and filter only contacts matching all words.
For example:
var contacts: [Contact] = [Contact(givenName: "David", familyName: "Seek", organizationName: "Aldi"),
Contact(givenName: "Johne", familyName: "Doe", organizationName: "Kaisers"),
Contact(givenName: "Jane", familyName: "Doe", organizationName: "Tengelmann"),
Contact(givenName: "Marco", familyName: "Seidl", organizationName: "Rewe"),
Contact(givenName: "Filip", familyName: "Halbig", organizationName: "Aldi")]
I want to enter: David Aldi and only find David who works at Aldi. I don't want to see Filip who also works at Aldi.
Also if I enter David Do, I don't want to see any contacts, because none should match.
func getSearchResults(_ filterKey: String) {
self.presentActivityIndicator()
var processed: Int = 0
self.filteredContacts.removeAll()
DispatchQueue.global(qos: .background).async {
for contact in self.unfilteredContacts {
processed += 1
let lowercasedGivenName = contact.givenName.replacingOccurrences(of: " ", with: "").lowercased()
let lowercasedFamilyName = contact.familyName.replacingOccurrences(of: " ", with: "").lowercased()
let lowercasedOrganizationName = contact.organizationName.replacingOccurrences(of: " ", with: "").lowercased()
let name = lowercasedGivenName.appending(lowercasedFamilyName)
if name.range(of: filterKey.replacingOccurrences(of: " ", with: "")) != nil {
if !self.filteredContacts.contains(contact) {
self.filteredContacts.append(contact)
}
}
for word in filterKey.components(separatedBy: " ") {
if lowercasedOrganizationName.range(of: word.lowercased()) != nil {
if !self.filteredContacts.contains(contact) {
self.filteredContacts.append(contact)
}
}
}
if processed == self.unfilteredContacts.count {
self.reloadTableViewInMainThread()
}
}
}
}
This is one of the several approaches I've tried today. But with every try, I've been ending up, filtering out David and by entering the second name, I have found other contacts not matching the first name David, but match for example part of the family name or company name.
What am I missing and would be the best approach for this? Help is very appreciated.
First separate the filterKey into space separated components
let components = filterKey.components(separatedBy: " ")
then use the filter function with the closure syntax
self.filteredContacts = contacts.filter { contact -> Bool in
for string in components {
if contact.givenName != string && contact.familyName != string && contact.organizationName != string {
return false
}
}
return true
}
The closure returns true if the contact matches all components.
Wrapped in the function it's
func getSearchResults(_ filterKey: String) {
let components = filterKey.components(separatedBy: " ")
self.filteredContacts = contacts.filter { contact -> Bool in
for string in components {
if contact.givenName != string && contact.familyName != string && contact.organizationName != string {
return false
}
}
return true
}
}
Note:
Please, please never declare properties / members as implicit unwrapped optionals which are initialized with an init method. It perfectly legal (and recommended) to declare the members without the exclamation marks. However if they are supposed to be optional declare them as real optional (question mark).
And if the values won't change declare the members as constants (let).
struct Contact {
let givenName: String
let familyName: String
let organizationName: String
}
Edit:
To filter the contacts whose properties contain filterKey write
self.filteredContacts = contacts.filter { contact -> Bool in
for string in components {
if !contact.givenName.lowercased().contains(string) &&
!contact.familyName.lowercased().contains(string) &&
!contact.organizationName.lowercased().contains(string) {
return false
}
}
return true
}
To filter the contacts whose properties begin with filterKey write
self.filteredContacts = contacts.filter { contact -> Bool in
for string in components {
if !contact.givenName.lowercased().hasPrefix(string) &&
!contact.familyName.lowercased().hasPrefix(string) &&
!contact.organizationName.lowercased().hasPrefix(string) {
return false
}
}
return true
}
func getSearchResults(contacts: [Contact], filterKeys: String) -> [Contact] {
let keys = filterKeys.components(separatedBy: " ")
var contactsFiltered = contacts
keys.forEach { key in
contactsFiltered = contactsFiltered.filter {
$0.givenName == key || $0.familyName == key || $0.organizationName == key
}
}
return contactsFiltered
}
I splitted the filterKeys by blank spaces. Then, for each key, I check if the value exists in contact attribute.
If do you want a pure functional solution, you can use Set and intersection:
func getSearchResults(contacts: [Contact], filterKeys: String) -> [Contact] {
let keys = filterKeys.components(separatedBy: " ")
return contacts.filter {
Set([$0.givenName, $0.familyName, $0.organizationName]).intersection(keys).count >= keys.count
}
}
And, if do you want a crazy solution with Mirror, for when adding a new attribute in Contact, you do not need to update getSearchResults:
func getSearchResults(contacts: [Contact], filterKeys: String) -> [Contact] {
let keys = filterKeys.components(separatedBy: " ")
return contacts.filter {
let stringAttr = Mirror(reflecting: $0).children.filter { ($0.value as? String) != nil }
let contactValues = stringAttr.map { $0.value as! String }
return Set(contactValues).intersection(keys).count >= keys.count
}
}
Use with caution my last code (or never use it)
Edit
For match part of string in key.
func getSearchResults(contacts: [Contact], filterKeys: String) -> [Contact] {
let keys = filterKeys.components(separatedBy: " ").map { $0.lowercased() }
var contactsFiltered = contacts
keys.forEach { key in
contactsFiltered = contactsFiltered.filter {
$0.givenName.lowercased().range(of: key) != nil ||
$0.familyName.lowercased().range(of: key) != nil ||
$0.organizationName.lowercased().range(of: key) != nil
}
}
return contactsFiltered
}

Proper way to concatenate optional swift strings?

struct Person {
var firstName: String?
var lastName: String?
}
Now I want to construct a fullName string that contains either just their first or last name (if that's all that is available), or if we have both, their first and last name with a space in the middle.
var fullName: String?
if let first = person.firstName {
fullName = first
}
if let last = person.lastName {
if fullName == nil {
fullName = last
} else {
fullName += " " + last
}
}
or
var fullName = ""
if let first = person.firstName {
fullName = first
}
if let last = person.lastName {
fullName += fullName.count > 0 ? (" " + last) : last
}
Are we just supposed to nest if let's? Nil coalescing seems appropriate but I can't think of how to apply it in this scenario. I can't help but feeling like I'm doing optional string concatenation in an overly complicated way.
compactMap would work well here, combined with .joined(separator:):
let f: String? = "jo"
let l: String? = "smith"
[f,l] // "jo smith"
.compactMap { $0 }
.joined(separator: " ")
It doesn't put the space between if one is nil:
let n: String? = nil
[f,n] // "jo"
.compactMap { $0 }
.joined(separator: " ")
Somewhere, I believe in the swift book, I ran into this pattern, from when before you could have multiple lets in a single if:
class User {
var lastName : String?
var firstName : String?
var fullName : String {
switch (firstName, lastName) {
case (.Some, .Some):
return firstName! + " " + lastName!
case (.None, .Some):
return lastName!
case (.Some, .None):
return firstName!
default:
return ""
}
}
init(lastName:String?, firstName:String?) {
self.lastName = lastName
self.firstName = firstName
}
}
User(lastName: nil, firstName: "first").fullName // -> "first"
User(lastName: "last", firstName: nil).fullName // -> "last"
User(lastName: nil, firstName: nil).fullName // -> ""
User(lastName: "last", firstName: "first").fullName // -> "first last"
An even briefer solution, given swift 3.0:
var fullName : String {
return [ firstName, lastName ].flatMap({$0}).joined(separator:" ")
}
Sometimes simple is best:
let first = p.first ?? ""
let last = p.last ?? ""
let both = !first.isEmpty && !last.isEmpty
let full = first + (both ? " " : "") + last
This works if there is no first or last, if there is a first but no last, if there is a last but no first, and if there are both a first and a last. I can't think of any other cases.
Here's an idiomatic incorporation of that idea into a calculated variable; as an extra benefit, I've allowed full to be nil just in case both the other names are nil:
struct Person {
var first : String?
var last : String?
var full : String? {
if first == nil && last == nil { return nil }
let fi = p.first ?? ""
let la = p.last ?? ""
let both = !fi.isEmpty && !la.isEmpty
return fi + (both ? " " : "") + la
}
}
Here is an alternative method:
let name =
(person.first != nil && person.last != nil) ?
person.first! + " " + person.last! :
person.first ?? person.last!
For those who are want to check nil and "" value as well you can do something like this:
var a: String? = nil
let b = "first value"
let c: String? = nil
let d = ""
let e = "second value"
var result = [a,b,c,d,e].compactMap{ $0 }.filter { $0 != "" }.joined(separator:", ")
print(result)
//first value, second value
I like oisdk's approach but I didn't like the empty string if both were nil. I would rather have nil.
func add(a a: String?, b: String?, separator: String = " ") -> String? {
let results = [a, b].flatMap {$0}
guard results.count > 0 else { return nil }
return results.joinWithSeparator(separator)
}
What oisdk answered was great, but I needed something very specific along the lines of the OP's original question.
Writing for Swift 4.x, I created this extension which works well when populating other strings, such as text labels. I have also updated it to include a function for handling an array if needed.
extension String {
static func combine(first: String?, second: String?) -> String {
return [first, second].compactMap{ $0 }.joined(separator: " ")
}
static func combine(strings: [String?]) -> String {
return strings.compactMap { $0 }.joined(separator: " ")
}
}
An example of this populating a text label with two optional strings:
print(String.combine(first: "First", second: "Last")) // "First Last"
print(String.combine(first: "First", second: nil)) // "First"
print(String.combine(first: nil, second: "Last")) // "Last"
If you have an array of optional strings, you can call the array function:
print(String.combine(strings: ["I", "Have", nil, "A", "String", nil, "Here"]))
// "I Have A String Here"
for swift 4
let name: String? = "Foo"
let surname: String? = "Bar"
let fullname = (name ?? "") + " " + (surname ?? "")
print(fullname)
// Foo Bar
func getSingleValue(_ value: String?..., seperator: String = " ") -> String? {
return value.reduce("") {
($0) + seperator + ($1 ?? "")
}.trimmingCharacters(in: CharacterSet(charactersIn: seperator) )
}
It's too bad that there isn't more support for operators on the Optional enum, but I overloaded the standard concatenation operator (+) like this:
func +<T: StringProtocol>(lhs: Optional<T>, rhs: Optional<T>) -> String {
return [lhs, rhs].compactMap({ $0 }).joined()
}
Then you can use it like this:
let first: String? = "first"
let last: String? = nil
first + first // "firstfirst"
first + last // "first"
last + first // "first"
last + last // ""
Add-On:
Consider you have Struct:
struct Person {
var firstName: String?
var lastName: String?
}
you can use the CustomStringConvertible
extension Person: CustomStringConvertible {
var description: String {
[firstName, lastName].compactMap { $0 }.joined(separator: " ")
}
}
let person1 = Person(firstName "Jeba", lastName: "Moses")
print(person1) // Prints "Jeba Moses"
let person2 = Person(firstName: "Ebenrick", lastName: nil)
print(person2) // Prints "Ebenrick"