Positions of a Character in a String with Swift 2 - swift

I'm making a string extension for finding multiple positions a character can occur in a string. This is my code:
let letter: Character = "l"
extension String {
func checkLetter(letter: Character) {
if let index = self.rangeOfString(AString: String(letter), range: Range<Int>(start: 2, end: 5) ) {
print(index)
}
}
}
I'm just completely lost on how to fill in that range part. There keep getting errors. I want to write a while loop which checks for the index of a character in a string. When found it will update a variabele which I can insert in range so it skips the part next time in the whole loop that contained the position of the character found before. Hope this makes it a bit clear. Here's some pseaduo code:
extension func
let range = 0
while loop: checks if character is in string
update range to index
append index to NSarray list
return nsarray list and if none found return nil

This is your extension written following the Functional Programming approach.
extension String {
func indexesOfChar(c: Character) -> [Int] {
return characters
.enumerate()
.filter { $0.element == c }
.map { $0.index }
}
}
Test
"Luminetic Land".indexesOfChar("i") // [3, 7]

Just for reference yourString.characters.indexOf("a") will give you the index of the first appearance of "a" in yourString. You could use this in a while loop to find "a" in the range from the previous index of "a" plus one and then add the indexes to an array until the output is negative one.

Related

Swift. How to get the previous character?

For example: I have character "b" and I what to get "a", so "a" is the previous character.
let b: Character = "b"
let a: Character = b - 1 // Compilation error
It's actually pretty complicated to get the previous character from Swift's Character type because Character is actually comprised of one or more Unicode.Scalar values. Depending on your needs you could restrict your efforts to just the ASCII characters. Or you could support all characters comprised of a single Unicode scalar. Once you get into characters comprised of multiple Unicode scalars (such as the flag Emojis or various skin toned Emojis) then I'm not even sure what the "previous character" means.
Here is a pair of methods added to a Character extension that can handle ASCII and single-Unicode scalar characters.
extension Character {
var previousASCII: Character? {
if let ascii = asciiValue, ascii > 0 {
return Character(Unicode.Scalar(ascii - 1))
}
return nil
}
var previousScalar: Character? {
if unicodeScalars.count == 1 {
if let scalar = unicodeScalars.first, scalar.value > 0 {
if let prev = Unicode.Scalar(scalar.value - 1) {
return Character(prev)
}
}
}
return nil
}
}
Examples:
let b: Character = "b"
let a = b.previousASCII // Gives Optional("a")
let emoji: Character = "😆"
let previous = emoji.previousScalar // Gives Optional("😅")

Count number of characters between two specific characters

Trying to make a func that will count characters in between two specified char like:
count char between "#" and "." or "#" and ".com"
If this is only solution could this code be written in a simple way with .count or something less confusing
func validateEmail(_ str: String) -> Bool {
let range = 0..<str.count
var numAt = Int()
numDot = Int()
if str.contains("#") && str.contains(".") && str.characters.first != "#" {
for num in range {
if str[str.index(str.startIndex, offsetBy: num)] == "#" {
numAt = num
print("The position of # is \(numAt)")
} else if
str[str.index(str.startIndex, offsetBy: num)] == "." {
numDot = num
print("The position of . is \(numDot)")
}
}
if (numDot - numAt) > 1 {
return true
}
}
return false
}
With help from #Βασίλης Δ. i made a direct if statement for func validateEmail that check if number of char in between are less than 1
if (str.split(separator: "#").last?.split(separator: ".").first!.count)! < 1{
return false
}
It could be usefull
There are many edge cases to what you're trying to do, and email validation is notoriously complicated. I recommend doing as little of it as possible. Many, many things are legal email addresses. So you will need to think carefully about what you want to test. That said, this addresses what you've asked for, which is the distance between the first # and the first . that follows it.
func lengthOfFirstComponentAfterAt(in string: String) -> Int? {
guard
// Find the first # in the string
let firstAt = string.firstIndex(of: "#"),
// Find the first "." after that
let firstDotAfterAt = string[firstAt...].firstIndex(of: ".")
else {
return nil
}
// Return the distance between them (not counting the dot itself)
return string.distance(from: firstAt, to: firstDotAfterAt) - 1
}
lengthOfFirstComponentAfterAt(in: "rob#example.org") // Optional(7)
There's a very important lesson about Collections in this code. Notice the expression:
string[firstAt...].firstIndex(of: ".")
When you subscript a Collection, each element of the resulting slice has the same index as in the original collection. The returned value from firstIndex can be used directly to subscript string without offsetting. This is very different than how indexes work in many other languages, and allows powerful algorithms, and also creates at lot of bugs when developers forget this.

