Использование qsort в Cython для получения индекса/перестановки сортировки

Обзор

Есть несколько вопросов, похожих на этот, но все они немного отличаются. Чтобы было ясно, если values представляет собой массив целых чисел, я хочу найти perm такое, что sorted_values (values отсортировано некоторым оператором сравнения) задается как

sorted_values[i] = values[perm[i]]

Шаг 1

Как это сделать на Си? Что ж, qsort требует объявления функции сравнения, чтобы сказать вам, больше ли одно значение, чем другое. Если мы сделаем values глобальной переменной, то сможем использовать эту функцию сравнения для сортировки массива perm, изначально установленного в 0:N-1 (где N — длина values), не сравнивая perm[i] с perm[j], а вместо этого сравнивая values[perm[i]] с values[perm[j]]. См. эту ссылку. Некоторый пример кода C:

// sort_test.c
#include <stdio.h>
#include <stdlib.h>

int *values;

int cmpfunc (const void * a, const void * b) {
   return ( values[*(int*)a] - values[*(int*)b] );
}

int main () {
   int i;
   int n = 5;
   int *perm;

   // Assign memory
   values = (int *) malloc(n*sizeof(int));
   perm = (int *) malloc(n*sizeof(int));

   // Set values to random values between 0 and 99
   for (i=0; i<n; i++) values[i] = rand() % 100;

   // Set perm initially to 0:n-1
   for (i=0; i<n; i++) perm[i] = i;

   printf("Before sorting the list is: \n");
   for (i=0; i<n; i++) printf("%d ", values[i]);

   qsort(perm, n, sizeof(int), cmpfunc);

   printf("\nThe sorting permutation is: \n");
   for (i=0; i<n; i++) printf("%d ", perm[i]);

   free(values);
   free(perm);

   printf("\n");

   return(0);
}

Конечно, хитрость заключается в том, чтобы определить values глобально, чтобы cmpfunc мог его видеть.

Шаг 2

Как это сделать на Цитоне? К сожалению, я не могу заставить Cython использовать тот же трюк со значениями, объявленными глобально. Моя лучшая попытка - следующая, основанная на ответе здесь, однако разница в том, что они просто сортируют массив, который им не нужен для индексации/перестановки.

# sort_test_c.pyx
cimport cython
from libc.stdlib cimport qsort

# Try declaring global variable for the sort function
cpdef long[:] values

cdef int cmpfunc (const void *a , const void *b) nogil:
    cdef long a_v = (<long *> a)[0] 
    cdef long b_v = (<long *> b)[0]
    return (values[a_v] - values[b_v]);

def sort(long[:] py_values, long[:] perm, int N):
    # Assign to global
    values = py_values

    # Make sure perm is 0:N-1
    for i in range(N):
        perm[i] = i

    # Perform sort
    qsort(&perm[0], N, perm.strides[0], &cmpfunc)

Это можно скомпилировать с помощью

cythonize -i sort_test_c.pyx

и протестировано со скриптом

# sort_test.py
from sort_test_c import sort
import numpy as np

n = 5
values = np.random.randint(0, 100, n).astype(int)
perm = np.empty(n).astype(int)

sort(values, perm, n)

Однако это жалуется на нашу глобальную переменную values, т.е.

UnboundLocalError: local variable 'values' referenced before assignment
Exception ignored in: 'sort_test_c.cmpfunc

и перестановка сортировки неверна (если только values уже не упорядочены, в этом случае это удача, поскольку perm всегда возвращает массив 0:4). Как я могу это исправить?


person rwolst    schedule 15.02.2018    source источник
comment
Глобальные переменные в Python и Cython находятся на уровне модуля, а не на самом деле глобальны. (Я не уверен, что этот подход к проблеме является лучшим, хотя)   -  person DavidW    schedule 15.02.2018
comment
Я согласен, мне не нравится решение использовать глобальную переменную, но я не вижу способа обойти это с помощью qsort API   -  person rwolst    schedule 15.02.2018
comment
структура значений и индексов выглядит стандартным подходом. В качестве альтернативы вы можете отсортировать массив указателей и вычислить из него индексы.   -  person DavidW    schedule 15.02.2018
comment
Ссылка - гораздо более приятный способ сделать это, по какой-то причине я не подумал об использовании структуры. Теперь API функции сравнения имеет больше смысла.   -  person rwolst    schedule 15.02.2018