How to change empty input value to empty array? - swift

var input = [readLine() ?? ""]
If I just entered, input has [""]
If I do not input anything, I want to make the input an empty list.
How can I do it?
This is because I want the count of the input to be zero when the input is empty.

You can use an IF statement to check if the input was an empty string and if so, then set the input to an empty array. There may be a better way to do this but I think this will work.
if input == [""] {
input = []
}
I hope this helped.

Another way to do this is to define your own function that reads the line or returns an empty array:
private func myReadLine() -> [String] {
let line = readLine()
if line == [""] {
return []
} else {
return line
}
}
And then at the call site you can write:
var input = myReadLine()
Which keeps the logic separated from the calling code and is easier to read. It also has the added advantage of being a lot easier to change if you want to amend your input handling conditions later on.

Simply filter out empty values:
input = input.filter { !$0.isEmpty }
or even:
let input = [readLine()]
.compactMap { $0 } // remove nil
.filter { !$0.isEmpty } // remove empty strings

Related

Checking for whitespace in a string typed array - Swift

In a string typed array how can I achieve the functionality as I would for checking whitespace in a string? I'd like to check if the array contains only whitespace
var stringExample: String!
var stringArrayExample: [String]!
if stringExample.trimmingCharacters(in: .whitespaces).isEmpty{
//string contains whitespace characters
}
Swift 3 would look something like this if I'm understanding what you're wanting:
var someStrings = [" ", "foo", "bar", "\t"]
let result = someStrings.filter { $0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
print(result) // [" ", "\t"]
If you're just wanting to know if the array of strings are all whitespace-only strings you could change the last two lines to:
let result = someStrings.filter { $0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false }
print(result.isEmpty) // false
Note that both these use .whitespacesAndNewlines if you don't want new lines, just use .whitespaces like you do in your original example.
I've created an extension for String which returns whether it's empty or contains only whitespace:
extension String {
var isEmptyOrWhitespace : Bool {
return self.trimmingCharacters(in: .whitespaces).isEmpty
}
}
And since I'm also a .NET developer and like the methods Any, All etc. I've also created an extension for the Array type, which lets me check a condition for every element in the array, leveraging the reduce function:
extension Array {
func all(test: (Element) -> Bool) -> Bool {
return self.reduce(true) { $0 && test($1) }
}
}
Then you can combine these two to get a fairly nice syntax, which is also fairly performant, since it "breaks" when it stumbles upon an element that does not comply with the provided test (using a for instead of reduce would probably be even more efficient).
let strings1 = [" ", "", "\t"]
print(strings1.all { $0.isEmptyOrWhitespace }) // true
print(strings1.all { !$0.isEmptyOrWhitespace }) // false
By printing within the test, you can see it no longer executes the tests for elements when it finds the first non-compliant one.
let strings2 = [" ", "x", "\t"]
print(strings2.all(test: { (str) -> Bool in
let e = str.isEmptyOrWhitespace
print ("[\(str)]: \(e)")
return e
}))
Prints:
[ ]: true
[x]: false
false

CSVImporter starts importing after viewdidload [Swift]

I want to import data from a .csv file, so I have used the CSVImporter https://github.com/Flinesoft/CSVImporter. It works well, but it starts the importing before the other part of the function viewDidLoad is executed.
The following code is only a test but I need either a solution that ensures that the CSVImporter completes importing before the other viewDidLoad code executes or a function which starts automatically after viewDidLoad.
Here is my code:
var Vokabeln: [[String]]?
var i = 0
override func viewDidLoad() {
super.viewDidLoad()
let path = "/Users/---CENSORED---/Documents/TestLoĢˆschen/TestLoĢˆschen/Vokabeln.csv"
let importer = CSVImporter<[String]>(path: path, delimiter: ";")
importer.startImportingRecords { $0 }.onFinish { importedRecords in
for record in importedRecords {
self.Vokabeln?[self.i][0] = record[0]
self.Vokabeln?[self.i][1] = record[1]
self.Vokabeln?[self.i][2] = record[2]
print("Begin1")
print(record[0])
print(record[1])
print(record[2])
print("End1")
self.i += 1
}
}
print("Begin2")
print(Vokabeln?[0][0])
print(Vokabeln?[0][1])
print(Vokabeln?[0][2])
print(Vokabeln?[1][0])
print(Vokabeln?[1][1])
print(Vokabeln?[1][2])
print("End2")
}
So first it prints "Begin2" and 6 times prints nil. Then, when the function viewDidLoad is finished, it prints "Begin1", then the correct variables and "End1"
Can anybody help me?
Thanks.
startImportingRecords works asynchronously, just put the code to print the Vokabeln in the completion handler after the repeat loop.
First of all you need to initialize the array otherwise nothing will be appended. And do not declare the array as optional and variable names are supposed to start with a lowercase letter.
var vokabeln = [[String]]()
In case to update the UI wrap the code in a dispatch block for example
importer.startImportingRecords { $0 }.onFinish { importedRecords in
for record in importedRecords {
self.vokabeln[self.i][0] = record[0]
self.vokabeln[self.i][1] = record[1]
self.vokabeln[self.i][2] = record[2]
print("Begin1")
print(record[0])
print(record[1])
print(record[2])
print("End1")
self.i += 1
}
DispatchQueue.main.async {
print("Begin2")
print(self.vokabeln[0][0])
print(self.vokabeln[0][1])
print(self.vokabeln[0][2])
print(self.vokabeln[1][0])
print(self.vokabeln[1][1])
print(self.vokabeln[1][2])
print("End2")
}
}
But there is still another issue. If you declare the array as [[String]] both outer and inner arrays are empty and you cannot assign values with index subscripting. I recommend this syntax
for record in importedRecords {
self.vokabeln.append(record) // this appends the whole record array
print("Begin1")
print(record)
print("End1")
}
PS: Consider to use a more suitable text format like JSON or property list.

Swift 3: Change item in dictionary

I'm saving lists in a dictionary. These lists need to be updated. But when searching for an item, I need [] operator. When I save the result to a variable, a copy is used. This can not be used, to change the list itself:
item = dicMyList[key]
if item != nil {
// add it to existing list
dicMyList[key]!.list.append(filename)
// item?.list.append(filename)
}
I know, that I need the uncommented code above, but this accesses and searches again in dictionary. How can I save the result, without searching again? (like the commented line)
I want to speed up the code.
In case you needn't verify whether the inner list was actually existing or not prior to adding element fileName, you could use a more compact solution making use of the nil coalescing operator.
// example setup
var dicMyList = [1: ["foo.sig", "bar.cc"]] // [Int: [String]] dict
var key = 1
var fileName = "baz.h"
// "append" (copy-in/copy-out) 'fileName' to inner array associated
// with 'key'; constructing a new key-value pair in case none exist
dicMyList[key] = (dicMyList[key] ?? []) + [fileName]
print(dicMyList) // [1: ["foo.sig", "bar.cc", "baz.h"]]
// same method used for non-existant key
key = 2
fileName = "bax.swift"
dicMyList[key] = (dicMyList[key] ?? []) + [fileName]
print(dicMyList) // [2: ["bax.swift"], 1: ["foo.sig", "bar.cc", "baz.h"]]
Dictionaries and arrays are value types. So if you change an entry you'll need to save it back into the dictionary.
if var list = dicMyList[key] {
list.append(filename)
dicMyList[key] = list
} else {
dicMyList[key] = [filename]
}
It's a little bit late, but you can do something like this:
extension Optional where Wrapped == Array<String> {
mutating func append(_ element: String) {
if self == nil {
self = [element]
}
else {
self!.append(element)
}
}
}
var dictionary = [String: [String]]()
dictionary["Hola"].append("Chau")
You can try this in the Playground and then adapt to your needs.

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>

Efficiently remove the last word from a string in Swift

I am trying to build an autocorrect system, so I need to be able to delete the last word typed and replace it with the correct one. My solution:
func autocorrect() {
hasWordReadyToCorrect = false
var wordProxy = self.textDocumentProxy as UITextDocumentProxy
var stringOfWords = wordProxy.documentContextBeforeInput
fullString = "Unset Value"
if stringOfWords != nil {
var words = stringOfWords.componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
for word in words {
arrayOfWords += [word]
}
println("The last word of the array is \(arrayOfWords.last)")
for (mistake, word) in autocorrectList {
println("The mistake is \(mistake)")
if mistake == arrayOfWords.last {
fullString = word
hasWordReadyToCorrect = true
}
}
println("The corrected String is \(fullString)")
}
}
This method is called after each keystroke, and if the space is pressed, it corrects the word. My problem comes in when the string of text becomes longer than about 20 words. It takes a while for it to fill the array each time a character is pressed, and it starts to lag to a point of not being able to use it. Is there a more efficient and elegant Swift way of writing this function? I'd appreciate any help!
This doesn't answer the OP's "autocorrect" issue directly, but this is code is probably the easiest way to answer the question posed in the title:
Swift 3
let myString = "The dog jumped over a fence"
let myStringWithoutLastWord = myString.components(separatedBy: " ").dropLast().joined(separator: " ")
1.
One thing, iteration isn't necessary for this:
for word in words {
arrayOfWords += [word]
}
You can just do:
arrayOfWords += words
2.
Breaking the for loop will prevent iterating unnecessarily:
for (mistake, word) in autocorrectList {
println("The mistake is \(mistake)")
if mistake == arrayOfWords.last {
fullString = word
hasWordReadyToCorrect = true
break; // Add this to stop iterating through 'autocorrectList'
}
}
Or even better, forget the for-loop completely:
if let word = autocorrectList[arrayOfWords.last] {
fullString = word
hasWordReadyToCorrect = true
}
Ultimately what you're doing is seeing if the last word of the entered text matches any of the keys in the autocorrect list. You can just try to get the value directly using optional binding like this.
---
I'll let you know if I think of more.