Inserting a character in String (Swift)

I have a String "000". I want to change this to "0.00".
I took a look at the insert function.
var str = "000"
str.insert(".", at: str.endIndex)
How do I get the index of 2 before the end index?
I tried:
str.insert(".", at: str.endIndex - 1)
but this didn't work at all.
You could also use Strings character property. Its basically an array made up of all the characters (duh) in the String.
So you would:
var str = "000"
let index = str.characters.index(str.characters.startIndex, offsetBy: 1) //here you define a place (index) to insert at
str.characters.insert(".", at: index) //and here you insert
Unfortunately you have to create an index first, as .insert does not allow you to specify the position using an Int.
Since Swift 2, String does no longer conform to SequenceType. In other words, you can not iterate through a string with a for...in loop.
The simple and easy way is to convert String to Array to get the benefit of the index just like that:
let input = Array(str)
I remember when I tried to index into String without using any conversion. I was really frustrated that I couldn’t come up with or reach a desired result, and was about to give up.
But I ended up creating my own workaround solution, and here is the full code of the extension:
extension String {
subscript (_ index: Int) -> String {
get {
String(self[self.index(startIndex, offsetBy: index)])
}
set {
if index >= count {
insert(Character(newValue), at: self.index(self.startIndex, offsetBy: count))
} else {
insert(Character(newValue), at: self.index(self.startIndex, offsetBy: index))
}
}
}
}
Now that you can read and write a single character from string using its index just like you originally wanted to:
var str = "car"
car[3] = "d"
print(str)
It’s simple and useful way to use it and get through Swift’s String access model.
Now that you’ll feel it’s smooth sailing next time when you can loop through the string just as it is, not casting it into Array.
Try it out, and see if it can help!

rindex in Swift 3

I think, there is no builtin function rindex (like in perl) to get the position of the last occurrence of a character in a string.
I tried in a naive way like this
func rindex (_ s:String, _ needle:Character) -> Int?
{ var pos = s.characters.count
for char in s.characters.reversed()
{
if (char == needle)
{ return pos;
}
pos -= 1
}
return nil
}
Is there a more elegant way for this?
First of all, I would recommend making this an extension of String (or even String.CharacterView) rather than a free-floating function. Also, given that strings are indexed by String.Index rather than Int, I would advise returning that instead.
You could then implement it like so:
extension String {
func lastIndex(of character: Character) -> Index? {
return characters.indices.reversed().first(where: {self[$0] == character})
}
}
indices gives you the indices of the string's characters.
reversed() gives you a reversed view onto these indices.
first(where:) iterates through the these reversed indices until it finds the index where the element at that index is the character you're looking for.
You can then use it like so:
let string = "foobarbaz"
if let index = string.lastIndex(of: "a") {
print(index) // Index(_base: Swift.String.UnicodeScalarView.Index(_position: 7), _countUTF16: 1)
print(string[index]) // "a"
}

Swift 3.0 iterate over String.Index range

