0

So I needed a way to represent a line in Python. My first stab was to create a function dynamically. But classes are so much ... classier, so here's a simple implementation of a line object, with plenty of room for additional methods.

The line is created as

l = Line(slope,intercept) *or*
l = Line(slope,Q) *or*
l = Line(P,Q)

where P and Q are (x,y) pairs.

"""
line.py

Exports Line class
"""

class Line(object):
    """
A representation of a line.  Can be populated through

__init__(slope,intercept)
__init__(slope,Q)
__init__(P,Q)

where P and Q are (x,y) points.
"""

    def __init__(self, a, b):
        """
L.__init__(a,b) --> None

Populates self with information from a,b.

- If a is an int or float, it is taken to be the slope of the line.
- If b is an int or float, it is taken to be the intercept of the line.
- If a or b is a tuple, it is taken to be a point through which the line passes.
- A vertical line can be indicated by a slope of None.

An error is raised if there is incomplete information, or if a and b represent
the same point.
"""
        try:
            x0, y0 = a
            slope = None
        except:
            slope = a
            x0 = y0 = None

        try:
            x1, y1 = b
            intercept = None
        except:
            intercept = b
            x1 = y1 = None

        if slope == None:
            if x0 == None and x1 != None:
                self._m = None
                self._inv_m = 0
                self._int = None
                self._A = 1
                self._B = 0
                self._C = x1
                return
            else:
                try:
                    slope = float(y1 - y0)/float(x1 - x0)
                except ZeroDivisionError:
                    self._m = None
                    self._inv_m = 0
                    self._int = None
                    self._A = 1
                    self._B = 0
                    self._C = x1
                    return
                
        if intercept == None:
            intercept = -slope * x1 + y1
            
        self._m = slope
        if self._m == 0:
            self._inv_m = None
        else:
            self._inv_m = 1./slope
        self._int = intercept
        self._A = -slope
        self._B = 1
        self._C = intercept
                
    def __str__(self):
        if self._m != None:
            if self._m == 0:
                return "y = %0.2f" % (self._int)
            if self._int > 0:
                return "y = %0.2fx + %0.2f" % (self._m, self._int)
            elif self._int < 0:
                return "y = %0.2fx - %0.2f" % (self._m, -self._int)
            else:
                return "y = %0.2fx" % (self._m)
        else:
            return "x = %0.2f" % self._C

    

    def y(self, x):
        """
L.y(x) --> y

Return y-coordinate of point on line whose x-coordinate is x.
"""
        try:
            return self._m * x + self._int
        except:
            return None

    def x(self, y):
        """
L.x(y) --> x

Return x-coordinate of point on line whose y-coordinate is y.
"""
        if self._m == None:
            return self._C
        else:
            return self._inv_m * (y - self._int)

    def contains(self, P, tol = 0.0001):
        """
L.contains(P, tol) --> Bool

Return True if P = (x,y) is on line.  The y-coordinate of point on line whose
x-coord is x must be within tol of y.
"""
        x, y = P
        return abs(self.y(x) - y) < tol

    def slope(self):
        """
L.slope() --> m

Return slope.
"""
        return self._m

# This can be called as Line.test()
@staticmethod
def test():
        pass_count = 0
        fail_count = 0

        print """
----------------------------
Testing __init__ and __str__
----------------------------"""
        test_cases = [((2,3),(4,5)),\
                      ((2.5,3),(4.2,5.8)),\
                      ((3,4),(3,5)),\
                      ((3,4),(4,4)),\
                      (5,(1,8)),\
                      (None,(2,5)),\
                      (3,8),\
                      (None,8)]
        test_results = ['y = 1.00x + 1.00',\
                        'y = 1.65x - 1.12',\
                        'x = 3.00',\
                        'y = 4.00',\
                        'y = 5.00x + 3.00',\
                        'x = 2.00',\
                        'y = 3.00x + 8.00',\
                        'TypeError']
        tests = dict(zip(test_cases,test_results))
        test_lines = []
        for a,b in test_cases:
            try:
                print "%s , %s -->" % (str(a),str(b)),
                l = Line(a,b)
                test_lines.append(l)
                s = str(l)
            except TypeError:
                s = "TypeError"
            print s
            if s == tests[(a,b)]:
                print "test passed"
                pass_count += 1
            else:
                print "***TEST FAILED***"
                fail_count += 1

        print """
---------------
Testing y and x
---------------"""
        y_intercept_results = [1, -1.12, None, 4, 3, None, 8]
        y_int_tests = dict(zip(test_lines, y_intercept_results))
        x_intercept_results = [-1, 0.6788, 3, None, -0.6, 2, -2.667]
        x_int_tests = dict(zip(test_lines, x_intercept_results))
        print "Line\t\t\t\ty-int\t\tx-int"
        print "----\t\t\t\t-----\t\t-----"
        for line in test_lines:
            try:
                
                if line.slope() == None:
                    if abs(line.x(0) - x_int_tests[line]) < 0.01:
                        print "%s\t\t\tNone\t\t%.2f" % (line, line.x(0))
                        print "test passed"
                        pass_count += 1
                        continue
                elif line.slope() == 0:
                    if abs(line.y(0) - y_int_tests[line]) < 0.01:
                        print "%s\t\t\t%.2f\t\tNone" % (line, line.y(0))
                        print "test passed"
                        pass_count += 1
                        continue
                print "%s\t\t%.2f\t\t%.2f" % (line, line.y(0), line.x(0))
                if abs(line.y(0) - y_int_tests[line]) < 0.01 and \
                   abs(line.x(0) - x_int_tests[line]) < 0.01:
                    print "test passed"
                    pass_count += 1
                else:
                    print line
                    print "***TEST FAILED***"
                    print y_int_tests[line], x_int_tests[line]
                    fail_count += 1
            except: 
                print line
                print "***TEST FAILED***"
                fail_count += 1
        print """
----------------
Testing contains
----------------
"""
        l = Line(3,5)
        test_cases = [(1,2),(0,5),(0,5.003),(-3,18)]
        test_results = [False,True,True,False]
        tests = dict(zip(test_cases,test_results))

        for P in tests:
            print "%s contains %s: %s" % (l, str(P), l.contains(P, tol=0.01))
            if l.contains(P, tol=0.01) == tests[P]:
                print "test passed"
                pass_count += 1
            else:
                print "***TEST FAILED***"
                fail_count += 1
                
        print "\n\nTOTALS:"  
        print "%d tests passed" % pass_count
        print "%d tests failed" % fail_count
2
Contributors
2
Replies
3
Views
10 Years
Discussion Span
Last Post by jrcagle
0

Thanks for gently pointing out that I had an error in the x() method. :)

The test routine is not, unfortunately, a 'few lines' -- I'm finding that I can't do short unit tests. I'll try to check out the unittest module.

Thanks,
Jeff

Have something to contribute to this discussion? Please be thoughtful, detailed and courteous, and be sure to adhere to our posting rules.