124 lines
3.9 KiB
Python
124 lines
3.9 KiB
Python
from emis_funky_funktions import *
|
|
|
|
from itertools import chain
|
|
from typing import List, NamedTuple
|
|
|
|
def meters_to_millimeters(meters: float) -> int:
|
|
"""
|
|
Convert meters (floating point) to millimeters (int)
|
|
|
|
>>> meters_to_millimeters(1.9333473e+02)
|
|
193334
|
|
|
|
>>> meters_to_millimeters(1.0)
|
|
1000
|
|
"""
|
|
return int(1000 * meters)
|
|
|
|
def read_single_elevation(dat: str) -> Result[int, str]:
|
|
"""
|
|
Read in a single elevation
|
|
|
|
Expects a single, stringified floating point value, in meters, without a unit, e.g.
|
|
"2.057e+02". If there is a parsing error, returns an error containing the input
|
|
string. Otherwise, returns the elevation in millimeters.
|
|
|
|
>>> read_single_elevation("1.9333473e+02")
|
|
Ok(193334)
|
|
|
|
>>> read_single_elevation("1.0")
|
|
Ok(1000)
|
|
|
|
>>> read_single_elevation("10.0m")
|
|
Err('10.0m')
|
|
"""
|
|
return map_res(
|
|
meters_to_millimeters, # fuck floating points
|
|
try_(replace(dat), float, dat)
|
|
)
|
|
|
|
def read_elevation_line(line: str) -> Result[Iterator[int], Tuple[int, str]]:
|
|
"""
|
|
Reads a line of elevations
|
|
|
|
Each elevation should be in the format specified by `read_single_elevation`. Every
|
|
element of the line is parsed and combined into an iterator, which is returned. If
|
|
any parsing errors are encountered, the first error is returned. It takes the form of
|
|
a tuple containing the column of the error (in terms of whitespace-deliniated words,
|
|
not characters) and the string which could not be parsed.
|
|
|
|
Recall from the definition of `read_single_elevation()` that the return value is an
|
|
integer number of millimeters.
|
|
|
|
As per the problem specification, the last five columns of the line are not read or
|
|
parsed. Parsing errors in these columns therefor cannot be encountered, and any data
|
|
in these columns will not be returned.
|
|
|
|
>>> map_res(list, read_elevation_line("1.9e2 18e1 181 179.1 ignored 170.1 1.8e2 18i+2 20e1"))
|
|
Ok([190000, 180000, 181000, 179100])
|
|
|
|
>>> map_res(list, read_elevation_line("1.9e2 18e 181 179.1 ignored 170.1 1.8e2 18i+2 20e1"))
|
|
Err((2, '18e'))
|
|
"""
|
|
return sequence([
|
|
map_err(
|
|
lambda err_text: (col_no + 1, err_text),
|
|
read_single_elevation(elem)
|
|
)
|
|
# As per spec, we drop the last five entries of the file.
|
|
for (col_no, elem) in enumerate(line.split()[:-5])
|
|
])
|
|
|
|
class ErrorLocation(NamedTuple):
|
|
"The location of an error within file"
|
|
|
|
line_no: int
|
|
"The line on which the error occurred"
|
|
|
|
col_no: int
|
|
"The column (1-indexed number of whitespace deliniated words) on which the error occurred"
|
|
|
|
invalid_entry: str
|
|
"The text which failed to parse as a floating point value"
|
|
|
|
def read_elevations(lines: str) -> Result[Iterator[int], ErrorLocation]:
|
|
"""
|
|
Read an entire elevation file into a list
|
|
|
|
This parses each line in of the file using `read_elevation_line()`. Errors
|
|
encountered are reported using an `ErrorLocation`. The resulting list is
|
|
single-dimensional, but can be indexed arbitrarily, meaning for an input file with a
|
|
constant number of columns per line, this data can be accessed as if it were a 2d
|
|
square array.
|
|
|
|
However, keep in mind that, as with `read_elevation_line()`, the last five columns of
|
|
each line are dropped/ignored, and are therefor not present in the returned data.
|
|
|
|
>>> map_res(list, read_elevations('''
|
|
... 1 2 3 4 5 6 7 8 9e 10
|
|
... 11 2.0 3e1 4 5 6 7 bwa 9 10
|
|
... 11 2.0 3e0 4 5 ign err 8 9 10
|
|
... '''))
|
|
Ok([1000, 2000, 3000, 4000, 5000, 11000, 2000, 30000, 4000, 5000, 11000, 2000, 3000, 4000, 5000])
|
|
|
|
>>> map_res(list, read_elevations('''
|
|
... 1 2 3 4 5 6 7 8 9e 10
|
|
... 11 ERR 3e1 4 5 6 7 bwa 9 10
|
|
... 11 2.0 3e0 4 5 ign err 8 9 10
|
|
... '''))
|
|
Err(ErrorLocation(line_no=2, col_no=2, invalid_entry='ERR'))
|
|
"""
|
|
return map_res(
|
|
chain.from_iterable,
|
|
sequence([
|
|
map_err(
|
|
lambda err: ErrorLocation(line_no + 1, *err),
|
|
read_elevation_line(line)
|
|
)
|
|
for (line_no, line) in enumerate(lines.strip().split('\n'))
|
|
])
|
|
)
|
|
|
|
if __name__ == '__main__':
|
|
import doctest
|
|
doctest.testmod() |