How can I individually replace each occurence of a string? - swift

I am trying to replace every occurrence of a space with two random characters (out of 103). The problem is that it is always the same 2 characters every time, which makes sense if you look at the code
I'm pretty new to Swift and Xcode and I've already tried a bunch of things, like using a "for" loop.
newSentence = passedSentence.replacingOccurrences(of: " ", with: " \(randomArray[Int.random(in: 0...103)]) \(randomArray[Int.random(in: 0...103)]) ")
resultText.text = newSentence
As I said before, it is always the same 2 characters when I want it to "refresh" for every occurrence of a space.

Using map(_:) method instead of replacingOccurrences(of:with:) to get the desired result.
replacingOccurrences(of:with:)
A new string in which all occurrences of target in the receiver are
replaced by replacement.
It replaces all the occurrences with the same instance that is passed in the replacementString i.e. randomArray[Int.random(in: 0...103) (randomArray[Int.random(in: 0...103)] is executed only once and used throughout the string for all occurrences of " ".
let passedSentence = "This is a sample sentence"
let newSentence = (passedSentence.map { (char) -> String in
if char == " " {
return " \(randomArray[Int.random(in: 0...103)]) \(randomArray[Int.random(in: 0...103)]) "
}
return String(char)
}).joined()
print(newSentence)
In case the you're using the whole range of randomArray, i.e. if randomArray contains 104 elements i.e. 0...103, you can directly use randomElement() on randomArray instead of using random(in:) on Int, i.e.
Use
randomArray.randomElement()
instead of
randomArray[Int.random(in: 0...103)]

Yes, your behavior is expected given your code. The parameter passed in with: is only executed once so if it generates "SX", that will be used to replace ALL the occurrences of " "(space) in your passedSentence.
To get your expected behavior, you would have to loop:
var rangeOfEmptyString = passedSentence.range(of: " ")
while rangeOfEmptyString != nil {
let randomModifier = "\(randomArray[Int.random(in: 0...103)])\(randomArray[Int.random(in: 0...103)])"
passedSentence = passedSentence.replacingCharacters(in: rangeOfEmptyString!, with: randomModifier)
rangeOfEmptyString = passedSentence.range(of: " ")
}

You should replace each space with different random elements of the array. replacingOccurrences method replaces all ranges at once. Get the range of space one by one and replace with unique random elements
let randomArray:[String] = ["a","b","c","d"]
var passedSentence = "This is a long text"
var start = passedSentence.startIndex
while let range = passedSentence[start...].rangeOfCharacter(from: .whitespaces),
let random1 = randomArray.randomElement(),
let random2 = randomArray.randomElement() {
let replacementString = " \(random1) \(random2) "
passedSentence.replaceSubrange(range, with: " \(random1) \(random2) ")
start = passedSentence.index(range.upperBound, offsetBy: replacementString.count)
}
print(passedSentence)
To get the random element from an array don't use randomArray[Int.random(in: 0...103)]. You can use randomElement()

Related

Swift find the end index of a word within a string

I have a string.
I have a word inside that string
I want to basically get rid of everything in the string and the word.
But everything after the word in the string I want to keep.
This word and string can be dynamic.
Here is what I got so far and I am finding it hard to find online resources to solve this.
Error:
Cannot convert value of type 'String' to expected argument type 'String.Element' (aka 'Character')
Here is my code
var bigString = "This is a big string containing the pattern"
let pattern = "containing" //random word that is inside big string
let indexEndOfPattern = bigString.lastIndex(of: pattern) // Error here
let newText = bigString[indexEndOfPattern...]
bigString = newText // bigString should now be " the pattern"
Think in ranges and bounds, lastIndex(of expects a single Character
var bigString = "This is a big string containing the pattern"
let pattern = "containing" //random word that is inside big string
if let rangeOfPattern = bigString.range(of: pattern) {
let newText = String(bigString[rangeOfPattern.upperBound...])
bigString = newText // bigString should now be " the pattern"
}

I have problems with ł, ó, ą type characters in Swift

I have a piece of code in my iOS app that should go through a word and check if a character is in it. When it finds at least one, it should change a string full of "_" of the same length as the word to one with the character in the right place:
wordToGuess = six
letterGuessed = i
wordAsUnderscores = _i_
The code works. But I start to have problems when I type in characters like: "ć", "ł", "ą", etc. From using character.utf8.count I saw that Swift thinks those are not 1 but 2 characters. So I get something like this:
wordToGuess = cześć
letterGuessed = ś
wordAsUnderscores = _ _ ś (place filled with empty char) _
It takes up 2 places.
I was at it for 6 hours and didn't figure out how to fix it, so I'm asking you guys for help.
Code that is supposed to do that:
let characterGuessed = Character(letterGuessed)
for index in wordToGuess.indices {
if (wordToGuess[index] == characterGuessed) {
let endIndex = wordToGuess.index(after: index)
let charRange = index..<endIndex
wordAsUnderscores = wordAsUnderscores.replacingCharacters(in: charRange, with: letterGuessed)
wordToGuessLabel.text = wordAsUnderscores
}
}
I would like the code to treat "ć", "ł", "ą" characters the same as "i", "a" and so on. I don't want them to be treated as 2.
The reason is that you cannot use indices from one string (wordToGuess) for subscripting another string (wordAsUnderscores). Generally, indices of one collection must not be used with a different collection. (There are exception like Array though).
Here is a working variant:
let wordToGuess = "cześć"
let letterGuessed: Character = "ś"
var wordAsUnderscores = "c____"
wordAsUnderscores = String(zip(wordToGuess, wordAsUnderscores)
.map { $0 == letterGuessed ? letterGuessed : $1 })
print(wordAsUnderscores) // c__ś_
The strings are traversed in parallel, and for each correctly guessed character in wordToGuess the corresponding character in wordAsUnderscores is replaced by that character.

Swift shift range by 2 or exclude the found character

this might be a basic question but I am having a hard time not including the - in the second
var title1 = "I will be part of string 1 - I am part of string 2"
let end = title1.range(of: "-", options: .backwards)?.lowerBound
let firstPartRange = title1.startIndex..<end!
var secondPart = title1.substring(with: firstPartRange) // Gives me "I will be part of string 1" which is correct
title1.substring(from: end!) // however this guy gives me "- I am part of string 2" & I only want to get "I am part of string 2" without the space and dash in front
Can I shift the range or change it's lowerBound somehow?. I know I can user separate component by function here but would like to learn how to offset my range
You just need to get the index after end or offset it by 2. Note that you should also make sure it doesn't pass the end index using the method index(theIndex, offsetBy: n, limitedBy: endIndex)
let title1 = "I will be part of string 1 - I am part of string 2"
if let end = title1.range(of: "-", options: .backwards)?.lowerBound {
let firstPartRange = title1.startIndex..<end
let secondPart = title1.substring(with: firstPartRange) // "I will be part of string 1 "
title1.substring(from: title1.index(after: end)) // " I am part of string 2"
// or to offset it by two you should also make sure it doesn't pass the end index
title1.substring(from: title1.index(end, offsetBy: 2, limitedBy: title1.endIndex) ?? title1.endIndex) // "I am part of string 2"
}
You are looking for index(_:offsetBy:). This is a method of the original string, like this:
var title1 = "I will be part of string 1 - I am part of string 2"
let end = title1.range(of: "-", options: .backwards)!.lowerBound
let ix = title1.index(end, offsetBy: 2)
title1.substring(from: ix) // "I am part of string 2"
You can separate the contents of string by using components(separatedBy: String) method which will return you with array of separated strings then you can remove white spaces from the last element.
var title1 = "I will be part of string 1 - I am part of string 2"
print(title1.components(separatedBy: "-").last!.trimmingCharacters(in: .whitespacesAndNewlines))
it will give you the desired result
"I am part of string 2"
Hope it helps!
You could use the components function and include the spaces in the separator:
title1.components(separatedBy:" - ")
As #matt suggest, you are looking for index(_:offsetBy:)
A little bit different approach using Swift standard library only (no Foundation)
let str = "I will be part of string 1 - I am part of string 2"
let parts = str.characters.split(separator: "-").map(String.init)
if you would like to trim all extra spaces
let partsTrimmed = parts.map {
$0.characters.split(separator: " ").map(String.init).joined(separator: " ")
}

swift why is characters.split used for? and why is map(String.init) used for

import Foundation
for i in 1 ... n {
let entry = readLine()!.characters.split(" ").map(String.init)
let name = entry[0]
let phone = Int(entry[1])!
phoneBook[name] = phone``
}
//can someone explain this piece of code`
I assume you know everything else in the code except this line:
let entry = readLine()!.characters.split(" ").map(String.init)
readLine() reads user input and returns it. Let's say the user input is
Sweeper 12345678
using .characters.split(" "), we split the input using a separator. What is this separator? A space (" ")! Now the input has been split into two - "Sweeper" and "12345678".
We want the two split parts to be strings, right? Strings are much more easier to manipulate. Currently the split parts are stored in an array of String.CharacterView.SubSequence. We want to turn each String.CharacterView.SubSequence into a string. That is why we use map. map applies a certain function to everything in a collection. So
.map(String.init)
is like
// this is for demonstration purposes only, not real code
for x in readLine()!.characters.split(" ") {
String.init(x)
}
We have now transformed the whole collection into strings!
There is error in your code replace it like below:
let entry = readLine()!.characters.split(separator: " ").map(String.init)
Alternative to the above code is:
let entry = readLine()!.components(separatedBy: " ")
Example:
var str = "Hello, playground"
let entry = str.characters.split(separator: " ").map(String.init)
print(entry)
Now characters.split with split the characters with the separator you give in above case " "(space). So it will generate an array of characters. And you need to use it as string so you are mapping characters into String type by map().

Swift - endIndex.predecessor()

Do these comments make any sense ?
Trying to figure out why it removes the character of my reversed given string, instead of the regular given string in this case.
import Foundation
extension String {
func reverseWords() -> String {
var result = ""
let words = self.componentsSeparatedByString(" ")
for thisWord in words.reverse() {
result += thisWord + " "
}
print("..\(result)..")
// Result is the words array ( contains self ) reversed with seperator " "
print("..\(self)..")
result.removeAtIndex(self.endIndex.predecessor())
// So here i am checking self within result?, and am removing the last index
// of my currently reversed given string inside result?
// I do result in my intended last space removal with result.endIndex but i'm
// wondering what happens in this case with the self.endIndex :)
return result
}
}
var str = "This string contains a few elements"
str.reverseWords()
self still refers to the original unreversed String.
The correct code would be:
result.removeAtIndex(result.endIndex.predecessor())
You should never use a String index for another string. If you are indexing result, you shouldn't use an index from self.
With a simple string you won't seem a difference but if you start adding multi-byte characters, e.g. emojis, your application can crash.
For example, using result += thisWord + "😀" would result in:
elements😀few😀a😀contains😀string�This😀
with self.endIndex
and
elements😀few😀a😀contains😀string😀This
with result.endIndex.
endIndex is the index past the end of the String. It works the same as count for arrays. count in arrays doesn't represent the last element, count - 1 represents the last element.
If your aim is to change the original String, you have to declare the method as mutating and assign to self, e.g.:
mutating func reverseWords() {
var result = ""
let words = self.componentsSeparatedByString(" ")
for thisWord in words.reverse() {
result += thisWord + " "
}
self = result
self.removeAtIndex(self.endIndex.predecessor())
}
although that's rather uncommon in functional programming.
Using self.endIndex.predecessor() to remove from result won't effect self (the "given" String).
The index is just an abstraction of a number. That number is based off the string self, rather than result
But ultimately, that index is used to remove from result, not from self.
So this is what's happening, which indeed results into the conclusion that it's quite idiotic to use the index of self.. ?
result.removeAtIndex(self.endIndex.predecessor()) // <-- Stupid move
// self.endIndex = 35
// self.endIndex predecessor = 34 ( last index in self )
// Removes index 34 out of result
// result.endIndex = 36
// results.endIndex predecessor = 35 ( the " " )
// Removes the one before the " " inside results