I have searched this site and found similar answers but for some reason I still get Optional word printed, here's my code:
if description != nil {
description = description! + symbol + String(describing: accumulator)
if let desc = description {
print(desc)
} else {
description = String(describing: accumulator) + symbol
if let desc = description {
print(desc)
}
}
}
The output is: Optional(value)
String(describing: s) will output "Optional()" if the type of s is String?
So, you need to unwrap accumulator as well as description.
var accumulator: String? = ""
var GlobalAccmulator:String = "NA"
if accumulator != nil
{
GlobalAccmulator = accumulator!
}
Related
ruby has the function string.squeeze, but I can't seem to find a swift equivalent.
For example I want to turn bookkeeper -> bokepr
Is my only option to create a set of the characters and then pull the characters from the set back to a string?
Is there a better way to do this?
Edit/update: Swift 4.2 or later
You can use a set to filter your duplicated characters:
let str = "bookkeeper"
var set = Set<Character>()
let squeezed = str.filter{ set.insert($0).inserted }
print(squeezed) // "bokepr"
Or as an extension on RangeReplaceableCollection which will also extend String and Substrings as well:
extension RangeReplaceableCollection where Element: Hashable {
var squeezed: Self {
var set = Set<Element>()
return filter{ set.insert($0).inserted }
}
}
let str = "bookkeeper"
print(str.squeezed) // "bokepr"
print(str[...].squeezed) // "bokepr"
I would use this piece of code from another answer of mine, which removes all duplicates of a sequence (keeping only the first occurrence of each), while maintaining order.
extension Sequence where Iterator.Element: Hashable {
func unique() -> [Iterator.Element] {
var alreadyAdded = Set<Iterator.Element>()
return self.filter { alreadyAdded.insert($0).inserted }
}
}
I would then wrap it with some logic which turns a String into a sequence (by getting its characters), unqiue's it, and then restores that result back into a string:
extension String {
func uniqueCharacters() -> String {
return String(self.characters.unique())
}
}
print("bookkeeper".uniqueCharacters()) // => "bokepr"
Here is a solution I found online, however I don't think it is optimal.
func removeDuplicateLetters(_ s: String) -> String {
if s.characters.count == 0 {
return ""
}
let aNum = Int("a".unicodeScalars.filter{$0.isASCII}.map{$0.value}.first!)
let characters = Array(s.lowercased().characters)
var counts = [Int](repeatElement(0, count: 26))
var visited = [Bool](repeatElement(false, count: 26))
var stack = [Character]()
var i = 0
for character in characters {
if let num = asciiValueOfCharacter(character) {
counts[num - aNum] += 1
}
}
for character in characters {
if let num = asciiValueOfCharacter(character) {
i = num - aNum
counts[i] -= 1
if visited[i] {
continue
}
while !stack.isEmpty, let peekNum = asciiValueOfCharacter(stack.last!), num < peekNum && counts[peekNum - aNum] != 0 {
visited[peekNum - aNum] = false
stack.removeLast()
}
stack.append(character)
visited[i] = true
}
}
return String(stack)
}
func asciiValueOfCharacter(_ character: Character) -> Int? {
let value = String(character).unicodeScalars.filter{$0.isASCII}.first?.value ?? 0
return Int(value)
}
Here is one way to do this using reduce(),
let newChar = str.characters.reduce("") { partial, char in
guard let _ = partial.range(of: String(char)) else {
return partial.appending(String(char))
}
return partial
}
As suggested by Leo, here is a bit shorter version of the same approach,
let newChar = str.characters.reduce("") { $0.range(of: String($1)) == nil ? $0.appending(String($1)) : $0 }
Just Another solution
let str = "Bookeeper"
let newChar = str.reduce("" , {
if $0.contains($1) {
return "\($0)"
} else {
return "\($0)\($1)"
}
})
print(str.replacingOccurrences(of: " ", with: ""))
Use filter and contains to remove duplicate values
let str = "bookkeeper"
let result = str.filter{!result.contains($0)}
print(result) //bokepr
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
}
I have a string that contains a url. I am trying to check if the url has a space which is invalid.
let url = "http://www.example.com/images/pretty pic.png"
As you can see in this example, there is a space between pretty and pic. Thanks.
Another alternative: check if the set of characters to the string contain a whitespace
let url = "http://www.example.com/images/pretty pic.png"
url.characters.contains(" ") // true
let url = "http://www.example.com/images/prettypic.png"
url.characters.contains(" ") // false
let url = "http://www.example.com/images/pretty pic.png"
let whiteSpace = " "
if let hasWhiteSpace = url.rangeOfString(whiteSpace) {
print ("has whitespace")
} else {
print("no whitespace")
}
Use simple indexOf to find the space.
function hasWhiteSpace(s) {
return s.indexOf(' ') >= 0;
}
extension on String which returns bool would be more elegant solution here
extension String {
public var hasWhiteSpace: Bool {
return self.contains(" ")
}
}
For Swift 5
var string = "Hi "
if string.contains(" "){
print("Has space")
}else{
print("Does not have space")
}
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"
What I'm trying to accomplish in imperative:
var mapNames = [String]()
var mapLocation = [String]()
for valueMap in valueMaps {
if let name = valueMap.name {
mapNames.append(name)
}
if let location = valueMap.location {
mapLocation.append(location)
}
}
What's the best way using a high order function or perhaps an array method (array.filter etc.) to compact the code above and also avoid using the for loop
Here is what I have tried, but the compiler gives an error:
let getArrayOfNames = valueMaps.filter() {
if let name = ($0 as valueMaps).name as [String]! {
return name;
}
}
let getArrayOfLocations = valueMaps.filter() {
if let type = ($0 as valueMaps).location as [String]! {
return type;
}
}
You need both filter() and map() :
let mapNames = valueMaps.filter( {$0.name != nil }).map( { $0.name! })
let mapLocations = valueMaps.filter( {$0.location != nil }).map( { $0.location! })
The filter takes a predicate as an argument (which specifies which
elements should be included in the result), and the map takes
a transformation as an argument. You were trying to merge both
aspects into the filter, which is not possible.
Update: As of Swift 2(?) has a flatMap() method for sequences, which
can be used to obtain the result in a single step:
let mapNames = valueMaps.flatMap { $0.name }
The closure is applied to all array elements, and the return value is an
array with all non-nil unwrapped results.
The filter() function needs its closure to return a bool - not the value you want to store in an array. You could chain filter and map together to get what you want, then:
let getArrayOfNames = valueMaps
.filter { $0.name != nil }
.map{ $0.name! }
Or, to do it in one function, with reduce:
let getArrayOfNames = valueMaps
.reduce([String]()) {
accu, element in
if let name = element.name {
return accu + [name]
} else {
return accu
}
}
Actually, the reduce can be a little better:
let getArrayOfNames = valueMaps.reduce([String]()) {
(names, value) in names + (value.name.map{[$0]} ?? [])
}
let getArrayOfLocations = valueMaps.reduce([String]()) {
(locs, value) in locs + (value.location.map{[$0]} ?? [])
}