# More idiomatic way to write this Python coordinate parser?

This is a Python 2 script to take in a file in one of two formats (unknown), and standardize it.

The data are coordinates in the xy-plane. They represent airfoils in the first and fourth quadrants: x should always be positive, and y may be positive or negative. The magnitude of each coordinate of any given point should be between 0 and 1, inclusive.

The file may or may not have header data (e.g., RAE 103 AIRFOIL), and it may or may not list the total number of points at the top or bottom of the file. We want to ignore all of that, if it's present.

Ideally, we want the x-coordinates to go from 0 to 1 and then back to 0. However, in some input formats, it goes from 0 to 1, then jumps back and goes from 0 to 1 again. The goal of this parser is to identify those files and reverse the second half, and delete the repeated points.

This code works, and I've tried to make it Pythonic. There are a couple places in particular where I am considering improvements, but any suggestions are welcome.

import math
import fileinput  # to read files from stdin

"""
Syntax to run:
python AirfoilRead.py < infile.dat > outfile.geo

Some assumptions by this program:

* all valid values are in [0, 1]
* File does not have strange formating (from UIUC airfoil data base rae103 and ag13 will be called standard file types)
* all points relatively sequential (no Delaunay triangulation needed)
* only one discontinuity allowed in data, after which data reverses
* distances of > 0.5 between two points are discontinuities
"""

def parse_point(string):
"""
Takes as input a string of the form "0.123 4.567" and creates the
corresponding tuple of the form (0.123, 4.567).

If the string-to-float conversion fails (e.g., on "0.123 a.bcd"), or there
are not exactly two components, this function returns None.
"""

parts = string.split()  # split by whitespace

if len(parts) is not 2:
return None

try:
parts = [float(component) for component in parts]
except ValueError: # conversion failed on one or more parts
return None

# Make sure all components are between 0 and 1.
if all(0 <= component <= 1 for component in point):
return tuple(parts)

"""
Read in all input, convert to points, and create the list.
"""
points = []
for line in fileinput.input():
# Convert the line to a tuple.
point = parse_line_to_point(line)

# Check for validity: valid parse, valid range.
if point is not None:
# Make sure all components are between 0 and 1.
# or: if 0 <= min(point) and max(point) <= 1:
if all(0 <= component <= 1 for component in point):
points.append(point)

"""
Iterate over the list checking for discontinuities (distance > 0.5).
If we find one, split the list, reverse the second half, and concatenate.
Limit to one discontinuity (break out at the first).
"""
for i in xrange(len(points) - 1):
p0, p1 = points[i], points[i+1]
distance = math.hypot(p1[0] - p0[0], p1[1] - p0[1])
if distance > 0.5:
# Discontinuity! Split here.
first_half = points[:i+1] # inclusive upper bound
second_half = points[i+1:-1][::-1] # exclude last, then reverse
points = first_half + second_half
break

"""
Check for duplicate data at the end.
Trim the last element if both elements are duplicated (point is identical).
"""
if points[0] == points[-1]:
points = points[:-1]

"""
Print the points to stdout.
"""
for point in points:
print "(%f, %f)" % point
print ("TotalPoints = %i;" % len(points))


My thoughts:

• The parse_point method is pretty good.
• I would like the first for loop, which reads the points from the file into points, to be improved. I was considering a comprehension along the lines of

points = [parse_point(line) for line in fileinput.input()]
points = [point for point in points if point is not None and all(0 <= component <= 1 for component in point)]


but I think this may violate PEP 20.3–4. Thoughts?

• I'm not sure about the double slice on second_half. I could do it in a single slice with something like [-1:i:-1], but then you have to worry about inclusive/exclusive, and it's less clear (to me) what it's doing. What's better? (How about performance?) What about .reverse()?
• Is there a better way to do pairwise iteration with index? I don't really want to do enumerate(zip(points, points[1:])), because it just seems clumsy. But is this the way to go? Or are the dreaded index traversals the best solution here?

Please note also that I'd like to avoid anything too advanced (such as a lambda filter for reading in the points) because I'm working on this with someone who's learning Python for the first time.

General thoughts, comments, suggestions?

• Just saying, why not using the PEP8 style ? Jul 14 '14 at 20:45

• don't use is to compare numbers
• don't use """ string literal as comments
• you don't need to use lambda with filter, just use filter with a validator function if you want list comprehension instead of explicit for loop.
• 1) It's not guaranteed that every time you create a number if will have the same id. For more information, have a look here. 2) A comment isn't code that is executed in realtime, but with """ a string object is created and discarded every time you run the program. Jul 15 '14 at 7:11