Add ability to read in elevation data

This commit is contained in:
Emi Simpson 2023-02-10 14:50:34 -05:00
parent 6577717cbf
commit 63eda6d69c
Signed by: Emi
GPG key ID: A12F2C2FFDC3D847

124
read_in.py Normal file
View file

@ -0,0 +1,124 @@
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(val=193334)
>>> read_single_elevation("1.0")
Ok(val=1000)
>>> read_single_elevation("10.0m")
Err(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(val=[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(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(val=[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(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()