Reverse words with exclusion rules - swift

I would like to get a func which will be able to reverse a string without affecting special characters, preferably using regex, ex:
Input: “Weather is cool 24/7” -> Output: “rehtaeW si looc 24/7”
Input: “abcd efgh” -> Output: “dcba hgfe”
Input: “a1bcd efg!h” -> Output: “d1cba hgf!e”
I was able to write only for all characters without exceptions, I'm a beginner, and I don't know how to use regexes
func reverseTheWord(reverse: String) -> String {
let parts = reverse.components(separatedBy: " ")
let reversed = parts.map{String($0.reversed())}
let reversedWord = reversed.joined(separator: " ")
return reversedWord
}
thanks in advance!

Here is a solution where I first check what type each word is, only letters, no letters or a mix of letters and other characters and handle each differently.
The first two are self explanatory and for the mix one I first reverse the word and remove all non letters and then reinsert the non letters at their original position
func reverseTheWords(_ string: String) -> String {
var words = string.components(separatedBy: .whitespaces)
for (index, word) in words.enumerated() {
//Only letters
if word.allSatisfy(\.isLetter) {
words[index] = String(word.reversed())
continue
}
//No letters
if !word.contains(where: \.isLetter) { continue }
//Mix
var reversed = word.reversed().filter(\.isLetter)
for (index, char) in word.enumerated() {
if !char.isLetter {
index < reversed.endIndex ? reversed.insert(char, at: index) : reversed.append(char)
}
}
words[index] = String(reversed)
}
return words.joined(separator: " ")
}

Related

Splitting String after Comma into Tuples Swift

let input = "hello, world song"
I have an input string as above.
So i can easily use this partial string something like this
output.0 // hello
output.1 // world song
I tried something like this How to split a string by new lines in Swift but i could not exactly what i want.
Can someone write an extension for this please in a nice way?
I would do something like this:
extension String {
func splitAtFirst(_ separator: Character) -> (head: Substring, tail: Substring?) {
guard let indexOfSeparator = self.firstIndex(of: separator) else {
return (head: Substring(self), tail: nil)
}
let indexAfterSeparator = self.index(indexOfSeparator, offsetBy: +1, limitedBy: self.endIndex)!
return (
head: self[..<indexOfSeparator],
tail: self[indexAfterSeparator...]
)
}
}
let (head, tail) = "abc, def, ghi".splitAtFirst(",")
print(head) // abc
print(tail as Any) // Optional(" def, ghi")
This returns Substrings, which gives you an efficient way to do a lot of processing on an input string without causing a bunch of copies along the way. Of course, you should promote these substrings to full on strings after you've finished processing them.
You can use an extension as below:
extension StringProtocol {
var tupleOfSplittedString: (String,String) {
if !self.isEmpty {
let splitted = self.split(separator: ",").map { String($0)}
let firstPart = splitted[0]
let otherPart = String(splitted[1...].joined().dropFirst())
return (firstPart,otherPart)
}
return ("","")
}
}
let input = "hello, world song"
let resultOfFirstPart = input.tupleOfSplittedString.0 // hello
let resultOfOtherPart = input.tupleOfSplittedString.1 // world song
This code uses the .components method to split a string by a substring. I have tested this, and it was successful, even if the String is blank (it returns ("", "") in that case)
Line 4 of this code is a bit hard to read, but you can split it up into multiple lines of code if you would like.
import Foundation
extension String {
var tuple: (String, String) {
return self.components(separatedBy: ",").count == 2 ? ((self.components(separatedBy: ",")[0], self.components(separatedBy: ",")[1] )) : ("", "")
}
}
var input = "hello, world song"
print(input.tuple.0) //prints hello
print(input.tuple.1) //prints world song

replacing for loop to map method

