Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get multiple lines of stdin Swift HackerRank?

Tags:

swift

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?

like image 435
Peter Parker Avatar asked Jun 27 '16 09:06

Peter Parker


4 Answers

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

:)

like image 139
Peter Parker Avatar answered Nov 14 '22 02:11

Peter Parker


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.

like image 42
Vladimir Nul Avatar answered Nov 14 '22 02:11

Vladimir Nul


For new version, to get an array of numbers separated by space

let numbers = readLine()!.components(separatedBy: [" "]).map { Int($0)! }
like image 7
KIO Avatar answered Nov 14 '22 01:11

KIO


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"] */
like image 5
dfrib Avatar answered Nov 14 '22 01:11

dfrib