3
\$\begingroup\$

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?

\$\endgroup\$
  • \$\begingroup\$ Just saying, why not using the PEP8 style ? \$\endgroup\$ – Julien Palard Jul 14 '14 at 20:45
1
\$\begingroup\$
  • 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.
\$\endgroup\$
  • \$\begingroup\$ (1) Why? (2) Why? (3) Yes, I'll consider that. But would it be better just to keep the loop? \$\endgroup\$ – wchargin Jul 15 '14 at 4:00
  • 1
    \$\begingroup\$ 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. \$\endgroup\$ – jcollado Jul 15 '14 at 7:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.