'advance' keyword in swift? - ios

let word = "sample string"
let firstLetter = Character(word.substringToIndex(advance(word.startIndex,1)).uppercaseString)
I got the above example from a tutorial. Can anyone know what they mean by "advance" and what is difference between "substringToIndex" and "substringWithRange".

This advance syntax is from Swift 1, it's different now.
Swift 2
let firstLetter = Character(word.substringToIndex(word.startIndex.advancedBy(1)).uppercaseString)
The advancedBy method moves the current index along the String.
With substringToIndex you slice a part of the String, beginning at the start of the String and ending at the index defined by advancedBy.
Here you advance by 1 in the String, so it means that substringToIndex will get the first character from the String.
Swift 3
The syntax has changed again, we now use substring and an index with an offset:
let firstLetter = Character(word.substring(to: word.index(word.startIndex, offsetBy: 1)).uppercased())

substringToIndex
Returns a new string containing the characters of the receiver up to,
but not including, the one at a given index.
Return Value A new string containing the characters of the receiver up to, but not including, the one at anIndex. If anIndex is
equal to the length of the string, returns a copy of the receiver.
substringWithRange
Returns a string object containing the characters of the receiver that
lie within a given range.
Return Value A string object containing the characters of the receiver that lie within aRange.
Special Considerations This method detects all invalid ranges (including those with negative lengths). For applications linked
against OS X v10.6 and later, this error causes an exception; for
applications linked against earlier releases, this error causes a
warning, which is displayed just once per application execution.
For more info detail, you can get in the Apple NSString Class Reference

Your tutorial is outdated. advance was deprecated in Swift 2. Strings in Swift cannot be randomly accessed, i.e. there's no word[0] to get the first letter of the string. Instead, you need an Index object to specify the position of the character. You create that index by starting with another index, usually the startIndex or endIndex of the string, then advance it to the character you want:
let word = "sample string"
let index0 = word.startIndex // the first letter, an 's'
let index6 = word.startIndex.advancedBy(6) // the seventh letter, the whitespace
substringToIndex takes all characters from the left of string, stopping before the index you specified. These two are equivalent:
print("'\(word.substringToIndex(index6))'")
print("'\(word[index0..<index6])'")
Both print 'sample'

Related

Getting "trailing characters" error when trying to parse string with serde_json

I need to convert a specific string in serde_json::Value, but getting a trailing character error while using:
let new_str = "73723235c81ebbec0"
let json_value: serde_json::Value = serde_json::from_str(new_str).unwrap();
Error:
trailing characters at line 1 column 9
Is there any way to convert these kinds of strings to serde_json::Value?
Looking at the repo for serde_json, it appears from their test cases this is expected behavior. For example, see this test case. Since your string starts with numeric values, it is attempting to parse as a number and then fails when it reaches the 'c' in position 9.
Rather than calling from_str(), use to_value(). This will give you a String variant of the Value enum.
let json_value: serde_json::Value = serde_json::to_value(&new_str).unwrap();
Also, if you use to_value(), you can omit the type identifier for your variable, which I believe is probably more idiomatic.
let json_value = serde_json::to_value(&new_str).unwrap();

New lines are not included to String count in iOS Swift 5

Here is a weird problem in iOS 14.1/Swift 5 that caused many griefs and consumed hours, until I understood what's going on. I still want to know if this is a bug or "by design". In the latter case, please provide a link describing the behavior and provide a list of other characters that are not counted.
Let us assume that I have a string like below:
let data = "l1\r\nl2\r\nl3"
I need to create an HTTP response manually and replace the Content-Length with the data length. I use a template for that:
static let RESPONSE =
"""
HTTP/1.1 200 OK
Content-Length: LENGTH
Content-Type: text/plain
DATA
""".trimmingCharacters(in: .whitespaces)
Finally, I create a response from a template:
let response = RESPONSE.replacingOccurrences(of: ": LENGTH", with: ": \(data.count)")
.replacingOccurrences(of:"DATA", with:data)
As a result, Content-Length was set to 8, not to 10, and a client didn't receive "l3".
Please note that the string with carriage returns has been generated by Apple's own BASE64 API, so there is nothing "special" that I did here myself:
Data(digest).base64EncodedString(options: [.lineLength64Characters])
Very strange behavior that I didn't see in any other languages.
Swift treats the combination of \r\n as a single newline character (abbreviated in the docs to CR-LF).
let combo = Character('\r\n')
print(combo.isNewline) // true
So when you convert this Character to a String and count it you get the answer one.
print(String(combo).count) // 1
Character has no count because by definition it represents a single user-perceived character even if it is constructed from a number of components.
I guess Swift's developers decided that the count property of String should output the number of user perceived characters, and since \r\n to all intents and purposes has the same effect has a single newline character it is counted as a single character.
Note however that String does not throw away the data from which it was constructed; you can still get the 'raw' count property that is most relevant to your case via the unicodeScalars property.
let data = "l1\r\nl2\r\nl3"
print(data.count) // 8
print(data.unicodeScalars.count) // 10
By the way, it's not just CR-LF that gets this special treatment; national flag emojis are a single user perceived character that are actually composed of two scalars.
let unionJack = Character("🇬🇧")
for scalar in unionJack.unicodeScalars {
print(String(scalar.value, radix: 16).uppercased() )
}
// 1F1EC
// 1F1E7
Change data.count to data.utf16.count in order to get the "outside world" view of how "long" the string is.
(Alternatively you could say (data as NSString).length.)

