(Python)确定(x,y)坐标是否共线的脚本 - 出现一些错误

问题描述 投票:0回答:7

就像标题所说,我正在尝试编写一个程序,它采用 (x, y) 坐标列表,并确定是否有任何 3 个点共线(位于具有相同斜率的直线上)

我收到一些错误消息。就目前情况而言,我收到一条“TypeError: 'int' object is not subscriptable”消息。如果我取出共线性测试调用 areCollinear 函数的部分,我会收到“索引超出范围”错误。我是Python新手,只是想学习一下。

def areCollinear(p1, p2, p3):
    slope1 = (p2[1] - p1[1]) / (p2[0] - p1[0])
    slope2 = (p3[1] - p2[1]) / (p3[0] - p2[0])
    if slope1 == slope2:
        print "Points are colinear"
    else:
        print "Points are NOT colinear, what's the matter with you?"

def collinearityTest(pointList):
    position = 0
    while position >=0 and position < len(pointList):

        for p1 in pointList[position]:
            position = position + 1
            for p2 in pointList[position]:
                position = position + 1
                for p3 in pointList[position]:
                    position = position + 1
                    areCollinear(p1, p2, p3)

pointList = [(10, 20), (55, 18), (10, -45.5), (90, 34), (-34, -67), (10, 99)]

collinearityTest(pointList)

错误信息:

Traceback (most recent call last):
  File "C:\Program Files (x86)\Wing IDE 101 4.1\src\debug\tserver\_sandbox.py", line 23, in <module>
  File "C:\Program Files (x86)\Wing IDE 101 4.1\src\debug\tserver\_sandbox.py", line 19, in collinearityTest
  File "C:\Program Files (x86)\Wing IDE 101 4.1\src\debug\tserver\_sandbox.py", line 2, in areCollinear
    if __name__ == '__main__':
TypeError: 'int' object is not subscriptable
python list function graph points
7个回答
13
投票

这是一个更简单且在数值上更稳健且更稳定的函数来测试三点的共线性:

def collinear(p0, p1, p2):
    x1, y1 = p1[0] - p0[0], p1[1] - p0[1]
    x2, y2 = p2[0] - p0[0], p2[1] - p0[1]
    return abs(x1 * y2 - x2 * y1) < 1e-12

(请注意,最好不要对 epsilon 进行硬编码,并使其相对于向量的长度。)


4
投票

错误

主要错误是您试图访问

int
对象的一部分,但这是不可能的。您可以像这样重现类似的错误:

>>> p1 = 1
>>> p1[1]
Traceback (most recent call last):
  File "<pyshell#12>", line 1, in <module>
    p1[1]
TypeError: 'int' object is not subscriptable

其他问题

您的代码有几个问题,尤其是两个问题:

  1. 除法(您没有使用Python 3.x,因此
    /
    的工作方式与您想要的不同,例如,以下内容是正确的:
    3/2==1
    - 您应该使用涉及
    float
    的除法或至少使用
     from __future__ import division
    ),
  2. 三级循环 - 由于复杂性,这是个坏主意,只需使用
    itertools.combinations
    代替即可。

改进框架

你应该这样做:

import itertools

for x, y, z in itertools.combinations(pointList, 3):
    # Check if x, y and z lie on the same line,
    # where x, y and z are tuples with two elements each.
    # And remember to use floats in divisions
    # (eg. `slope1 = float(p2[1] - p1[1]) / (p2[0] - p1[0])`)
    pass

4
投票

您可以使用矩阵(即二维数组)的秩来确定其列(解释为坐标列表)是否共线:当且仅当所有点与原点共线时,其秩为一。

这在所有维度和任意数量的列(即任意数量的点)中都是如此。

请注意,如果我们将点移动恒定的偏移量,那么它们将保持共线。

因此,使用

numpy.linalg.matrix_rank
的优雅方法是:

from numpy.linalg import matrix_rank

def are_collinear(coords, tol=None):
    coords = np.array(coords, dtype=float)
    coords -= coords[0] # offset for collinear points to intersect the origin
    return matrix_rank(coords, tol=tol)==1

在三点情况下的用法:

are_collinear([p0, p1, p2])
。但是您可以插入具有任意数量的点的可迭代对象,而不仅仅是三个,例如您的
pointList
列表。

这里还有几个例子:

# Non-colinear example
are_collinear(np.random.rand(10, 2)) # False

# Colinear examples
x = np.linspace(0, 10, 100)
y = 2 * x + 1
z = 3 * x + 2
are_collinear(np.c_[x, y]) # True
are_collinear(np.c_[x, y, z]) # True

3
投票

你的代码可以更简洁:

import itertools

def arecolinear(points):
    xdiff1 = float(points[1][0] - points[0][0])
    ydiff1 = float(points[1][1] - points[0][1])
    xdiff2 = float(points[2][0] - points[1][0])
    ydiff2 = float(points[2][1] - points[1][1])

    # infinite slope?
    if xdiff1 == 0 or xdiff2 == 0:
        return xdiff1 == xdiff2
    elif ydiff1/xdiff1 == ydiff2/xdiff2:
        return True
    else:
        return False

pointlist = [(10, 20), (55, 18), (10, -45.5), (90, 34), (-34, -67), (10, 99)]

for points in itertools.combinations(pointlist, 3):
    if arecolinear(points):
        print("Points are colinear")
    else:
        print("Points are NOT colinear")

1
投票

因此您需要 3 个 for 循环,并且每个循环都迭代相同的点列表。那为什么还有 while 循环呢?只需将其删除即可。在这种情况下就没有必要了。

此外;

pointList[position]
是一个二维元组,例如 (10,20)。通过编写
for p1 in pointList[position]
,您正在尝试迭代该元组。您想要的是迭代列表。所以请尝试使用
for p1 in pointList
来代替。即,删除尖括号以迭代列表,而不是元组。所以;您也不需要跟踪位置。

所以就变成了

for p1 in pointList:
  for p2 in pointList:
    for p3 in pointList:
        #do something with p1 p2 p3

提示: 您还可以考虑让

areCollinear
函数返回布尔值而不是打印某些内容。并没有真正改变功能,但这是一个更好的实践,因为它使您的函数稍后可以在其他地方重用。


1
投票

我会使用

itertools.combinations()
,但既然你正在尝试学习 Python,那么你的基本问题是:你对
pointList
的了解比你需要的更深。

修改功能:

def collinearityTest(pointList):
    for p1 in pointList:
        for p2 in pointList:
            if p2 is p1:
                continue
            for p3 in pointList:
                if p3 is p2 or p3 is p1:
                    continue
                areCollinear(p1, p2, p3)

for p1 in pointList
将为您提供
pointList
中的每个项目。这正是您想要的。如果您愿意,您也可以使用索引 (
pointList[index]
) 来完成此操作。

再次,去

itertools.combinations()


1
投票

其他答案存在一些问题,例如检查是否被零除。这是我的解决方案,它使用“全部”Python 功能,并且可以检查任意长度的点列表:

def collinear(Points):
    '''Accepts [(x1, y1), (x2, y2), ...] and returns true if the 
    points are on the same line.'''
    ERR=1.0e-12
    if len(Points)<3:
        return True
    x1, y1 = Points[0]
    x2, y2 = Points[1]
    if x2==x1:
        raise Exception("points are not a function")
    m=(y2-y1)/(x2-x1)
    return all([abs(m*(xi-x1)-yi+y1)<ERR for xi,yi in Points[2:]])
© www.soinside.com 2019 - 2024. All rights reserved.