将自定义比较器传递到 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
作为比较器,我假设您要存储long
s。需要此文件是因为 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] < b[element]
。不过这有点乱。这是使用cdef
函数的主要限制——它们不能存储状态
我们能否定义一个 C++ 函数来返回一个 lambda 或类似的东西?以上是关于将自定义比较器传递到 Cython 中的优先级队列的主要内容,如果未能解决你的问题,请参考以下文章
将自定义道具传递给 TypeScript 中的 Redux 表单字段
将自定义ListView项目传递给Android中的其他活动