The following was possible with Swift 2.2:
let m = "alpha"
for i in m.startIndex..<m.endIndex {
print(m[i])
}
a
l
p
h
a
With 3.0, we get the following error:
Type 'Range' (aka 'Range') does not conform to protocol 'Sequence'
I am trying to do a very simple operation with strings in swift -- simply traverse through the first half of the string (or a more generic problem: traverse through a range of a string).
I can do the following:
let s = "string"
var midIndex = s.index(s.startIndex, offsetBy: s.characters.count/2)
let r = Range(s.startIndex..<midIndex)
print(s[r])
But here I'm not really traversing the string. So the question is: how do I traverse through a range of a given string. Like:
for i in Range(s.startIndex..<s.midIndex) {
print(s[i])
}
You can traverse a string by using indices property of the characters property like this:
let letters = "string"
let middle = letters.index(letters.startIndex, offsetBy: letters.characters.count / 2)
for index in letters.characters.indices {
// to traverse to half the length of string
if index == middle { break } // s, t, r
print(letters[index]) // s, t, r, i, n, g
}
From the documentation in section Strings and Characters - Counting Characters:
Extended grapheme clusters can be composed of one or more Unicode scalars. This means that different characters—and different representations of the same character—can require different amounts of memory to store. Because of this, characters in Swift do not each take up the same amount of memory within a string’s representation. As a result, the number of characters in a string cannot be calculated without iterating through the string to determine its extended grapheme cluster boundaries.
emphasis is my own.
This will not work:
let secondChar = letters[1]
// error: subscript is unavailable, cannot subscript String with an Int
Another option is to use enumerated() e.g:
let string = "Hello World"
for (index, char) in string.characters.enumerated() {
print(char)
}
or for Swift 4 just use
let string = "Hello World"
for (index, char) in string.enumerated() {
print(char)
}
Use the following:
for i in s.characters.indices[s.startIndex..<s.endIndex] {
print(s[i])
}
Taken from Migrating to Swift 2.3 or Swift 3 from Swift 2.2
Iterating over characters in a string is cleaner in Swift 4:
let myString = "Hello World"
for char in myString {
print(char)
}
If you want to traverse over the characters of a String, then instead of explicitly accessing the indices of the String, you could simply work with the CharacterView of the String, which conforms to CollectionType, allowing you access to neat subsequencing methods such as prefix(_:) and so on.
/* traverse the characters of your string instance,
up to middle character of the string, where "middle"
will be rounded down for strings of an odd amount of
characters (e.g. 5 characters -> travers through 2) */
let m = "alpha"
for ch in m.characters.prefix(m.characters.count/2) {
print(ch, ch.dynamicType)
} /* a Character
l Character */
/* round odd division up instead */
for ch in m.characters.prefix((m.characters.count+1)/2) {
print(ch, ch.dynamicType)
} /* a Character
l Character
p Character */
If you'd like to treat the characters within the loop as strings, simply use String(ch) above.
With regard to your comment below: if you'd like to access a range of the CharacterView, you could easily implement your own extension of CollectionType (specified for when Generator.Element is Character) making use of both prefix(_:) and suffix(_:) to yield a sub-collection given e.g. a half-open (from..<to) range
/* for values to >= count, prefixed CharacterView will be suffixed until its end */
extension CollectionType where Generator.Element == Character {
func inHalfOpenRange(from: Int, to: Int) -> Self {
guard case let to = min(to, underestimateCount()) where from <= to else {
return self.prefix(0) as! Self
}
return self.prefix(to).suffix(to-from) as! Self
}
}
/* example */
let m = "0123456789"
for ch in m.characters.inHalfOpenRange(4, to: 8) {
print(ch) /* \ */
} /* 4 a (sub-collection) CharacterView
5
6
7 */
The best way to do this is :-
let name = "nick" // The String which we want to print.
for i in 0..<name.count
{
// Operation name[i] is not allowed in Swift, an alternative is
let index = name.index[name.startIndex, offsetBy: i]
print(name[index])
}
for more details visit here
Swift 4.2
Simply:
let m = "alpha"
for i in m.indices {
print(m[i])
}
Swift 4:
let mi: String = "hello how are you?"
for i in mi {
print(i)
}
To concretely demonstrate how to traverse through a range in a string in Swift 4, we can use the where filter in a for loop to filter its execution to the specified range:
func iterateStringByRange(_ sentence: String, from: Int, to: Int) {
let startIndex = sentence.index(sentence.startIndex, offsetBy: from)
let endIndex = sentence.index(sentence.startIndex, offsetBy: to)
for position in sentence.indices where (position >= startIndex && position < endIndex) {
let char = sentence[position]
print(char)
}
}
iterateStringByRange("string", from: 1, to: 3) will print t, r and i
When iterating over the indices of characters in a string, you seldom only need the index. You probably also need the character at the given index. As specified by Paulo (updated for Swift 4+), string.indices will give you the indices of the characters. zip can be used to combine index and character:
let string = "string"
// Define the range to conform to your needs
let range = string.startIndex..<string.index(string.startIndex, offsetBy: string.count / 2)
let substring = string[range]
// If the range is in the type "first x characters", like until the middle, you can use:
// let substring = string.prefix(string.count / 2)
for (index, char) in zip(substring.indices, substring) {
// index is the index in the substring
print(char)
}
Note that using enumerated() will produce a pair of index and character, but the index is not the index of the character in the string. It is the index in the enumeration, which can be different.