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

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().

Related

Iterate trough every word from WordsArray to take every character from it

I'm making some hangman app so words i use should be displayed with "?" instead of letters
if let wordsUrl = Bundle.main.url(forResource: "start", withExtension: "txt"){
if let wordsContent = try? String(contentsOf: wordUrl){
var allWords = wordsContent.components(separatedBy: "\n")
I don't know how to index every word from allWords array.? After that i would change letters using another property which i would use to display
for letter in word {
usedLetters.append(letter)
promptWord.append("?")
I’d recommend creating a method which you can call whenever your text field needs updating due to something such as a new letter input from the user.
var wordTextField: UITextField!
var usedWords = [] // Array to track the words already used by the user
let word = "hangman" // Word for the user to guess
var promptWord = "" // What will be displayed in the wordTextField
func updateTextField() {
for letter in word.uppercased() {
let strLetter = String(letter)
if usedLetters.contains(strLetter) {
promptWord += strLetter
} else {
promptWord += "?"
}
}
wordTextField.text = promptWord
A brief explanation of what the code does:
Firstly it iterates through the word inspecting each letter (uppercase so that there are no inconsistencies with the characters when the comparison is made to what the user has entered as their guess).
Secondly it checks to see if the strLetter is contained within the usedLetters array if it is then it places the letter inside of the correct location in the promptWord.
Whenever the letter is not found to be contained within the usedWords array a “?” is instead added to the string.
Finally the text of the wordTextField is set to be the promptWord displaying the amount of letters which the user has left to guess and how many as well as which letters the user has guessed correctly.
You can convert a String to an array of characters:
let string = "a String"
let characters = Array(characters)
So you could map your array of words to an array of arrays of characters like this:
var allWordsAsCharacterArrays = allWords.map { Array($0) }
You can also populate strings with question marks using String.init(repeating:count:)
When you pick a word from your words array, you could convert it to an array of characters, and a working string that you would populate with an array of question marks. As the user picks letters, you could replace the question marks in the working string with the correct letters from the word they are guessing.
It looks like you are just trying to provide the user ultimately with a hidden word containing only question marks. May I suggest a more straight forward approach?
let wordToGuess = "Hangman"
let hiddenWord = String(repeating: "?", count: wordToGuess.count)
now when the user guesses you can replace the proper characters
let guess = "h" // get from your user input
if wordToGuess.localizedStandardContains(guess) {
var location = 0
for c in wordToGuess {
if c.lowercased() == guess.lowercased() {
let index = hiddenWord.index(hiddenWord.startIndex, offsetBy: location)
hiddenWord = hiddenWord.replacingCharacters(in: index...index , with: String(c) )
print("hidden word now: \(hiddenWord)")
}
location += 1
}
}
note this is pretty messy code. It works, but I'm sure there is a much better way.

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.

How can I individually replace each occurence of a string?

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()

Print in Swift 3

i would like to know what's the different between these two way to print the object in Swift.
The result seems identical.
var myName : String = "yohoo"
print ("My name is \(myName).")
print ("My name is ", myName, ".")
There is almost no functional difference, the comma simply inputs a space either before or after the string.
let name = "John"
// both print "Hello John"
print("Hello", name)
print("Hello \(name)")
You can use the \(variable) syntax to create interpolated strings, which are then printed just as you input them. However, the print(var1,var2) syntax has some "facilities":
It automatically adds a space in between each two variables, and that is called separator
You can customise your separator based on the context, for example:
var hello = "Hello"
var world = "World!"
print(hello,world,separator: "|") // prints "Hello|World!"
print(hello,world,separator: "\\//") // prints "Hello\\//World!"
No difference between the two
var favoriteFood: String = "Pizza" //favoriteFood = Pizza
//both print the same thing
print("My favorite food is", favoriteFood)
print("My favorite food is \(favoriteFood)")

How to get multiple lines of stdin Swift HackerRank?

I just tried out a HackerRank challenge, and if a question gives you x lines of input, putting x lines of let someVariable = readLine() simply doesn't cut it, because there are lot's of test cases that shoot way more input to the code we write, so hard coded readLine() for each line of input won't fly.
Is there some way to get multiple lines of input into one variable?
For anyone else out there who's trying a HackerRank challenge for the first time, you might need to know a couple of things that you may have never come across. I only recently learned about this piece of magic called the readLine() command, which is a native function in Swift.
When the HackerRank system executes your code, it passes your code lines of input and this is a way of retrieving that input.
let line1 = readLine()
let line2 = readLine()
let line3 = readLine()
line1 is now given the value of the first line of input mentioned in the question (or delivered to your code by one of the test cases), with line2 being the second and so on.
Your code may work just great but may fail on a bunch of other test cases. These test cases don't send your code the same number of lines of input. Here's food for thought:
var string = ""
while let thing = readLine() {
string += thing + " "
}
print(string)
Now the string variable contains all the input there was to receive (as a String, in this case).
Hope that helps someone
:)
Definitely you shouldn't do this:
while let readString = readLine() {
s += readString
}
This because Swift will expect an input string (from readLine) forever and will never terminate, causing your application die by timeout.
Instead you should think in a for loop assuming you know how many lines you need to read, which is usually this way in HackerRank ;)
Try something like this:
let n = Int(readLine()!)! // Number of test cases
for _ in 1 ... n { // Loop from 1 to n
let line = readLine()! // Read a single line
// do something with input
}
If you know that each line is an integer, you can use this:
let line = Int(readLine()!)!
Or if you know each line is an array of integers, use this:
let line = readLine()!.characters.split(" ").map{ Int(String($0))! }
Or if each line is an array of strings:
let line = readLine()!.characters.split(" ").map{ String($0) }
I hope this helps.
For new version, to get an array of numbers separated by space
let numbers = readLine()!.components(separatedBy: [" "]).map { Int($0)! }
Using readLine() and AnyGenerator to construct a String array of the std input lines
readLine() will read from standard input line-by-line until EOF is hit, whereafter it returns nil.
Returns Characters read from standard input through the end of the
current line or until EOF is reached, or nil if EOF has already been
reached.
This is quite neat, as it makes readLine() a perfect candidate for generating a sequence using the AnyGenerator initializer init(body:) which recursively (as next()) invokes body, terminating in case body equals nil.
AnyGenerator
init(body: () -> Element?)
Create a GeneratorType instance whose next method invokes body
and returns the result.
With this, there's no need to actually supply the amount of lines we expect from standard input, and hence, we can catch all input from standard input e.g. into a String array, where each element corresponds to an input line:
let allLines = AnyGenerator { readLine() }.map{ $0 }
// type: Array<String>
After which we can work with the String array to apply whatever operations needed to solve a given task (/HackerRank task).
// example standard input
4 3
<tag1 value = "HelloWorld">
<tag2 name = "Name1">
</tag2>
</tag1>
tag1.tag2~name
tag1~name
tag1~value
/* resulting allLines array:
["4 3", "<tag1 value = \"HelloWorld\">",
"<tag2 name = \"Name1\">",
"</tag2>",
"</tag1>",
"tag1.tag2~name",
"tag1~name",
"tag1~value"] */
I recently discovered a neat trick to get a certain amount of lines. I'm gonna assume the first line gives you the amount of lines you get:
guard let count = readLine().flatMap({ Int($0) }) else { fatalError("No count") }
let lines = AnyGenerator{ readLine() }.prefix(count)
for line in lines {
}
I usually use this form.
if let line = readLine(), let cnt = Int(line) {
for _ in 1...cnt {
if let line = readLine() {
// your code for a line
}
}
}
Following the answer from dfrib, for Swift 3+, AnyIterator can be used instead of AnyGenerator, in the same way:
let allLines = AnyIterator { readLine() }.map{ $0 }
// type: Array<String>