Swift optional string splitting into array problem - swift

I have a function that takes second from my controller and introInterval from network service. But my codes does not split string that coming from API. But if i put dummy data, it works. I dont know why. Where is the problem ? Both of them works with same string. My aim is to catch up same data with test variable. You can see outputs of them in print lines.
func getCurrentPlayerTimeAndIntroDuration(second: String, introInterval: String?) {
if let introInterval = introInterval {
print(introInterval) //output: "01:38 - 5:00"
let test = "01:38 - 5:00".trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: "-")
let introIntervalArray = introInterval.trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: "-")
print("test \(test)") //output: ["01:38", "5:00"] correct
print("introIntervalArray \(introIntervalArray)") //output introIntervalArray ["01:38–02:13"] wrong
}
}

I believe you're using the wrong character to separate the string. If you compare the - (Non-ASCII) character from your example //output introIntervalArray ["01:38–02:13"] wrong you will see the – is different than the one you are using to split the string - (ASCII).
This code:
let test = "01:38–02:13".trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: "–")
Outputs:
["01:38", "02:13"]

Related

Explanation of lastIndex of: and firstIndex of: used in a string in Swift

I am solving a programming problem in Swift and I found a solution online which I don't totally understand, the problem is: Write a function that reverses characters in (possibly nested) parentheses in the input string. the solution is
var inputString = "foo(bar)baz(ga)kjh"
var s = inputString
while let openIdx = s.lastIndex(of: "(") {
let closeIdx = s[openIdx...].firstIndex(of:")")!
s.replaceSubrange(openIdx...closeIdx, with: s[s.index(after: openIdx)..<closeIdx].reversed())
}
print (s) // output: foorabbazagkjh (the letters inside the braces are reversed)
I d like to have details about: lastIndex(of: does in this case
and what let closeIdx = s[openIdx...].firstIndex(of:")")! does as well
The best place to experiment with these kinds of questions would Playground. Also, check out the documentation.
Now let go through each of the statement:
let openIdx = s.lastIndex(of: "(") // it will find the last index of "(", the return type here is Array.Index?
so if I print the value after with index including till end of string, it would be
print(s[openIdx!...]) // `!` exclamation is used for forced casting
// (ga)kjh
Now for your second question;
let closeIdx = s[openIdx...].firstIndex(of:")")!
Let break it down s[openIdx...] is equal to (ga)kjh in first iteration and so it will return the index of ) after a.
The suggestion would be always break the statement and learn what each expression is doing.

Make extension for String

