【问题标题】:How do I remove almost-duplicate integers from list?如何从列表中删除几乎重复的整数?
【发布时间】:2014-06-02 03:51:46
【问题描述】:

我正在用 Python 解析一些 PDF。这些 PDF 在视觉上被组织成行和列。 pdftohtml 脚本将这些 PDF 转换为 XML 格式,其中充满了松散的 <text> 标签,这些标签没有任何层次结构。然后,我的代码需要将这些 <text> 标记重新排序为行。

由于每个<text> 标记都具有“顶部”或“左侧”坐标等属性,因此我编写了代码以将具有相同“顶部”坐标的<text> 项目附加到列表中。这个列表实际上是一行。

我的代码首先遍历页面,找到所有唯一的“顶部”值,并将它们附加到顶部列表中。然后它遍历这个顶级列表。对于每个唯一的最高值,它会搜索具有该“最高”值的所有项目并将它们添加到行列表中。

for side in page:
    tops = list( set( [ d['top'] for d in side ] ) )
    tops.sort()
    for top in tops:
        row = []
        for blob in side:
            if int(blob['top']) == int(top):
                row.append(blob)
        rows.append(row)

此代码适用于我正在解析的大多数 PDF。但在某些情况下,同一行上的项目的最高值略有不同,相差一到两个。

我正在尝试调整我的代码,使其变得更加模糊。

底部的比较似乎很容易修复。像这样的:

        for blob in side:
            rangeLower = int(top) - 2
            rangeUpper = int(top) + 2
            thisTop = int(blob['top'])
            if rangeLower <= thisTop <= rangeUpper :
                row.append(blob)

但我首先创建的唯一顶级值列表是一个问题。我使用的代码是

    tops = list( set( [ d['top'] for d in side ] ) )

在这些边缘情况下,我最终会得到如下列表:

[925, 946, 966, 995, 996, 1015, 1035]

如何调整该代码以避免列表中出现“995”和“996”?我想确保当整数彼此相差 1 或 2 时,我只得到一个值。

【问题讨论】:

  • 如果您的列表中有1,2,3,4,5,您会选择哪一个? 1和4? 1 和 5 ? 2和5? 3?
  • 在我正在解析的 PDF 中,行始终间隔至少 20 个单位,所以我认为我不会得到这样的列表。

标签: python list sorting


【解决方案1】:
  • 对列表进行排序以将关闭的值彼此相邻
  • 使用reduce根据前一个值过滤值

代码:

>>> tops = [925, 946, 966, 995, 996, 1015, 1035]
>>> threshold = 2
>>> reduce(lambda x, y: x + [y] if len(x) == 0 or y > x[-1] + threshold else x, sorted(tops), [])
[925, 946, 966, 995, 1015, 1035]

有几个连续的值:

>>> tops = range(10)
>>> reduce(lambda x, y: x + [y] if len(x) == 0 or y > x[-1] + threshold else x, sorted(tops), [])
[0, 3, 6, 9]

编辑

Reduce 读起来可能有点麻烦,所以这里有一个更直接的方法:

res = []
for item in sorted(tops):
    if len(res) == 0 or item > res[-1] + threshold:
        res.append(item)

【讨论】:

  • 你能带我看看你的第一个代码示例中的 reduce() 行吗?这看起来像我需要的,我只是想了解发生了什么。
  • reduce 像这样工作。它接受第一个值(在本例中是最后一个参数,[] 空列表),然后调用 lambda,其中 x 是那个值,y 是列表的第一个值。然后它重复调用,其中 x 是前一次调用的结果,y 是列表中的下一项。它返回最终结果。例如,reduce(lambda x,y: x+[y], tops, []) 复制了tops
  • lambda 中的测试内容只有在大于前一个元素+阈值的情况下才会追加y。这个条件就足够了,因为列表是单调增长的。 (len(x) == 0 被添加到测试中以说明第一次迭代。)
  • a if condition else b 是 python 中的三元符号。如果条件为真,则返回 a,如果条件为假,则返回 b。
【解决方案2】:

@njzk2 的回答也有效,但是这个函数实际上显示了正在发生的事情并且更容易理解:

>>> def sort(list):
...     list.sort() #sorts in ascending order
...     x = range(0, len(list), 1) #gets range
...     x.reverse() #reverses
...     for k in x:
...             if list[k]-1 == list[k-1]: #if the list value -1 is equal to the next,
...                     del(list[k-1])     #remove it
...     return list #return
... 
>>> tops = [925, 946, 966, 995, 996, 1015, 1035]
>>> sort(tops)
[925, 946, 966, 996, 1015, 1035]
>>> 

【讨论】:

  • del(list[k-1]) 效率极低。您不需要reverse,只需使用range(len(tops) -1, -1, -1)。如果您打算使用reverse,请使用reversed,它返回一个迭代器,而不是执行完整的原位反转过程。题中提到within 1 or 2 of each other,所以1是不够的。
  • 另外,你迭代的项目太多了,因为 k 低至 0。(如果你测试相等的项目并且所有项目都相等,它将返回一个空列表)
  • 另外,你真的不应该列出一个列表list
  • 另外,这会修改(就地排序)输入列表。这可能不是想要的效果。 (虽然函数名确实是sort
猜你喜欢
  • 2019-04-07
  • 1970-01-01
  • 2011-06-01
  • 2013-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-25
  • 1970-01-01
相关资源
最近更新 更多