【问题标题】:Efficient data structure keeping objects sorted on multiple keys高效的数据结构保持对象按多个键排序
【发布时间】:2015-05-21 00:47:58
【问题描述】:

我有一个 python 程序,我在其中使用优先级队列来跟踪要处理的对象。目前,队列是使用 SortedList 实现的,效果很好。

但是,我需要扩展此代码,以便列表按多个键排序。有点像在多列上有索引的 SQL 数据库,这样我就可以有效地访问、添加和删除所有键上的对象。我的工作量添加/删除繁重。为了说明我想要做什么,这里有一些伪代码:

ml = MultiSortedList()
ml.append((1, "z", 1.5), a)
ml.append((2, "a", 40.0), b)
ml.append((3, "f", 0.5), c)

print(ml.sorted(0))
   [((1, "z", 1.5), a),
   ((2, "a", 40.0), b),
   ((3, "f", 0.5), c),]

print(ml.sorted(2))
   [((3, "f", 0.5), c),
   ((1, "z", 1.5), a),
   ((2, "a", 40.0), b)]

print(ml.sorted(2).pop(1)
   (1, "z", 1.5), a)

print(ml.sorted(0))
   [((2, "a", 40.0), b),
   ((3, "f", 0.5), c)]

我不太清楚如何有效地做到这一点。当然,我可以为每次访问不同列时再次对列表进行排序,但这太昂贵了。此外,python 列表上的O(n) 删除操作变得很痛苦,因为列表可能包含数千个对象。

是否存在解决此问题的现有数据结构(最好已经在 python 中实现)?如果没有,你能帮我概述一下如何有效地实现这一点吗?

【问题讨论】:

  • 有多少列/不同的字段?是固定数字吗?
  • 另外,为什么不让 sql 为你做这件事呢?
  • 如果你在 python 中寻找 SQL 风格的功能,你看过 pandas 包 - pandas.pydata.org 吗?
  • @sasha:大约 4-5 列。如果这种情况发生变化,我可以接受重写应用程序。在 SQL 中执行此操作的问题只是开销。这是一个会被大量访问的数据结构,所以我认为将其实现为在数据库中添加/删除行(以及序列化对象)会产生过高的开销。

标签: python sorting data-structures heap


【解决方案1】:

您应该使用heap,而不是使用排序列表作为优先级队列的实现。例如,二进制堆具有log(n) 插入和删除。斐波那契堆将有 O(1) 插入。

您在标准库中有一个实现:heapq 模块。

对于您的用例,您需要保留多个堆:每个排序顺序一个。为了清楚起见,您可能希望将原始数据保存在字典中(可能使用随机或递增键),并且只将其键保存在堆中。

使用此技术,您可以轻松获得O(1) 插入和O(log(n)) 删除。你并没有真正指定你将拥有什么样的访问,但如果你需要随机访问,你可以在适当的堆上使用binary search来获取O(log(n))

【讨论】:

  • 我最终使用了一个与此类似的解决方案,它带有一个包装器,它可以根据需要从所有堆中插入和删除对象。感谢您的帮助!
【解决方案2】:

一种方法是在内部维护n 列表,每个排序顺序一个,并为每个排序顺序添加/删除项目。 这样,您“只”将数据结构的操作时间乘以一个常数值(即,如果使用 3 个键而不是一个,则使用 3 log(n) 而不是 log(n) 来删除/插入一个元素)。

我想象的这种实现背后的概念是java的比较器。

为每个键创建一个比较器方法来对它们进行排序,然后在插入和删除时使用它。

它的工作原理如下:

class SortedList(list):
    def __init__(self, comparator = None):
        list.__init__(self)

        self.comparator = comparator

    def add(self, element):
        """ Adds the element into the sorted list.
        If no comparator where provided at initialisation, then try to compare
        the element to item already stored in the list using standard __gt__ 
        and __eq__.

        Argument :
            element : the element to insert in the list

        /!\ NB : A more inteligent implementation should be used, such as binary search... /!\
        """
        index = 0
        while (index < len(self)):
            if self.isGreatterThan(element, self[index]):
                index += 1
            else:
                break

        self.insert(index, element)

    def remove(self, element):
        """ Same as previous, a better approach is possible that use binary search """
        list.remove(self, element)


    def isGreatterThan(self, element, otherElement):
        """ Compare if element is greater than other element. """
        if self.comparator != None:
            return self.comparator(element, otherElement)
        else:
            return element.__gt__(otherElement)


