Replacing a char in a string based on a condition [duplicate] - swift

This question already has answers here:
Any way to replace characters on Swift String?
(23 answers)
Closed 8 months ago.
I came across this question in CodeWars recently & tried to solve it using Swift 5 (which I'm new to):
Given a string of digits, you should replace any digit below 5 with '0' and any digit 5 and above with '1'. Return the resulting string.
Note: input will never be an empty string
I come from a Python background where we could using indexing with if statements to solve this particular problem like this:
def bin(x):
newstr = ""
for num in x:
if int(num) < 5:
newstr += "0"
else:
newstr += "1"
return newstr
But when I tried to use indexing in Swift( like the Python style), I failed...which, after a while of researching made me come across this method: String.replacingOccurrences(of: , with: )
So if my Swift code for the above ques is like this:
func fakeBin(digits: String) -> String {
for i in digits{
if (Int(i)<5){
some code to replace the char with '0'
}
else{some code to replace the char with '1'}
return digits
}
How do I use the above method String.replacingOccurrences(of: , with: ) in my code and achieve the desired output? If this is not possible, how else can we do it?
Note: I have already looked up for solutions on the website & found one:How do I check each individual digit within a string? But wasn't of any use to me as the questioner wanted the help in Java

A Swift String is a collection of Characters. You can map each character to "0" or "1", and join the result to a new string:
func fakeBin(digits: String) -> String {
return String(digits.map { $0 < "5" ? "0" : "1" })
}

Your code equivalent would be:
func fakeBinBasicForLoop(digits: String) -> String {
var output = ""
for aChar in digits {
if let intValue = Int(String(aChar)) { //It's a number
if intValue < 5 {
output.append("0")
} else {
output.append("1")
}
} else { //Not a number
output.append(aChar)
}
}
return output
}
I'm checking if it's an integer, just in case.
Manually, with replacingOccurrences(of:with:):
func fakeBinReplacingOccurences(digits: String) -> String {
var output = digits
output = output.replacingOccurrences(of: "0", with: "0") //No really needed in this case, we can ommit it
output = output.replacingOccurrences(of: "1", with: "0")
output = output.replacingOccurrences(of: "2", with: "0")
output = output.replacingOccurrences(of: "3", with: "0")
output = output.replacingOccurrences(of: "4", with: "0")
output = output.replacingOccurrences(of: "5", with: "1")
output = output.replacingOccurrences(of: "6", with: "1")
output = output.replacingOccurrences(of: "7", with: "1")
output = output.replacingOccurrences(of: "8", with: "1")
output = output.replacingOccurrences(of: "9", with: "1")
return output
}
The issue is that it iterate all the string each time, so you'll have 9 loops.
Allso, if you start by replacing 5, 6, 7, 8, 9 (ie in reverse order), your higher values will be replaced by 1, and then, replaced by 0. Order matters.
An alternative solution with reduce(into:_:):
func fakeBinReduceInto(digits: String) -> String {
let output = digits.reduce(into: "") { partialResult, character in
guard let intValue = Int(String(character)) else { partialResult.append(character); return }
if intValue < 5 {
partialResult.append("0")
} else {
partialResult.append("1")
}
}
return output
}
You can also use a map() as suggested by other answers.

Related

Get Length of a substring in string before certain character Swift

My main string is like this "90000+8000-1000*10". I wanted to find the length of substring that contain number and make it into array. So it will be like this:
print(substringLength[0]) //Show 5
print(substringLength[1]) //Show 4
Could anyone help me with this? Thanks in advance!
⚠️ Be aware of using replacingOccurrences!
Although this method (mentioned by #Raja Kishan) may work in some cases, it's not forward compatible and will fail if you have unhandled characters (like other expression operators)
βœ… Just write it as you say it:
let numbers = "90000+8000-1000*10".split { !$0.isWholeNumber && $0 != "." }
You have the numbers! go ahead and count the length
numbers[0].count // show 5
numbers[1].count // shows 4
🎁 You can also have the operators like:
let operators = "90000+8000-1000*10".split { $0.isWholeNumber || $0 == "." }
You can split when the character is not a number.
The 'max splits' method is used for performance, so you don't unnecessarily split part of the input you don't need. There are also preconditions to handle any bad input.
func substringLength(of input: String, at index: Int) -> Int {
precondition(index >= 0, "Index is negative")
let sections = input.split(maxSplits: index + 1, omittingEmptySubsequences: false) { char in
!char.isNumber
}
precondition(index < sections.count, "Out of range")
return sections[index].count
}
let str = "90000+8000-1000*10"
substringLength(of: str, at: 0) // 5
substringLength(of: str, at: 1) // 4
substringLength(of: str, at: 2) // 4
substringLength(of: str, at: 3) // 2
substringLength(of: str, at: 4) // Precondition failed: Out of range
If the sign (operator) is fixed then you can replace all signs with a common one sign and split the string by a common sign.
Here is the example
extension String {
func getSubStrings() -> [String] {
let commonSignStr = self.replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "*", with: "-")
return commonSignStr.components(separatedBy: "-")
}
}
let str = "90000+8000-1000*10"
str.getSubStrings().forEach({print($0.count)})
I'd assume that the separators are not numbers, regardless of what they are.
let str = "90000+8000-1000*10"
let arr = str.split { !$0.isNumber }
let substringLength = arr.map { $0.count }
print(substringLength) // [5, 4, 4, 2]
print(substringLength[0]) //Show 5
print(substringLength[1]) //Show 4
Don't use isNumber Character property. This would allow fraction characters as well as many others that are not single digits 0...9.
Discussion
For example, the following characters all represent numbers:
β€œ7” (U+0037 DIGIT SEVEN)
β€œβ…šβ€ (U+215A VULGAR FRACTION FIVE SIXTHS)
β€œγŠˆβ€ (U+3288 CIRCLED IDEOGRAPH NINE)
β€œπŸ β€ (U+1D7E0 MATHEMATICAL DOUBLE-STRUCK DIGIT EIGHT)
β€œΰΉ’β€ (U+0E52 THAI DIGIT TWO)
let numbers = "90000+8000-1000*10".split { !("0"..."9" ~= $0) } // ["90000", "8000", "1000", "10"]
let numbers2 = "90000+8000-1000*10 ΰ₯« ΰΉ™ δΈ‡ β…š 𝟠 ΰΉ’ ".split { !("0"..."9" ~= $0) } // ["90000", "8000", "1000", "10"]

How can capture individual characters in a string and place them in an array in Swift 4.2?

FINAL OBJECTIVE: Turn integers into written long hand.
I have seen some discussions on this which are over my head. I have come across functions which happily break up a string and print out the characters but capturing them in an array seems impossible.
It seems individual characters can be accessed by subscript but they can't be operated on.
The following will print out 1,2,9,.,5,0 if I remove the commenting out but when I run through the if loop I get written number strings but in the wrong sequence.
let sentence = "129.50"
for (character) in sentence {
// print(character)
if character == "0" {
print("zero")
}
if character == "1" {
print("one")
}
if character == "2" {
print("two")
}
etc etc
I have also tried to access the indexing function via a function and although it prints out in full every time it always crashes at the end.
func speakNum(_ num:Int) {
let strgNum = String(num)
for t in 0...strgNum.count {
let index = strgNum.index(strgNum.startIndex, offsetBy:t)
//strgnum.index(strgNum.startIndex, offsetBy:t)
print(String(strgNum[index]))
}
}
Any help appreciated.
This is an excellent time for you to learn TDD. Test Driven Development. Start off with a simple case, the simplest you can think of...
assert(writtenOut("1") == "one")
Get the above working then add another test:
assert(writtenOut("1") == "one")
assert(writtenOut("2") == "two")
Do the above for all the numbers and the decimal. You should also handle error cases:
assert(writtenOut("d") == "")
Then try for something more complex:
assert(writtenOut("12") == "one two") // or do you want "twelve" in this case?
You can do this yourself, start start small and work your way up. By the time you are done, you will have a working function and a whole bunch of tests that prove it works.
Try this
let str = "129.50"
let array = Array(str)
print(array)
prints ["1", "2", "9", ".", "5", "0"]
Thanks to all for feedback, I have ended up with this which seems a bit cumbersome but does work:
func radio(_ MHz:Double){
let sentence = String(MHz)
for (character) in sentence {
if character == "0" {
print("zero", terminator:" ")
}
if character == "1" {
print("one", terminator:" ")
}
if character == "2" {
print("two", terminator:" ")
}
if character == "3" {
print("tree", terminator:" ")
}
if character == "4" {
print("fower", terminator:" ")
}
if character == "5" {
print("fife", terminator:" ")
}
if character == "6" {
print("six", terminator:" ")
}
if character == "7" {
print("seven", terminator:" ")
}
if character == "8" {
print("eight", terminator:" ")
}
if character == "9" {
print("niner", terminator:" ")
}
if character == "." {
print("decimal", terminator:" ")
}
}
print()
print()
}
Thus radio(118.65) yields
one one eight decimal niner fife

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"

How can I check if a string contains letters in Swift? [duplicate]

This question already has answers here:
What is the best way to determine if a string contains a character from a set in Swift
(11 answers)
Closed 7 years ago.
I'm trying to check whether a specific string contains letters or not.
So far I've come across NSCharacterSet.letterCharacterSet() as a set of letters, but I'm having trouble checking whether a character in that set is in the given string. When I use this code, I get an error stating:
'Character' is not convertible to 'unichar'
For the following code:
for chr in input{
if letterSet.characterIsMember(chr){
return "Woah, chill out!"
}
}
You can use NSCharacterSet in the following way :
let letters = NSCharacterSet.letters
let phrase = "Test case"
let range = phrase.rangeOfCharacter(from: characterSet)
// range will be nil if no letters is found
if let test = range {
println("letters found")
}
else {
println("letters not found")
}
Or you can do this too :
func containsOnlyLetters(input: String) -> Bool {
for chr in input {
if (!(chr >= "a" && chr <= "z") && !(chr >= "A" && chr <= "Z") ) {
return false
}
}
return true
}
In Swift 2:
func containsOnlyLetters(input: String) -> Bool {
for chr in input.characters {
if (!(chr >= "a" && chr <= "z") && !(chr >= "A" && chr <= "Z") ) {
return false
}
}
return true
}
It's up to you, choose a way. I hope this help you.
You should use the Strings built in range functions with NSCharacterSet rather than roll your own solution. This will give you a lot more flexibility too (like case insensitive search if you so desire).
let str = "Hey this is a string"
let characterSet = NSCharacterSet(charactersInString: "aeiou")
if let _ = str.rangeOfCharacterFromSet(characterSet, options: .CaseInsensitiveSearch) {
println("true")
}
else {
println("false")
}
Substitute "aeiou" with whatever letters you're looking for.
A less flexible, but fun swift note all the same, is that you can use any of the functions available for Sequences. So you can do this:
contains("abc", "c")
This of course will only work for individual characters, and is not flexible and not recommended.
The trouble with .characterIsMember is that it takes a unichar (a typealias for UInt16).
If you iterate your input using the utf16 view of the string, it will work:
let set = NSCharacterSet.letterCharacterSet()
for chr in input.utf16 {
if set.characterIsMember(chr) {
println("\(chr) is a letter")
}
}
You can also skip the loop and use the contains algorithm if you only want to check for presence/non-presence:
if contains(input.utf16, { set.characterIsMember($0) }) {
println("contains letters")
}

How do I cycle through the entire alphabet with Swift while assigning values?

I am trying to cycle through the entire alphabet using Swift. The only problem is that I would like to assign values to each letter.
For Example: a = 1, b = 2, c = 3 and so on until I get to z which would = 26.
How do I go through each letter in the text field that the user typed while using the values previously assigned to the letters in the alphabet?
After this is done, how would I add up all the letters values to get a sum for the entire word. I am looking for the simplest possible way to accomplish this but works the way I would like it to.
edit/update: Xcode 12.5 β€’ Swift 5.4
extension Character {
static let alphabetValue = zip("abcdefghijklmnopqrstuvwxyz", 1...26).reduce(into: [:]) { $0[$1.0] = $1.1 }
var lowercased: Character { .init(lowercased()) }
var letterValue: Int? { Self.alphabetValue[lowercased] }
}
extension String {
var wordValue: Int { compactMap(\.letterValue).reduce(0, +) }
}
Character("A").letterValue // 1
Character("b").letterValue // 2
Character("c").letterValue // 3
Character("d").letterValue // 4
Character("e").letterValue // 5
Character("Z").letterValue // 26
"Abcde".wordValue // 15
I'd create a function something like this...
func valueOfLetter(inputLetter: String) -> Int {
let alphabet = ["a", "b", "c", "d", ... , "y", "z"] // finish the array properly
for (index, letter) in alphabet {
if letter = inputLetter.lowercaseString {
return index + 1
}
}
return 0
}
Then you can iterate the word...
let word = "hello"
var score = 0
for character in word {
score += valueOfLetter(character)
}
Assign the letters by iterating over them and building a dictionary with letters corresponding to their respective values:
let alphabet: [String] = [
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
]
var alphaDictionary = [String: Int]()
var i: Int = 0
for a in alphabet {
alphaDictionary[a] = ++i
}
Use Swift's built-in Array reduce function to sum up the letters returned from your UITextViewDelegate:
func textViewDidEndEditing(textView: UITextView) {
let sum = Array(textView.text.unicodeScalars).reduce(0) { a, b in
var sum = a
if let d = alphaDictionary[String(b).lowercaseString] {
sum += d
}
return sum
}
}
I've just put together the following function in swiftstub.com and it seems to work as expected.
func getCount(word: String) -> Int {
let alphabetArray = Array(" abcdefghijklmnopqrstuvwxyz")
var count = 0
// enumerate through each character in the word (as lowercase)
for (index, value) in enumerate(word.lowercaseString) {
// get the index from the alphabetArray and add it to the count
if let alphabetIndex = find(alphabetArray, value) {
count += alphabetIndex
}
}
return count
}
let word = "Hello World"
let expected = 8+5+12+12+15+23+15+18+12+4
println("'\(word)' should equal \(expected), it is \(getCount(word))")
// 'Hello World' should equal 124 :)
The function loops through each character in the string you pass into it, and uses the find function to check if the character (value) exists in the sequence (alphabetArray), and if it does it returns the index from the sequence. The index is then added to the count and when all characters have been checked the count is returned.
Maybe you are looking for something like this:
func alphabetSum(text: String) -> Int {
let lowerCase = UnicodeScalar("a")..."z"
return reduce(filter(text.lowercaseString.unicodeScalars, { lowerCase ~= $0}), 0) { acc, x in
acc + Int((x.value - 96))
}
}
alphabetSum("Az") // 27 case insensitive
alphabetSum("Hello World!") // 124 excludes non a...z characters
The sequence text.lowercaseString.unicodeScalars ( lower case text as unicode scalar ) is filtered filter keeping only the scalars that pattern match ~= with the lowerCase range.
reduce sums all the filtered scalar values shifted by -96 (such that 'a' gives 1 etc.). reduce starts from an accumulator (acc) value of 0.
In this solution the pattern match operator will just check for the scalar value to be between lowerCase.start (a) and lowerCase.end (z), thus there is no lookup or looping into an array of characters.