I got homework and I can't handle it. What I need?
I have a project that uses two languages (English, Spanish). The project has 2 Locolizable.strings files for two languages.
Example string:
"OrderDetails_IPText" = "IP: %#";
I understand %# is a string or some object, it does not matter. The problem is in people who help me with the translation of texts into different languages.
When they fill in the translation file, they see:
%#
They do not understand what I want to add there. This could be an email address or something else. People who translate the text gave me the task to implement a function that will take into account such nuances. They even offered some implementation, something like this:
func pffffff(format: something, ["key" : value] -> Id : value
Probably it should be an extension for String.
If you do not understand, thanks for watching this question. I did not understand anything.
We advised that you need to change this func:
func L (_ key: String, value: String = "") -> String
{
let str = NSLocalizedString(key, value: value, comment: "")
return str
}
You can create something like this.
extension String {
func yourFunction () {}
}
But I would recommend you not to use %# or any other character in localization string. You can always use replace string function with when the string contains any variable
For eg:
"We have sent an OTP at [VARIABLEA]"
Then while displaying just look for [VARIABLEA] and replace with actual value
I found a way out of this situation.
public extension String {
/* Creates the string representation of the poo with requested size.
- parameter format: string format with key
- returns: localizable string
*/
public init(format: String, keyArguments: [String: Any]) {
self = format
keyArguments.forEach {
self = self.replacingOccurrences(of: "{\($0.key)}", with: "\($0.value)", options: .caseInsensitive)
}
}
}
Was:
let asd = String(format: "Hi, %#! %d", "Arnold", 2)
Now:
let str = String(format: "Hi, {User_Name}! How are you, {user_name}?", keyArguments: ["user_name" : "Arnold", "number": 5.6])

Swift, stringWithFormat, %s gives strange results

I searched for an answer the entire day but nothing really came close to answering my issue. I am trying to use stringWithFormat in Swift but while using printf format strings. The actual issue I have is with the %s. I can't seem to get to the original string no matter how I try this.
Any help would be much much appreciated (or workarounds).
Things I already did: tried all available encodings for the cString, tried creating an ObjC function to use for this, but when I passed the arguments from Swift I ran into the same strange issue with the %s, even if when hardcoded in the ObjC function body it appears to print the actual correct String.
Please find bellow the sample code.
Many thanks!
var str = "Age %2$i, Name: %1$s"
let name = "Michael".cString(using: .utf8)!
let a = String.init(format: str, name, 1234)
Expected result is quite clear I presume, however I get something like this instead of the correct name:
"Age 1234, Name: ÿQ5"
Use withCString() to invoke a function with the C string
representation of a Swift string. Also note that %ld is the correct
format for a Swift Int (which can be a 32-bit or 64-bit integer).
let str = "Age %2$ld, Name: %1$s"
let name = "Michael"
let a = name.withCString { String(format: str, $0, 1234) }
print(a) // Age 1234, Name: Michael
Another possible option would be to create a (temporary) copy
of the C string representation
(using the fact a Swift string is automatically converted to a C string when passed to a C function taking a const char * parameter,
as explained in String value to UnsafePointer<UInt8> function parameter behavior):
let str = "Age %2$ld, Name: %1$s"
let name = "Michael"
let nameCString = strdup(name)!
let a = String(format: str, nameCString, 1234)
print(a)
free(nameCString)
I assume that your code does not work as expected because name
(which has type [CChar] in your code) is bridged to an NSArray,
and then the address of that array is passed to the string
formatting method.
Use "%1$#" instead of "%1$s", and don't use the cString call.
This works for me:
var str = "Age %2$i, Name: %1$#"
let name = "Michael"
let a = String.init(format: str, name, 1234)

How to get multiple lines of stdin Swift HackerRank?

I just tried out a HackerRank challenge, and if a question gives you x lines of input, putting x lines of let someVariable = readLine() simply doesn't cut it, because there are lot's of test cases that shoot way more input to the code we write, so hard coded readLine() for each line of input won't fly.
Is there some way to get multiple lines of input into one variable?
For anyone else out there who's trying a HackerRank challenge for the first time, you might need to know a couple of things that you may have never come across. I only recently learned about this piece of magic called the readLine() command, which is a native function in Swift.
When the HackerRank system executes your code, it passes your code lines of input and this is a way of retrieving that input.
let line1 = readLine()
let line2 = readLine()
let line3 = readLine()
line1 is now given the value of the first line of input mentioned in the question (or delivered to your code by one of the test cases), with line2 being the second and so on.
Your code may work just great but may fail on a bunch of other test cases. These test cases don't send your code the same number of lines of input. Here's food for thought:
var string = ""
while let thing = readLine() {
string += thing + " "
}
print(string)
Now the string variable contains all the input there was to receive (as a String, in this case).
Hope that helps someone
:)
Definitely you shouldn't do this:
while let readString = readLine() {
s += readString
}
This because Swift will expect an input string (from readLine) forever and will never terminate, causing your application die by timeout.
Instead you should think in a for loop assuming you know how many lines you need to read, which is usually this way in HackerRank ;)
Try something like this:
let n = Int(readLine()!)! // Number of test cases
for _ in 1 ... n { // Loop from 1 to n
let line = readLine()! // Read a single line
// do something with input
}
If you know that each line is an integer, you can use this:
let line = Int(readLine()!)!
Or if you know each line is an array of integers, use this:
let line = readLine()!.characters.split(" ").map{ Int(String($0))! }
Or if each line is an array of strings:
let line = readLine()!.characters.split(" ").map{ String($0) }
I hope this helps.
For new version, to get an array of numbers separated by space
let numbers = readLine()!.components(separatedBy: [" "]).map { Int($0)! }
Using readLine() and AnyGenerator to construct a String array of the std input lines
readLine() will read from standard input line-by-line until EOF is hit, whereafter it returns nil.
Returns Characters read from standard input through the end of the
current line or until EOF is reached, or nil if EOF has already been
reached.
This is quite neat, as it makes readLine() a perfect candidate for generating a sequence using the AnyGenerator initializer init(body:) which recursively (as next()) invokes body, terminating in case body equals nil.
AnyGenerator
init(body: () -> Element?)
Create a GeneratorType instance whose next method invokes body
and returns the result.
With this, there's no need to actually supply the amount of lines we expect from standard input, and hence, we can catch all input from standard input e.g. into a String array, where each element corresponds to an input line:
let allLines = AnyGenerator { readLine() }.map{ $0 }
// type: Array<String>
After which we can work with the String array to apply whatever operations needed to solve a given task (/HackerRank task).
// example standard input
4 3
<tag1 value = "HelloWorld">
<tag2 name = "Name1">
</tag2>
</tag1>
tag1.tag2~name
tag1~name
tag1~value
/* resulting allLines array:
["4 3", "<tag1 value = \"HelloWorld\">",
"<tag2 name = \"Name1\">",
"</tag2>",
"</tag1>",
"tag1.tag2~name",
"tag1~name",
"tag1~value"] */
I recently discovered a neat trick to get a certain amount of lines. I'm gonna assume the first line gives you the amount of lines you get:
guard let count = readLine().flatMap({ Int($0) }) else { fatalError("No count") }
let lines = AnyGenerator{ readLine() }.prefix(count)
for line in lines {
}
I usually use this form.
if let line = readLine(), let cnt = Int(line) {
for _ in 1...cnt {
if let line = readLine() {
// your code for a line
}
}
}
Following the answer from dfrib, for Swift 3+, AnyIterator can be used instead of AnyGenerator, in the same way:
let allLines = AnyIterator { readLine() }.map{ $0 }
// type: Array<String>