class MultipleKeysObjectContainer():
    def __init__(self, comparators):
        #register the comparators
        self.comparators = comparators
        #create a List for each comparator
        self.data = {}
        for key in self.comparators.keys():
            self.data[key] = SortedList(comparator = self.comparators[key])

    def add(self, element):
        for key in self.data.keys():
            self.data[key].add(element)

    def __repr__(self):
        return "<MultipleKeysObjectContainer :"+self.data.__repr__()+">"

    def __str__(self):
        result = "MultipleKeysObjectContainer{\n"
        for key in self.data.keys():
            result += "\tOrder by : "+key+"{\n"
            for element in self.data[key]:
                result += "\t\t" + str(element) + "\n"
            result += "\t}\n"
        result += "}"
        return result

    def popOrderedBy(self, key, position):
        """ pop the item a the position in the list of item ordered by the key.
        Remove also from other data containers. """
        item = self.data[key].pop(position)
        for dataKey in self.data.keys():
            if dataKey != key:
                self.data[dataKey].remove(item)

        return item



if __name__ == "__main__":
    a = SortedList(lambda x,y : x[0][0] > y[0][0])

    item1 = ((1, "z", 1.5),"foo")
    item2 = ((2, "a", 40.0), "bar")
    item3 = ((3, "f", 0.5), "barfoo")

    a.add(item1)
    a.add(item3)
    a.add(item2)
    print("Example of sorted list")
    print(a)
    a.remove(item3)
    print("The same list without the barfoo")
    print(a)


    b = MultipleKeysObjectContainer({"id": (lambda x,y : x[0][0] > y[0][0]), "letter": (lambda x,y : x[0][1] > y[0][1] ), "value":(lambda x,y : x[0][2] > y[0][2])})

    b.add(item1)
    b.add(item3)
    b.add(item2)
    print("A multiple key container, object are ordered according three criterion.")
    print(b)
    print("Remove the first item if items are ordered by letter", b.popOrderedBy("letter", 0))
    print("After this removing the container contains :")
    print(b)

这会导致:

Example of sorted list
[((1, 'z', 1.5), 'foo'), ((2, 'a', 40.0), 'bar'), ((3, 'f', 0.5), 'barfoo')]
The same list without the barfoo
[((1, 'z', 1.5), 'foo'), ((2, 'a', 40.0), 'bar')]
A multiple key container, object are ordered according three criterion.
MultipleKeysObjectContainer{
    Order by : id{
        ((1, 'z', 1.5), 'foo')
        ((2, 'a', 40.0), 'bar')
        ((3, 'f', 0.5), 'barfoo')
    }
    Order by : value{
        ((3, 'f', 0.5), 'barfoo')
        ((1, 'z', 1.5), 'foo')
        ((2, 'a', 40.0), 'bar')
    }
    Order by : letter{
        ((2, 'a', 40.0), 'bar')
        ((3, 'f', 0.5), 'barfoo')
        ((1, 'z', 1.5), 'foo')
    }
}
Remove the first item if items are ordered by letter ((2, 'a', 40.0), 'bar')
After this removing the container contains :
MultipleKeysObjectContainer{
    Order by : id{
        ((1, 'z', 1.5), 'foo')
        ((3, 'f', 0.5), 'barfoo')
    }
    Order by : value{
        ((3, 'f', 0.5), 'barfoo')
        ((1, 'z', 1.5), 'foo')
    }
    Order by : letter{
        ((3, 'f', 0.5), 'barfoo')
        ((1, 'z', 1.5), 'foo')
    }
}

这看起来像你正在寻找(几乎,只需添加二进制搜索:p) 祝你好运!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-19
    • 1970-01-01
    • 2012-05-03
    • 2021-11-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多