Incorrect implementation of String.removeSubrange?

I came across a weird behaviour with the String.removeSubrange function.
This is what the documentation says:
Removes the characters in the given range.
Parameters
bounds The range of the elements to remove. The upper and lower bounds
of bounds must be valid indices of the string and not equal to the
string’s end index.
bounds The range of the elements to remove. The upper and lower bounds
of bounds must be valid indices of the string.
The documentation already states that the range can not include the endIndex of the string, but I think that should be changed.
Lets look at an example why.
I have a string "12345" and I want to remove the first three characters which would result in "45".
The code for that is the following:
// Remove the characters 123
var str = "12345"
let endRemoveIndex = str.index(str.startIndex, offsetBy: 2)
str.removeSubrange(str.startIndex...endRemoveIndex)
So far so good I just create a closed range from the startIndex to the startIndex advanced by 2.
Lets say I want to remove the characters "345" I would expect the following code to work:
str = "12345"
let startRemoveIndex = endRemoveIndex
str.removeSubrange(startRemoveIndex...str.endIndex)
However this does not work as the documentation has already mentioned.
This results in fatalError saying
Can't advance past endIndex
The code that works for removing the last three characters is the following:
// Remove the characters 345
str = "12345"
let startRemoveIndex = endRemoveIndex
str.removeSubrange(startRemoveIndex..<str.endIndex)
That in my opinion is syntactically incorrect, because the half range operator implies that the maximum will not be included, but in this case it is.
What do you think about that?
Hamish pointed out that the String.endIndex is a “past the end” position which is the position one greater than the last valid subscript argument.

Swift: getting character from string (ascii value)

I'm trying to get the character value in ascii and also the character at index.
I have this Objective-C any of you would know the conversion to swift?
po [strToSort characterAtIndex:i] // character x
U+0078 u'x'
po [strToSort UTF8String][i]
x
I'll really appreciate your help.
Updated: you can directly subscript string with an Index
Swift doesn't allow you to subscript Strings with an Integer index. Instead you can construct an index to pass in.
let str = "String with some characters"
let index = str.startIndex.advancedBy(5)
let character = str[index]
print(character) // "g"
For more information on why you can't treat strings as a direct sequence of characters, you can find more info here.
Essentially to be properly unicode compliant, sometimes multiple characters can be combined to create a single character in the final string. This causes issues with naive counting and indexing.
If you want a utf8 representation of the string, String provides a utf8 property as well as a unicodeScalars property for getting the code point for each character.

from list to string and back to list

I have read a multiline file and converted it to a list with the following code:
Lines = string:tokens(erlang:binary_to_list(Binary), "\n"),
I converted it to a string to do some work on it:
Flat = string:join(Lines, "\r\n"),
I finished working on the string and now I need to convert it back to a multiline list, I tried to repeat the first snippet shown above but that never worked, I tried string:join and that didnt work.. how do i convert it back to a list just like it used to be (although now modified)?
Well that depends on the modifications you made on the flattened string.
string:tokens/2 will always explode a string using the separator you provide. So as long as your transformation preserves a specific string as separator between the individual substrings there should be no problem.
However, if you do something more elaborate and destructive in your transformation then the only way is to iterate on the string manually and construct the individual substrings.
Your first snippet above contains a call to erlang:binary_to_list/1 which first converts a binary to a string (list) which you then split with the call to string:tokens/2 which then join together with string:join/2. The result of doing the tokens then join as you have written it seems to be to convert it from a string containing lines separated by \n into one containing lines separated by \r\n. N.B. that this is a flat list of characters.
Is this what you intended?
What you should do now depends on what you mean by "I need to convert it back to a multiline list". Do you mean everything in a single list of characters (string), or in a nested list of lines where each line is a list of characters (string). I.e. if you ended up with
"here is line 1\r\nhere is line 2\r\nhere is line 3\r\n"
this already is a multiline line list, or do you mean
["here is line 1","here is line 2","here is line 3"]
Note that each "string" is itself a list of characters. What do you intend to do with it afterwards?
You have your terms confused. A string in any language is a sequence of integer values corresponding to a human-readable characters. Whether the representation of the value is a binary or a list does not matter, both are technically strings because of the data they contain.
That being said, you converted a binary string to a list string in your first set of instructions. To convert a list into a binary, you can call erlang:list_to_binary/1, or erlang:iolist_to_binary/1 if your list is not flat. For instance:
BinString = <<"this\nis\na\nstring">>.
ListString = "this\nis\na\nstring" = binary_to_list(BinString).
Words = ["this", "is", "a", "string"] = string:tokens(ListString, "\n").
<<"thisisastring">> = iolist_to_binary(Words).
Rejoined = "this\r\nis\r\na\r\nstring" = string:join(Words, "\r\n").
BinAgain = <<"this\r\nis\r\na\r\nstring">> = list_to_binary(Rejoined).
For your reference, the string module always expects a flat list (e.g., "this is a string", but not ["this", "is", "a", "string"]), except for string:join, which takes a list of flat strings.

Resources