How to apply whitespaces in pound signed string in Swift? - swift

Swift 5 came up with an extra new approach to declare string literals, which is using # sign (before opening and closing quotes) to declare a string without worrying about backslashes and quote marks.
(Reference: https://github.com/apple/swift-evolution/blob/master/proposals/0200-raw-string-escaping.md)
For example:
let string1 = #"\ Hello World"#
print(string1) // => \ Hello World
let string2 = "Hello World #"
print(string2) // => Hello World #
let string3 = ##"#\#\ Hello World #\#\"##
print(string3) // => #\#\ Hello World #\#\
However, when attempting to use pound signs for declaring a string that contains a whitespace, it won't work. Let's consider the tab:
let tabString = #"Hello World\t#"#
print(tabString) // => Hello World\t#
My expected result is Hello World # but not Hello World\t#.
How to resolve such an issue when using # declaration?

Updated Answer:
At this point, you could type it as \#t.
Thanks for #dan for commenting this.
Old Answer:
Logically, the result of Hello World\t\ seems to be logical, because the purpose of using # for declaring a string is to "literally" deal with backslashes as backslashes, means that "\t" would be displayed as "\t" but not " ".
As a workaround, what you could do here is to interpolate the whitespace in your string as a string declared without using the pound sign:
let tabString = #"Hello World \#("\t")#"#
print(tabString) // => Hello World #
Keep in mind that when interpolating inside a #""# declared string, you should add # after the backslash (\#("\t") NOT \("\t")).

Related

regex works in online tool but doesn't agree with NSRegularExpression

do {
// initialization failed, looks like I can not use "\\" here
let regex = try NSRegularExpression.init(pattern: "(?<!\\)\n")
let string = """
aaabbb
zzz
"""
// expect "aaabbb\nzzz"
print(regex.stringByReplacingMatches(in: string, options: [], range: NSMakeRange(0, string.count), withTemplate: "\\n"))
} catch let error {
print(error)
}
Here I want to replace "\n" in my string with "\\n", but failed at the very beginning, the error message is
// NSRegularExpression did not recognize the pattern correctly.
Error Domain=NSCocoaErrorDomain Code=2048 "The value “(?<!\)
” is invalid." UserInfo={NSInvalidValue=(?<!\)
}
The regex has been tested in regular expression 101, so it is right, just doesn't work in Swift for some reason.
How can I do this?
Base on Larme's comment:
in Swift, \ (double back slash) in a String is for "having a ``, as you see in the error, you have (?<!\), but it means then that you are escaping the closing ), so you have a missing closing ). I'd say that you should write then "(?<!\\\\)\n"?
I finally figured out what's going on and how to fix it.
The problem is backslash.
In Swift, a backslash inside double quotation mark would be treated as escape sequence, like this
// won't compile
// error: Invalid escape sequence in literal
let regex = try NSRegularExpression.init(pattern: "(?<!\)\n")
If we add another backslash, is it work?
No, cause these 2 backslashes would be treated as a single escape character for the upcoming closing ).
// compile but get a runtime error
let regex = try NSRegularExpression.init(pattern: "(?<!\\)\n")
Hence the runtime error
NSRegularExpression did not recognize the pattern correctly.
Error Domain=NSCocoaErrorDomain Code=2048 "The value “(?<!\)
” is invalid." UserInfo={NSInvalidValue=(?<!\)
To show that what we need is a literal backslash, we actually need 4 backslashes
let regex = try NSRegularExpression.init(pattern: "(?<!\\\\)\n")
The first two backslashes represent an escape character and the last two represent one literal backslash.
These seem very troublesome.
Better Approach
Fortunately, starting with Swift 5, we can use a pair of # to do this
// works like in online tool
let regex = try NSRegularExpression.init(pattern: #"(?<!\\)\n"#)
Another thing
It’s worth noticing that the initialization of regular expression isn’t the only thing that requires special handling
// withTemplate
print(regex.stringByReplacingMatches(in: string, options: [], range: NSMakeRange(0, string.count), withTemplate: #"\\n"#))
// As a comparison, this is OK
print(string.replacingOccurrences(of: "\n", with: "\\N"))

Convert a string JSON to a dictionary in Swift when curly quotes are used

I have a string JSON, but it has fancy curly quotes in it which makes NSJSONSerialization fail.
let str = "{“title”:\"this is a “test” example\"}"
try! JSONSerialization.jsonObject(with: str.data(using: .utf8)!) // Error
The quotes around title are curly double quotes and apparently JSONSerialization can not handle it and fails. A naive approach would be to simple replace all instances of the curly quote with a non-curly one. The problem with that approach is that it will change the curly quotes around test which shouldn't be changed! The quotes around title are OK to be changed but the ones around test should not.
What can I do to get around this issue?
To fix this, you talk to whoever created the string, which does not contain JSON at the moment, and convince them to create a string that does contain JSON.
For JSON the rule is: If your parser can't parse it, then it's broken and you don't touch it.
The problem isn't that JSONSerialization cannot handle it. The problem is that JSONSerialization absolutely must not under any circumstances handle it. Because it's not JSON.
If curly quotes are only used for the keys, this regex will do the job:
let str = "{“title”:\"this is a “test” example\"}"
let strFixed = str.replacingOccurrences(
of: #"“(.[^”]*)”:\"(.[^\"]*)\""#,
with: "\"$1\":\"$2\"",
options: .regularExpression
)
// It's strongly recommended to use try/catch instead of force unwrapping.
let json = try! JSONSerialization.jsonObject(with: strFixed.data(using: .utf8)!)
If we print json, we get the correct result:
{
title = "this is a \U201ctest\U201d example";
}
Explanation
“(.[^”]*)”:\"(.[^\"]*)\"
------------------------
“(.[^”]*)” match everything between curly braces,
except the closing curling brace character
: separator between keys and values
\"(.[^\"]*)\" match everything between double quotes,
except the double quote character
\"$1\":\"$2\"
-------------
\"$1\" place the first captured group between double quotes
: separator between keys and values
\"$2\" place the second captured group between double quotes

Swift: Simple method to replace a single character in a String?

I wanted to replace the first character of a String and got it to work like this:
s.replaceSubrange(Range(NSMakeRange(0,1),in:s)!, with:".")
I wonder if there is a simpler method to achieve the same result?
[edit]
Get nth character of a string in Swift programming language doesn't provide a mutable substring. And it requires writing a String extension, which isn't really helping when trying to shorten code.
To replace the first character, you can do use String concatenation with dropFirst():
var s = "😃hello world!"
s = "." + s.dropFirst()
print(s)
Result:
.hello world!
Note: This will not crash if the String is empty; it will just create a String with the replacement character.
Strings work very differently in Swift than many other languages. In Swift, a character is not a single byte but instead a single visual element. This is very important when working with multibyte characters like emoji (see: Why are emoji characters like 👩‍👩‍👧‍👦 treated so strangely in Swift strings?)
If you really do want to set a single random byte of your string to an arbitrary value as you expanded on in the comments of your question, you'll need to drop out of the string abstraction and work with your data as a buffer. This is sort of gross in Swift thanks to various safety features but it's doable:
var input = "Hello, world!"
//access the byte buffer
var utf8Buffer = input.utf8CString
//replace the first byte with whatever random data we want
utf8Buffer[0] = 46 //ascii encoding of '.'
//now convert back to a Swift string
var output:String! = nil //buffer for holding our new target
utf8Buffer.withUnsafeBufferPointer { (ptr) in
//Load the byte buffer into a Swift string
output = String.init(cString: ptr.baseAddress!)
}
print(output!) //.ello, world!

component(separatedBy:) versus .split(separator: )

In Swift 4, new method .split(separator:) is introduced by apple in String struct. So to split a string with whitespace which is faster for e.g..
let str = "My name is Sudhir"
str.components(separatedBy: " ")
//or
str.split(separator: " ")
Performance aside, there is an important difference between split(separator:) and components(separatedBy:) in how they treat empty subsequences.
They will produce different results if your input contains a trailing whitespace:
let str = "My name is Sudhir " // trailing space
str.split(separator: " ")
// ["My", "name", "is", "Sudhir"]
str.components(separatedBy: " ")
// ["My", "name", "is", "Sudhir", ""] ← Additional empty string
To have both produce the same result, use the omittingEmptySubsequences:false argument (which defaults to true):
// To get the same behavior:
str.split(separator: " ", omittingEmptySubsequences: false)
// ["My", "name", "is", "Sudhir", ""]
Details here:
https://developer.apple.com/documentation/swift/string/2894564-split
I have made sample test with following Code.
var str = """
One of those refinements is to the String API, which has been made a lot easier to use (while also gaining power) in Swift 4. In past versions of Swift, the String API was often brought up as an example of how Swift sometimes goes too far in favoring correctness over ease of use, with its cumbersome way of handling characters and substrings. This week, let’s take a look at how it is to work with strings in Swift 4, and how we can take advantage of the new, improved API in various situations. Sometimes we have longer, static strings in our apps or scripts that span multiple lines. Before Swift 4, we had to do something like inline \n across the string, add an appendOnNewLine() method through an extension on String or - in the case of scripting - make multiple print() calls to add newlines to a long output. For example, here is how TestDrive’s printHelp() function (which is used to print usage instructions for the script) looks like in Swift 3 One of those refinements is to the String API, which has been made a lot easier to use (while also gaining power) in Swift 4. In past versions of Swift, the String API was often brought up as an example of how Swift sometimes goes too far in favoring correctness over ease of use, with its cumbersome way of handling characters and substrings. This week, let’s take a look at how it is to work with strings in Swift 4, and how we can take advantage of the new, improved API in various situations. Sometimes we have longer, static strings in our apps or scripts that span multiple lines. Before Swift 4, we had to do something like inline \n across the string, add an appendOnNewLine() method through an extension on String or - in the case of scripting - make multiple print() calls to add newlines to a long output. For example, here is how TestDrive’s printHelp() function (which is used to print usage instructions for the script) looks like in Swift 3
"""
var newString = String()
for _ in 1..<9999 {
newString.append(str)
}
var methodStart = Date()
_ = newString.components(separatedBy: " ")
print("Execution time Separated By: \(Date().timeIntervalSince(methodStart))")
methodStart = Date()
_ = newString.split(separator: " ")
print("Execution time Split By: \(Date().timeIntervalSince(methodStart))")
I run above code on iPhone6 , Here are the results
Execution time Separated By: 8.27463299036026
Execution time Split By: 4.06880903244019
Conclusion : split(separator:) is faster than components(separatedBy:).
Maybe a little late to answer:
split is a native swift method
components is NSString Foundation method
When you play with them, they behave a little bit different:
str.components(separatedBy: "\n\n")
This call can give you some interesting results
str.split(separator: "\n\n")
This leads to an compile error as you must provide a single character.

Can you use string/character literals within Swift string interpolation?

Is it possible to use a String/Character literal within string interpolation in Swift?
The language reference says:
The expression you write inside parentheses within an interpolated string cannot contain an unescaped double quote (") ...
That's a little fuzzy to me, since it seems to deliberately leave the loophole of escaped double quotes.
If I try:
println( "Output: \(repeat("H",20))" );
func repeat( char:Character, times:Int ) -> String {
var output:String = "";
for index in 1...times {
output += char;
}
return output;
}
I get "Expected ',' separator".
Similarly, if I do the same thing, but escape the quotes, still no dice:
println( "Output: \(repeat(\"H\",20))" );
I suspect it's just not possible, and to be honest, no big deal -- I haven't found any example that I can't easily solve by doing a little work before the string interpolation, I guess I'm just looking for confirmation that it's just not possible.
It can be done starting in Swift 2.1:
http://www.russbishop.net/swift-2-1
Prior to that, it wasn't possible.
I hope it's:
let strfunc = repeat("H",20) as string
println( "Output: \(strfunc)" );