I’ve found this awesome extension that remove everything except the characters in the quotation marks.
extension String.UnicodeScalarView {
var removeCharacters: String {
return String(filter(("cfh".unicodeScalars).contains))
}
}
print("abcd123efg".unicodeScalars.removeCharacters) // it prints “cf”, my desirable result is to make it print “abd123eg”
It prints “cf”, my desirable result is to make it print “abd123eg”.
Can you help me invert it to remove only the characters that are between the quotation marks and leave everything else?
Note:It is also important that it recognize (unicodeScalars) so please don’t remove this part.
You need to negate the call to contains:
return String(filter { !"cfh".unicodeScalars.contains($0) })
Taking #rmaddy into consideration, if you are using extension then make sure you think about generics and work in any case. Like you have kept "cfh" as a static String, what if you want to remove "abc", then it won't work. So, here is the modified version:
extension String.UnicodeScalarView {
func removeCharacters(_ characters: String) -> String {
return String(filter { !characters.unicodeScalars.contains($0) })
}
}
Usage: "abcd123efg".unicodeScalars.removeCharacters("cfh")
Create an extension for String like below
extension String {
func removeGroupOfCharacters(removalString : String) -> String {
// return an Array without the removal Characters
let filteredCharacters = Array(self).filter { !Array(removalString).contains($0) }
// build a String with the filtered Array
let filtered = String(filteredCharacters)
return filtered
}
}
override func viewDidLoad() {
super.viewDidLoad()
let unfiltered = "abcd123efg"
let removal = "cfh"
print(unfiltered.removeGroupOfCharacters(removalString: removal))
// => output will be like this "abd123eg\n"
}
It's a basic example. You can do like that.
extension String {
return aString.replacingOccurrences(of: "X", with: "")
}
Related
In a string typed array how can I achieve the functionality as I would for checking whitespace in a string? I'd like to check if the array contains only whitespace
var stringExample: String!
var stringArrayExample: [String]!
if stringExample.trimmingCharacters(in: .whitespaces).isEmpty{
//string contains whitespace characters
}
Swift 3 would look something like this if I'm understanding what you're wanting:
var someStrings = [" ", "foo", "bar", "\t"]
let result = someStrings.filter { $0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
print(result) // [" ", "\t"]
If you're just wanting to know if the array of strings are all whitespace-only strings you could change the last two lines to:
let result = someStrings.filter { $0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false }
print(result.isEmpty) // false
Note that both these use .whitespacesAndNewlines if you don't want new lines, just use .whitespaces like you do in your original example.
I've created an extension for String which returns whether it's empty or contains only whitespace:
extension String {
var isEmptyOrWhitespace : Bool {
return self.trimmingCharacters(in: .whitespaces).isEmpty
}
}
And since I'm also a .NET developer and like the methods Any, All etc. I've also created an extension for the Array type, which lets me check a condition for every element in the array, leveraging the reduce function:
extension Array {
func all(test: (Element) -> Bool) -> Bool {
return self.reduce(true) { $0 && test($1) }
}
}
Then you can combine these two to get a fairly nice syntax, which is also fairly performant, since it "breaks" when it stumbles upon an element that does not comply with the provided test (using a for instead of reduce would probably be even more efficient).
let strings1 = [" ", "", "\t"]
print(strings1.all { $0.isEmptyOrWhitespace }) // true
print(strings1.all { !$0.isEmptyOrWhitespace }) // false
By printing within the test, you can see it no longer executes the tests for elements when it finds the first non-compliant one.
let strings2 = [" ", "x", "\t"]
print(strings2.all(test: { (str) -> Bool in
let e = str.isEmptyOrWhitespace
print ("[\(str)]: \(e)")
return e
}))
Prints:
[ ]: true
[x]: false
false
Every example of trimming strings in Swift remove both leading and trailing whitespace, but how can only trailing whitespace be removed?
For example, if I have a string:
" example "
How can I end up with:
" example"
Every solution I've found shows trimmingCharacters(in: CharacterSet.whitespaces), but I want to retain the leading whitespace.
RegEx is a possibility, or a range can be derived to determine index of characters to remove, but I can't seem to find an elegant solution for this.
With regular expressions:
let string = " example "
let trimmed = string.replacingOccurrences(of: "\\s+$", with: "", options: .regularExpression)
print(">" + trimmed + "<")
// > example<
\s+ matches one or more whitespace characters, and $ matches
the end of the string.
In Swift 4 & Swift 5
This code will also remove trailing new lines.
It works based on a Character struct's method .isWhitespace
var trailingSpacesTrimmed: String {
var newString = self
while newString.last?.isWhitespace == true {
newString = String(newString.dropLast())
}
return newString
}
This short Swift 3 extension of string uses the .anchored and .backwards option of rangeOfCharacter and then calls itself recursively if it needs to loop. Because the compiler is expecting a CharacterSet as the parameter, you can just supply the static when calling, e.g. "1234 ".trailing(.whitespaces) will return "1234". (I've not done timings, but would expect faster than regex.)
extension String {
func trailingTrim(_ characterSet : CharacterSet) -> String {
if let range = rangeOfCharacter(from: characterSet, options: [.anchored, .backwards]) {
return self.substring(to: range.lowerBound).trailingTrim(characterSet)
}
return self
}
}
In Foundation you can get ranges of indices matching a regular expression. You can also replace subranges. Combining this, we get:
import Foundation
extension String {
func trimTrailingWhitespace() -> String {
if let trailingWs = self.range(of: "\\s+$", options: .regularExpression) {
return self.replacingCharacters(in: trailingWs, with: "")
} else {
return self
}
}
}
You can also have a mutating version of this:
import Foundation
extension String {
mutating func trimTrailingWhitespace() {
if let trailingWs = self.range(of: "\\s+$", options: .regularExpression) {
self.replaceSubrange(trailingWs, with: "")
}
}
}
If we match against \s* (as Martin R. did at first) we can skip the if let guard and force-unwrap the optional since there will always be a match. I think this is nicer since it's obviously safe, and remains safe if you change the regexp. I did not think about performance.
Handy String extension In Swift 4
extension String {
func trimmingTrailingSpaces() -> String {
var t = self
while t.hasSuffix(" ") {
t = "" + t.dropLast()
}
return t
}
mutating func trimmedTrailingSpaces() {
self = self.trimmingTrailingSpaces()
}
}
Swift 4
extension String {
var trimmingTrailingSpaces: String {
if let range = rangeOfCharacter(from: .whitespacesAndNewlines, options: [.anchored, .backwards]) {
return String(self[..<range.lowerBound]).trimmingTrailingSpaces
}
return self
}
}
Demosthese's answer is a useful solution to the problem, but it's not particularly efficient. This is an upgrade to their answer, extending StringProtocol instead, and utilizing Substring to remove the need for repeated copying.
extension StringProtocol {
#inline(__always)
var trailingSpacesTrimmed: Self.SubSequence {
var view = self[...]
while view.last?.isWhitespace == true {
view = view.dropLast()
}
return view
}
}
No need to create a new string when dropping from the end each time.
extension String {
func trimRight() -> String {
String(reversed().drop { $0.isWhitespace }.reversed())
}
}
This operates on the collection and only converts the result back into a string once.
It's a little bit hacky :D
let message = " example "
var trimmed = ("s" + message).trimmingCharacters(in: .whitespacesAndNewlines)
trimmed = trimmed.substring(from: trimmed.index(after: trimmed.startIndex))
Without regular expression there is not direct way to achieve that.Alternatively you can use the below function to achieve your required result :
func removeTrailingSpaces(with spaces : String) -> String{
var spaceCount = 0
for characters in spaces.characters{
if characters == " "{
print("Space Encountered")
spaceCount = spaceCount + 1
}else{
break;
}
}
var finalString = ""
let duplicateString = spaces.replacingOccurrences(of: " ", with: "")
while spaceCount != 0 {
finalString = finalString + " "
spaceCount = spaceCount - 1
}
return (finalString + duplicateString)
}
You can use this function by following way :-
let str = " Himanshu "
print(removeTrailingSpaces(with : str))
One line solution with Swift 4 & 5
As a beginner in Swift and iOS programming I really like #demosthese's solution above with the while loop as it's very easy to understand. However the example code seems longer than necessary. The following uses essentially the same logic but implements it as a single line while loop.
// Remove trailing spaces from myString
while myString.last == " " { myString = String(myString.dropLast()) }
This can also be written using the .isWhitespace property, as in #demosthese's solution, as follows:
while myString.last?.isWhitespace == true { myString = String(myString.dropLast()) }
This has the benefit (or disadvantage, depending on your point of view) that this removes all types of whitespace, not just spaces but (according to Apple docs) also including newlines, and specifically the following characters:
“\t” (U+0009 CHARACTER TABULATION)
“ “ (U+0020 SPACE)
U+2029 PARAGRAPH SEPARATOR
U+3000 IDEOGRAPHIC SPACE
Note: Even though .isWhitespace is a Boolean it can't be used directly in the while loop as it ends up being optional ? due to the chaining of the optional .last property, which returns nil if the String (or collection) is empty. The == true logic gets around this since nil != true.
I'd love to get some feedback on this, esp. in case anyone sees any issues or drawbacks with this simple single line approach.
Swift 5
extension String {
func trimTrailingWhiteSpace() -> String {
guard self.last == " " else { return self }
var tmp = self
repeat {
tmp = String(tmp.dropLast())
} while tmp.last == " "
return tmp
}
}
I'm trying to implement search inside my app that I'm making. I have an array that I'm trying to search and I find this code online:
func filterContentForSearchText(searchText: String) {
filteredCandies = candies.filter({( candy : Candies) -> Bool in
if candy.name.lowercaseString.containsString(searchText.lowercaseString) == true {
return true
} else {
return false
}
})
tableView.reloadData()
}
The issue is that the database that I'm trying to implement search on has text that is all scrambled because it was supposed to shortened. How can I make it so that the search will check if all the letters are there instead of searching exactly the right name. Example of object from database (USDA): CRAB, DUNGINESS, RAW
If you have an answer, please make it fast enough for live searching. Non live searching makes searching terrible (at least for me)!
I'm using Swift 2.2 and Xcode 7
As an improvement to #appzYourLife's solution, you could do this with a native Swift Set, as a counted set isn't necessarily needed in this case. This will save having to map(_:) over the characters of each name and bridging them to Objective-C. You can now just use a set of Characters, as they're Hashable.
For example:
struct Candy {
let name: String
}
let candies = [Candy(name: "CRAB"), Candy(name: "DUNGINESS"), Candy(name: "RAW")]
var filteredCandies = [Candy]()
func filterContentForSearchText(searchText: String) {
let searchCharacters = Set(searchText.lowercaseString.characters)
filteredCandies = candies.filter {Set($0.name.lowercaseString.characters).isSupersetOf(searchCharacters)}
tableView.reloadData()
}
filterContentForSearchText("RA")
print(filteredCandies) // [Candy(name: "CRAB"), Candy(name: "RAW")]
filterContentForSearchText("ED")
print(filteredCandies) // Candy(name: "DUNGINESS")]
Also depending on whether you can identify this as a performance bottleneck (you should do some profiling first) – you could potentially optimise the above further by caching the sets containing the characters of your 'candy' names, saving from having to recreate them at each search (although you'll have to ensure that they're updated if you update your candies data).
When you come to search, you can then use zip(_:_:) and flatMap(_:) in order to filter out the corresponding candies.
let candies = [Candy(name: "CRAB"), Candy(name: "DUNGINESS"), Candy(name: "RAW")]
// cached sets of (lowercased) candy name characters
let candyNameCharacterSets = candies.map {Set($0.name.lowercaseString.characters)}
var filteredCandies = [Candy]()
func filterContentForSearchText(searchText: String) {
let searchCharacters = Set(searchText.lowercaseString.characters)
filteredCandies = zip(candyNameCharacterSets, candies).flatMap {$0.isSupersetOf(searchCharacters) ? $1 : nil}
tableView.reloadData()
}
First of all a block of code like this
if someCondition == true {
return true
} else {
return false
}
can also be written this ways
return someCondition
right? :)
Refactoring
So your original code would look like this
func filterContentForSearchText(searchText: String) {
filteredCandies = candies.filter { $0.name.lowercaseString.containsString(searchText.lowercaseString) }
tableView.reloadData()
}
Scrambled search
Now, given a string A, your want to know if another string B contains all the character of A right?
For this we need CountedSet which is available from Swift 3. Since you are using Swift 2.2 we'll use the old NSCountedSet but some bridging to Objective-C is needed.
Here's the code.
struct Candy {
let name: String
}
let candies = [Candy]()
var filteredCandies = [Candy]()
func filterContentForSearchText(searchText: String) {
let keywordChars = NSCountedSet(array:Array(searchText.lowercaseString.characters).map { String($0) })
filteredCandies = candies.filter {
let candyChars = NSCountedSet(array:Array($0.name.lowercaseString.characters).map { String($0) }) as Set<NSObject>
return keywordChars.isSubsetOfSet(candyChars)
}
tableView.reloadData()
}
Swift 3 code update :
func filterContentForSearchText(searchText: String, scope: String = "All") {
filteredCandies = candies.filter { candy in
return candy.name.localizedLowercase.contains(searchText.lowercased())
}
tableView.reloadData()
}
I have the need to parse some unknown data which should just be a numeric value, but may contain whitespace or other non-alphanumeric characters.
Is there a new way of doing this in Swift? All I can find online seems to be the old C way of doing things.
I am looking at stringByTrimmingCharactersInSet - as I am sure my inputs will only have whitespace/special characters at the start or end of the string. Are there any built in character sets I can use for this? Or do I need to create my own?
I was hoping there would be something like stringFromCharactersInSet() which would allow me to specify only valid characters to keep
I was hoping there would be something like stringFromCharactersInSet() which would allow me to specify only valid characters to keep.
You can either use trimmingCharacters with the inverted character set to remove characters from the start or the end of the string. In Swift 3 and later:
let result = string.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789.").inverted)
Or, if you want to remove non-numeric characters anywhere in the string (not just the start or end), you can filter the characters, e.g. in Swift 4.2.1:
let result = string.filter("0123456789.".contains)
Or, if you want to remove characters from a CharacterSet from anywhere in the string, use:
let result = String(string.unicodeScalars.filter(CharacterSet.whitespaces.inverted.contains))
Or, if you want to only match valid strings of a certain format (e.g. ####.##), you could use regular expression. For example:
if let range = string.range(of: #"\d+(\.\d*)?"#, options: .regularExpression) {
let result = string[range] // or `String(string[range])` if you need `String`
}
The behavior of these different approaches differ slightly so it just depends on precisely what you're trying to do. Include or exclude the decimal point if you want decimal numbers, or just integers. There are lots of ways to accomplish this.
For older, Swift 2 syntax, see previous revision of this answer.
let result = string.stringByReplacingOccurrencesOfString("[^0-9]", withString: "", options: NSStringCompareOptions.RegularExpressionSearch, range:nil).stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
Swift 3
let result = string.replacingOccurrences( of:"[^0-9]", with: "", options: .regularExpression)
You can upvote this answer.
I prefer this solution, because I like extensions, and it seems a bit cleaner to me. Solution reproduced here:
extension String {
var digits: String {
return components(separatedBy: CharacterSet.decimalDigits.inverted)
.joined()
}
}
You can filter the UnicodeScalarView of the string using the pattern matching operator for ranges, pass a UnicodeScalar ClosedRange from 0 to 9 and initialise a new String with the resulting UnicodeScalarView:
extension String {
private static var digits = UnicodeScalar("0")..."9"
var digits: String {
return String(unicodeScalars.filter(String.digits.contains))
}
}
"abc12345".digits // "12345"
edit/update:
Swift 4.2
extension RangeReplaceableCollection where Self: StringProtocol {
var digits: Self {
return filter(("0"..."9").contains)
}
}
or as a mutating method
extension RangeReplaceableCollection where Self: StringProtocol {
mutating func removeAllNonNumeric() {
removeAll { !("0"..."9" ~= $0) }
}
}
Swift 5.2 • Xcode 11.4 or later
In Swift5 we can use a new Character property called isWholeNumber:
extension RangeReplaceableCollection where Self: StringProtocol {
var digits: Self { filter(\.isWholeNumber) }
}
extension RangeReplaceableCollection where Self: StringProtocol {
mutating func removeAllNonNumeric() {
removeAll { !$0.isWholeNumber }
}
}
To allow a period as well we can extend Character and create a computed property:
extension Character {
var isDecimalOrPeriod: Bool { "0"..."9" ~= self || self == "." }
}
extension RangeReplaceableCollection where Self: StringProtocol {
var digitsAndPeriods: Self { filter(\.isDecimalOrPeriod) }
}
Playground testing:
"abc12345".digits // "12345"
var str = "123abc0"
str.removeAllNonNumeric()
print(str) //"1230"
"Testing0123456789.".digitsAndPeriods // "0123456789."
Swift 4
I found a decent way to get only alpha numeric characters set from a string.
For instance:-
func getAlphaNumericValue() {
var yourString = "123456789!##$%^&*()AnyThingYouWant"
let unsafeChars = CharacterSet.alphanumerics.inverted // Remove the .inverted to get the opposite result.
let cleanChars = yourString.components(separatedBy: unsafeChars).joined(separator: "")
print(cleanChars) // 123456789AnyThingYouWant
}
A solution using the filter function and rangeOfCharacterFromSet
let string = "sld [f]34é7*˜µ"
let alphaNumericCharacterSet = NSCharacterSet.alphanumericCharacterSet()
let filteredCharacters = string.characters.filter {
return String($0).rangeOfCharacterFromSet(alphaNumericCharacterSet) != nil
}
let filteredString = String(filteredCharacters) // -> sldf34é7µ
To filter for only numeric characters use
let string = "sld [f]34é7*˜µ"
let numericSet = "0123456789"
let filteredCharacters = string.characters.filter {
return numericSet.containsString(String($0))
}
let filteredString = String(filteredCharacters) // -> 347
or
let numericSet : [Character] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
let filteredCharacters = string.characters.filter {
return numericSet.contains($0)
}
let filteredString = String(filteredCharacters) // -> 347
Swift 4
But without extensions or componentsSeparatedByCharactersInSet which doesn't read as well.
let allowedCharSet = NSCharacterSet.letters.union(.whitespaces)
let filteredText = String(sourceText.unicodeScalars.filter(allowedCharSet.contains))
let string = "+1*(234) fds567#-8/90-"
let onlyNumbers = string.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
print(onlyNumbers) // "1234567890"
or
extension String {
func removeNonNumeric() -> String {
return self.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
}
}
let onlyNumbers = "+1*(234) fds567#-8/90-".removeNonNumeric()
print(onlyNumbers)// "1234567890"
Swift 3, filters all except numbers
let myString = "dasdf3453453fsdf23455sf.2234"
let result = String(myString.characters.filter { String($0).rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789")) != nil })
print(result)
Swift 4.2
let numericString = string.filter { (char) -> Bool in
return char.isNumber
}
You can do something like this...
let string = "[,myString1. \"" // string : [,myString1. "
let characterSet = NSCharacterSet(charactersInString: "[,. \"")
let finalString = (string.componentsSeparatedByCharactersInSet(characterSet) as NSArray).componentsJoinedByString("")
print(finalString)
//finalString will be "myString1"
The issue with Rob's first solution is stringByTrimmingCharactersInSet only filters the ends of the string rather than throughout, as stated in Apple's documentation:
Returns a new string made by removing from both ends of the receiver characters contained in a given character set.
Instead use componentsSeparatedByCharactersInSet to first isolate all non-occurrences of the character set into arrays and subsequently join them with an empty string separator:
"$$1234%^56()78*9££".componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: "0123456789").invertedSet)).joinWithSeparator("")
Which returns 123456789
Swift 3
extension String {
var keepNumericsOnly: String {
return self.components(separatedBy: CharacterSet(charactersIn: "0123456789").inverted).joined(separator: "")
}
}
Swift 4.0 version
extension String {
var numbers: String {
return String(describing: filter { String($0).rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789")) != nil })
}
}
Swift 4
String.swift
import Foundation
extension String {
func removeCharacters(from forbiddenChars: CharacterSet) -> String {
let passed = self.unicodeScalars.filter { !forbiddenChars.contains($0) }
return String(String.UnicodeScalarView(passed))
}
func removeCharacters(from: String) -> String {
return removeCharacters(from: CharacterSet(charactersIn: from))
}
}
ViewController.swift
let character = "1Vi234s56a78l9"
let alphaNumericSet = character.removeCharacters(from: CharacterSet.decimalDigits.inverted)
print(alphaNumericSet) // will print: 123456789
let alphaNumericCharacterSet = character.removeCharacters(from: "0123456789")
print("no digits",alphaNumericCharacterSet) // will print: Vishal
Swift 4.2
let digitChars = yourString.components(separatedBy:
CharacterSet.decimalDigits.inverted).joined(separator: "")
Swift 3 Version
extension String
{
func trimmingCharactersNot(in charSet: CharacterSet) -> String
{
var s:String = ""
for unicodeScalar in self.unicodeScalars
{
if charSet.contains(unicodeScalar)
{
s.append(String(unicodeScalar))
}
}
return s
}
}
I would like to run a filter on a string. My first attempt failed as string is not automagically converted to Character[].
var s: String = "abc"
s.filter { $0 != "b" }
If I clumsily convert the String to Character[] with following code, it works as expected. But surely there has to be a neater way?
var cs:Character[] = []
for c in s {
cs = cs + [c]
}
cs = cs.filter { $0 != "b" }
println(cs)
String conforms to the CollectionType protocol, so you can pass it directly to the function forms of map and filter without converting it at all:
let cs = filter(s) { $0 != "f" }
cs here is an Array of Characters. You can turn it into a String by using the String(seq:) initializer, which constructs a String from any SequenceType of Characters. (SequenceType is a protocol that all lists conform to; for loops use them, among many other things.)
let filteredString = String(seq: cs)
Of course, you can just as easily put those two things in one statement:
let filteredString = String(seq: filter(s) { $0 != "f" })
Or, if you want to make a convenience filter method like the one on Array, you can use an extension:
extension String {
func filter(includeElement: Character -> Bool) -> String {
return String(seq: Swift.filter(self, includeElement))
}
}
(You write it "Swift.filter" so the compiler doesn't think you're trying to recursively call the filter method you're currently writing.)
As long as we're hiding how the filtering is performed, we might as well use a lazy filter, which should avoid constructing the temporary array at all:
extension String {
func filter(includeElement: Character -> Bool) -> String {
return String(seq: lazy(self).filter(includeElement))
}
}
I don't know of a built in way to do it, but you could write your own filter method for String:
extension String {
func filter(f: (Character) -> Bool) -> String {
var ret = ""
for character in self {
if (f(character)) {
ret += character
}
}
return ret
}
}
If you don't want to use an extension you could do this:
Array(s).filter({ $0 != "b" }).reduce("", combine: +)
You can use this syntax:
var chars = Character[]("abc")
I'm not 100% sure if the result is an array of Characters or not but works for my use case.
var str = "abc"
var chars = Character[](str)
var result = chars.map { char in "char is \(char)" }
result
The easiest way to convert a char to string is using the backslash (), for example I have a function to reverse a string, like so.
var identityNumber:String = id
for char in identityNumber{
reversedString = "\(char)" + reversedString
}