Interpolate String Loaded From File

I can't figure out how to load a string from a file and have variables referenced in that string be interpolated.
Let's say a text file at filePath that has these contents:
Hello there, \(name)!
I can load this file into a string with:
let string = String.stringWithContentsOfFile(filePath, encoding: NSUTF8StringEncoding, error: nil)!
In my class, I have loaded a name in: let name = "George"
I'd like this new string to interpolate the \(name) using my constant, so that its value is Hello there, George!. (In reality the text file is a much larger template with lots of strings that need to be swapped in.)
I see String has a convertFromStringInterpolation method but I can't figure out if that's the right way to do this. Does anyone have any ideas?
This cannot be done as you intend, because it goes against type safety at compile time (the compiler cannot check type safety on the variables that you are trying to refer to on the string file).
As a workaround, you can manually define a replacement table, as follows:
// Extend String to conform to the Printable protocol
extension String: Printable
{
public var description: String { return self }
}
var string = "Hello there, [firstName] [lastName]. You are [height]cm tall and [age] years old!"
let firstName = "John"
let lastName = "Appleseed"
let age = 33
let height = 1.74
let tokenTable: [String: Printable] = [
"[firstName]": firstName,
"[lastName]": lastName,
"[age]": age,
"[height]": height]
for (token, value) in tokenTable
{
string = string.stringByReplacingOccurrencesOfString(token, withString: value.description)
}
println(string)
// Prints: "Hello there, John Appleseed. You are 1.74cm tall and 33 years old!"
You can store entities of any type as the values of tokenTable, as long as they conform to the Printable protocol.
To automate things further, you could define the tokenTable constant in a separate Swift file, and auto-generate that file by using a separate script to extract the tokens from your string-containing file.
Note that this approach will probably be quite inefficient with very large string files (but not much more inefficient than reading the whole string into memory on the first place). If that is a problem, consider processing the string file in a buffered way.
There is no built in mechanism for doing this, you will have to create your own.
Here is an example of a VERY rudimentary version:
var values = [
"name": "George"
]
var textFromFile = "Hello there, <name>!"
var parts = split(textFromFile, {$0 == "<" || $0 == ">"}, maxSplit: 10, allowEmptySlices: true)
var output = ""
for index in 0 ..< parts.count {
if index % 2 == 0 {
// If it is even, it is not a variable
output += parts[index]
}
else {
// If it is odd, it is a variable so look it up
if let value = values[parts[index]] {
output += value
}
else {
output += "NOT_FOUND"
}
}
}
println(output) // "Hello there, George!"
Depending on your use case, you will probably have to make this much more robust.