将自定义比较器传递到 Cython 中的优先级队列

Posted

技术标签:

【中文标题】将自定义比较器传递到 Cython 中的优先级队列【英文标题】:Pass a custom comparer to a priority queue in Cython 【发布时间】:2018-01-27 11:57:10 【问题描述】:

Cython libcpp 模块包含 priority_queue 的模板,这很棒,除了一件事:我无法将自定义比较器传递给它(或者,至少,我不知道如何传递)。

我需要这个,因为我需要priority_queue 来执行某种argsort 而不是sort(是的,优先级队列最适合我想做的事情),而且我需要它快速.

这在 Cython 中是否可能,可能是 wrapping a queue in a custom way,或者根本不可能?

例如,假设我想以一种稳定的方式按其中一个元素对vector[int[:]] 进行排序。实际的算法要复杂得多。

我决定通过将它逐个元素添加到priority_queue 来对其进行排序。但是,我不知道该怎么做。

我的实际操作类似于this question,但是我正在合并按特定元素排序的int[:]s 一维元素,其中原始列表也按该元素排序。

我不介意将对象转换为缓冲区/指针。

【问题讨论】:

你把什么放入优先队列? Python 对象?一个小的代码示例会让您更清楚地尝试实现什么。 知道您想要什么作为比较器也很有用?如果您想使用 cdef 函数(即没有绑定变量)或准备用 C++ 编写的类,这不会太难。使用通用的 Python 可调用对象会更加困难。 @DavidW 它实际上是一维 MemoryView 对象的比较器。只需复制两者的特定元素。 .而且我把它写成 cdef 也没有问题,尽管如果可能的话我不想去 C++。 制作 Python 对象的 C++ 容器(如 memoryviews)真的很困难,所以这听起来不是一个好主意,除非你已经有了一些可以工作的东西。但是,其他人可能不同意并有一个有用的解决方案。 @DavidW 我没有任何可以在 ATM 上工作的东西,但我想让它工作。但是,我可以简单地访问MemoryView 的数据缓冲区。无论如何,我使用的是 Cython MemoryView,而不是 Python ......我意识到这仍然是一个 Python 对象,但我们可以不使用 GIL 等。 【参考方案1】:

这是可能的,但我真的不推荐它。主要问题是:

除非您准备编写引用计数包装器代码(在 C++ 中),否则 C++ 容器无法轻松保存 Python 对象(例如 memoryviews) 您可以获得指向内存视图第一个元素的 C 指针,但是: 您必须确保保留对底层对象(拥有内存)的引用,否则 Python 将释放它,您将使用访问无效内存。 指针会丢失有关数组长度的所有信息。 您可以使用的比较器非常有限(它们必须可以表示为cdef 函数) - 例如,我编写了一个比较数组的第二个元素的比较器,但它需要重新编译为改为比较第三个元素。

因此,我的建议是找到另一种方法。然而:

你需要写一个非常小的C++文件到typedef你想要的优先级队列类型。这使用std::function 作为比较器,我假设您要存储longs。需要此文件是因为 Cython 的模板支持非常有限。

// cpp_priority_queue.hpp
#include <functional>
#include <queue>

using intp_std_func_prority_queue = std::priority_queue<long*,std::vector<long*>,std::function<bool(long*,long*)>>;

然后您不能使用 Cython 提供的 libcpp.queue.priority_queue 包装器。相反,编写自己的,包装你需要的函数(“priority_queue_wrap.pyx”)

# distutils: language = c++

from libcpp cimport bool

cdef extern from "cpp_priority_queue.hpp":
    cdef cppclass intp_std_func_prority_queue:
        intp_std_func_prority_queue(...) # get Cython to accept any arguments and let C++ deal with getting them right
        void push(long*)
        long* top()
        void pop()
        bool empty()

cdef bool compare_2nd_element(long* a, long* b):
    # note - no protection if allocated memory isn't long enough
    return a[1] < b[1]


def example_function(list _input):
    # takes a list of "memoryviewable" objects
    cdef intp_std_func_prority_queue queue = intp_std_func_prority_queue(compare_2nd_element) # cdef function is convertable to function pointer

    cdef long[::1] list_element_mview
    cdef long* queue_element


    for x in _input:
        #print(x)
        list_element_mview = x
        assert list_element_mview.shape[0] >= 2 # check we have enough elements to compare the second one
        queue.push(&list_element_mview[0]) # push a pointer to the first element

    while not queue.empty():
        queue_element = queue.top(); queue.pop()
        print(queue_element[0],queue_element[1]) # print the first two elements (we don't know that we have any more)

然后我创建了一个示例函数,它遍历 memoryview 兼容对象的列表,将它们转换为指针,并将它们添加到队列中。最后,它按顺序通过队列并打印它可以打印的内容。请注意,输入列表比队列更长

最后是一个快速的 Python 测试函数,它可以创建一个合适的列表:

import priority_queue_wrap
import numpy as np

a = np.vstack([np.arange(20),np.random.randint(0,10,size=(20,))])
l = [a[:,n].copy() for n in range(a.shape[1]) ]

print(l)
priority_queue_wrap.example_function(l)

综上所述,Python 对象 + Cython + C++ 是一团糟:我不建议这样做(但你可以尝试!)

【讨论】:

哇,感谢您提供非常详细的答案。有没有办法比较任意元素? 在不编写更多 C++ 的情况下,我能想到的最好的方法是在比较器函数中添加一个全局变量 element 并执行 a[element] &lt; b[element]。不过这有点乱。这是使用cdef 函数的主要限制——它们不能存储状态 我们能否定义一个 C++ 函数来返回一个 lambda 或类似的东西?

以上是关于将自定义比较器传递到 Cython 中的优先级队列的主要内容,如果未能解决你的问题,请参考以下文章

将自定义道具传递给 TypeScript 中的 Redux 表单字段

将自定义ListView项目传递给Android中的其他活动

如何将自定义对象从异步操作过滤器传递到 ASP.net 核心中的控制器?

通过引用传递自定义优先级队列

为啥我在比较我的优先级队列堆中的两个索引的行上得到空值?

如何将自定义结构传递给 C++(非 CLI)中的 _variant_t?