StreamScanner
StreamScanner copied to clipboard
Simple scanner for data streams like STDIN in Swift
Input Stream Scanner
A simple scanner for any kind of file input streams with arbitrary delimiters. Made with Swift, based on NSScanner.
Originally appeared as a necessity to parse the standard input for competitive programming challenges on HackerRank.
Installation
For competitive programming
Copy-paste the contents of this tiny sample and enjoy. For more general use cases check out the full version of StreamScanner.swift and see the usage and examples sections below.
As a framework
Compile, link against and don't forget to:
import StreamScanner
As a built-in class
Add StreamScanner.swift file to your project.
Usage
For convenience you can declare:
let stdin = StreamScanner.standardInput
and use stdin later on. Spaces and new lines are considered delimiters by default.
Or make your custom stream with:
let stream = StreamScanner(source: FileHandle(forReadingAtPath: "/path/to/file"),
delimiters: CharacterSet(charactersIn: "-.:\n"))
Now call read() -> Optional<T> to get the next value from a stream, where T is a type of a variable where you want to read() the stream.
You may call ready() -> Bool to check whether the stream currently contains any data it can parse. Note: the stream may try to grab some data from its source during this check.
Example
Imagine there's a file at /path/to/file with the following contents:
42.times show:not_a_double
And here's one of the ways to scan it with the stream just declared above:
var number: Int? = stream.read() //parse an integer at the current position in the stream
var string: String? = stream.read() //now skip a delimiting dot and parse a string
var double: Double? = stream.read() //now skip a delimiting colon and try to parse a double
print("\(number) \(string) \(double)") //Optional(42) Optional("times show") nil
More Examples
Read some arbitrary values of different types from the standard input
if
let int: Int = stdin.read(),
let string: String = stdin.read(),
let double: Double = stdin.read(),
let int64: Int64 = stdin.read(),
let float: Float = stdin.read()
{
print("\(int) \(string) \(double) \(int64) \(float)")
}
Input:
+42 st_ring!
-0.987654321 12345678900
.42
Output:
42 st_ring! -0.987654321 12345678900 0.42
Read an array of Int64 of an arbitrary size from the standard input
if var count: Int = stdin.read()
{
var array: [Int64] = []
for _ in 0..<count
{
if let integer: Int64 = stdin.read()
{
array.append(integer)
}
}
print(array)
}
Input:
2
1234567890123456789 987654321098765432
Output:
[1234567890123456789, 987654321098765432]
Read some strings from the stream in a for loop until it's empty
for name in stdin
{
print("Hello, \(name)!")
}
Input:
Chris Ally Joshua
Output:
Hello, Chris!
Hello, Ally!
Hello, Joshua!
Read and present the contents of /etc/passwd file
if let input = FileHandle(forReadingAtPath: "/etc/passwd")
{
let scanner = StreamScanner(source: input, delimiters: CharacterSet(charactersIn: ":\n"))
print("User Database:")
while let line: String = scanner.read()
{
//skip any comments
if !line.hasPrefix("#")
{
let username = line
if
let valid: String = scanner.read(),
let userId: Int = scanner.read(),
let groupId: Int = scanner.read(),
let gecos: String = scanner.read(),
let home: String = scanner.read(),
let shell: String = scanner.read()
{
print("------------------------------")
print("User: \t\(username) (\(gecos))")
print("UID: \t\(userId)")
print("GID: \t\(groupId)")
print("Home: \t\(home)")
print("Shell:\t\(shell)")
}
}
}
}
Output:
User Database:
------------------------------
User: nobody (Unprivileged User)
UID: -2
GID: -2
Home: /var/empty
Shell: /usr/bin/false
------------------------------
User: root (System Administrator)
UID: 0
GID: 0
Home: /var/root
Shell: /bin/sh
------------------------------
User: daemon (System Services)
UID: 1
GID: 1
Home: /var/root
Shell: /usr/bin/false
License
This toy is free and open.