I am stuck about this goal.
Learn about the map method, and use it in place of the loop that converts the array of characters to an array of strings in updateUI().
I have read Documentation and topics about map, but still all my tries did not work.
//change loop below to map method
for letter in currentGame.formattedWord {
letters.append(String(letter))
}
let wordWithSpacing = letters.joined(separator: " ")
correctWordLabel.text = wordWithSpacing
scoreLabel.text = "Wins: \(totalWins), Losses: \(totalLosses)"
treeImageView.image = UIImage(named: "Tree \(currentGame.incorrectMovesRemaining)")
Thanks for help
The String documentation tells us:
A string is a series of characters, such as "Swift", that forms a collection.
So, you can use map to convert each of these characters to individual strings:
let string = "Hello, world"
let letters = string.map { (character: Character) -> String in
return String(character)
}
Or, more concisely:
let letters = string.map { String($0) }
I did it. the problem was that I left the var letters as an empty array and tried to add characters to this array. Now I realized that this was wrong.
func updateUI() {
let letters = currentGame.formattedWord
let mappedLetters = letters.map { String($0)}
/* for letter in currentGame.formattedWord {
letters.append(String(letter))
} */
Thanks all!

Regex to do something only if string has prefix

I have string "something/w1/w2/". I want to get all string between "/" characters, only if I my string is prefixed by "something".
For example, if string is "something/w1/w2/" I want to get matches "w1", "w2".
And if it is "otherThing/w1/w2/" I don't want to get any matches.
Currently, I am using "(?<=something/).+?(?=/)", but in "something/w1/w2/" it returns only "w1". How can I get also "w2"?
You could use match something at the start of the string or get iterative matches using the \G anchor matching / and a capturing group that matches any char except a /
The matches are in the first capturing group.
(?:^something|\G(?!^))/([^/\r\n]+)
With double escapes:
(?:^something|\\G(?!^))/([^/\\r\\n]+)
(?: Non capturing group
^something Match something from the start of the string
| Or
\G(?!^) Assert position at the end of previous match, not at the start
) Close non capturing group
/ match literally
([^/\r\n]+) Capture group 1 Match 1+ times any char except a / or newline
Regex demo
You can achieve this without using regex but plain Swift. Check if the string has a prefix and then split by slashes.
func extractStringsBetweenSlashes(from string: String, ifPrefix prefix: String) -> [Substring]? {
guard string.hasPrefix(prefix) else { return nil }
return string.dropFirst(prefix.count).split(separator: "/")
}
print(extractStringsBetweenSlashes(from: "something/a/b/c/d/e", ifPrefix: "something/")) // Optional(["a", "b", "c", "d", "e"])
print(extractStringsBetweenSlashes(from: "something/abcdef/", ifPrefix: "something/")) // Optional(["abcdef"])
print(extractStringsBetweenSlashes(from: "else/a/b/c/d/e", ifPrefix: "something/")) // nil
You may use
let string = "something/w1/w2/"
extension String {
func findconsecutiveMatches() -> [[String]] {
let regex = try? NSRegularExpression(pattern: "(?:(?<!\\A)\\G|^something)/([^/]+)", options: [])
if let matches = regex?.matches(in: self, options:[], range:NSMakeRange(0, self.count)) {
return matches.map { match in
return (1..<match.numberOfRanges).map {
let rangeBounds = match.range(at: $0)
guard let range = Range(rangeBounds, in: self) else {
return ""
}
return String(self[range])
}
}
} else {
return []
}
}
}
let result = string.findconsecutiveMatches().flatMap { $0 }
print(result)
// => ["w1", "w2"]
The regex is
(?:(?<!\A)\G|^something)/([^/]+)
Details
(?:(?<!\A)\G|^something) - either the end of the previous match or something at the start of the string
/ - a / char
([^/]+) - Group 1: any 1+ more chars other than /.

Reversing Words Functionally in Swift

I can reverse every word in a string functionally without using a loop, but when I try to reverse EVERY OTHER WORD. I run into problems. I can do it with a loop but not functionally. What am I not seeing here?
Functionally (every word):
import UIKit
let input = "This is a sample sentence"
func reverseWords(input: String) -> String {
let parts = input.components(separatedBy: " ")
let reversed = parts.map { String($0.reversed()) }
return reversed.joined(separator: " ")
}
reverseWords(input: input)
With loop (EVERY OTHER WORD):
var sampleSentence = "This is a sample sentence"
func reverseWordsInSentence(sentence: String) -> String {
let allWords = sampleSentence.components(separatedBy:" ")
var newSentence = ""
for index in 0...allWords.count - 1 {
let word = allWords[index]
if newSentence != "" {
newSentence += " "
}
if index % 2 == 1 {
let reverseWord = String(word.reversed())
newSentence += reverseWord
} else {
newSentence += word
}
}
return newSentence
}
reverseWordsInSentence(sentence: sampleSentence)
With a slight modification of your reverseWords you can reverse every other word. Use enumerated() to combine a word with its position, and then use that to reverse odd words:
let input = "one two three four five"
func reverseOddWords(input: String) -> String {
let parts = input.components(separatedBy: " ")
let reversed = parts.enumerated().map { $0 % 2 == 0 ? String($1.reversed()) : $1 }
return reversed.joined(separator: " ")
}
print(reverseOddWords(input: input))
eno two eerht four evif
Or you could pattern your function after Swift's sort and pass the filter closure to the reverseWords function:
let input = "one two three four five"
func reverseWords(_ input: String, using filter: ((Int) -> Bool) = { _ in true }) -> String {
let parts = input.components(separatedBy: " ")
let reversed = parts.enumerated().map { filter($0) ? String($1.reversed()) : $1 }
return reversed.joined(separator: " ")
}
// default behavior is to reverse all words
print(reverseWords("one two three four five"))
eno owt eerht ruof evif
print(reverseWords("one two three four five", using: { $0 % 2 == 1 }))
one owt three ruof five
print(reverseWords("one two three four five", using: { [0, 3, 4].contains($0) }))
eno two three ruof evif
let everyThirdWord = { $0 % 3 == 0 }
print(reverseWords("one two three four five", using: everyThirdWord))
eno two three ruof five
Use stride() to generate a sequence of indexes of every other word.
Then use forEach() to select each index in the stride array and use it to mutate the word at that index to reverse it.
import UIKit
let string = "Now is the time for all good programmers to babble incoherently"
var words = string.components(separatedBy: " ")
stride(from: 0, to: words.count, by: 2)
.forEach { words[$0] = String(words[$0].reversed()) }
let newString = words.joined(separator: " ")
print(newString)
The output string is:
"woN is eht time rof all doog programmers ot babble yltnerehocni"

Trim only trailing whitespace from end of string in Swift 3

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
}
}