Сортировка OLE SafeArray вариантов в C

#arrays #sorting #c 11 #variant #safearray

#массивы #сортировка #c 11 #вариант #safearray

Вопрос:

Я сортирую OLE SafeArray вариантов. Я решил использовать тип C _variant_t , чтобы упростить задачу.

Массив создается в MS Excel на VBA, и я передаю его первый элемент в мою C / C DLL, которая содержит следующие фрагменты кода.

Все работает хорошо, и я получаю правильные результаты, которые я хочу, с хорошим увеличением скорости сортировки из DLL.

Однако я недоволен тем, сколько кода потребовалось. Мой вопрос в том, как это можно упростить?

В Excel VBA очень часто используется работа с массивами вариантов, где каждый элемент может иметь другой подтип. Например, одним элементом может быть указатель BString, а следующим может быть Double, а следующим может быть Currency, а следующим — Long, а следующим — код ошибки Variant. На самом деле, существует множество возможных типов, и одна структура SafeArray вполне может содержать сразу много разных типов вариантов.

Когда я решил использовать _variant_t тип в C , я надеялся, что при сортировке это приведет к порядку сортировки, который точно соответствует порядку сортировки, который выдает MS Excel при сортировке данных на листе.

Увы, это было совсем не так. Итак, я создал пользовательскую функцию сравнения, которая сортирует ТОЧНО так же, как это делает Excel при отправке в std::sort() или в std::stable_sort() . Однако функция сравнения намного длиннее, чем я ожидал.

И мне нужна была вторая версия для сортировки по убыванию, потому что точный порядок, в котором выполняется сортировка Excel, довольно неожиданный как для возрастания, так и для убывания, и простое обратное логическое значение не подходит близко.

Итак, теперь у меня есть ДВЕ раздражающе длинные функции сравнения.

Есть ли способ сделать это более разумным способом?

Вот мои две функции сравнения (опять же, они работают отлично):

 bool CompareVariantsAscending(const _variant_t lhs, const _variant_t rhs) {

    switch (lhs.vt) {

    case VT_R8: case VT_I4: case VT_DATE: case VT_I2: case VT_R4: case VT_CY: case VT_UI1: case VT_DECIMAL: 
        switch (rhs.vt) {
        default:
            return 0 == VarCmp(amp;(_variant_t)lhs, amp;(_variant_t)rhs, LOCALE_USER_DEFAULT, NORM_IGNORECASE);
            break;
        case VT_NULL: return false; break;
        case VT_EMPTY: case VT_BSTR: case VT_ERROR: case VT_BOOL: return true; break;
        }

    case VT_BSTR:
        switch (rhs.vt) {
        case VT_BSTR:
            return 0 == VarCmp(amp;(_variant_t)lhs, amp;(_variant_t)rhs, LOCALE_USER_DEFAULT, NORM_IGNORECASE);
            break;
        case VT_EMPTY: case VT_ERROR: case VT_BOOL: return true; break;
        default: return false; break;
        }

    case VT_BOOL:
        switch (rhs.vt) {
        case VT_EMPTY: case VT_ERROR: return true; break;
        case VT_BOOL: return lhs.boolVal > rhs.boolVal; break;
        default: return false; break;
        }

    case VT_ERROR:
        switch (rhs.vt) {
        default: return false; break;
        case VT_EMPTY:  return true; break;
        }

    case VT_EMPTY:  // always last
        return false; break;

    case VT_NULL:   // always first
        return true; break; 

    default: return false;

    }

}


bool CompareVariantsDescending(const _variant_t lhs, const _variant_t rhs) {

    switch (lhs.vt) {

    case VT_R8: case VT_I4: case VT_DATE: case VT_I2: case VT_R4: case VT_CY: case VT_UI1: case VT_DECIMAL: 
        switch (rhs.vt) {
        default:
            return 0 < VarCmp(amp;(_variant_t)lhs, amp;(_variant_t)rhs, LOCALE_USER_DEFAULT, NORM_IGNORECASE);
            break;
        case VT_NULL: case VT_BSTR: case VT_BOOL: case VT_ERROR: return false; break;
        case VT_EMPTY: return true; break;
        }

    case VT_BSTR:
        switch (rhs.vt) {
        case VT_BSTR:
            return 0 < VarCmp(amp;(_variant_t)lhs, amp;(_variant_t)rhs, LOCALE_USER_DEFAULT, NORM_IGNORECASE);
            break;
        case VT_ERROR: case VT_BOOL: case VT_NULL: return false; break;
        default: return true; break;
        }

    case VT_BOOL:
        switch (rhs.vt) {       
        case VT_BOOL: return lhs.boolVal < rhs.boolVal; break;
        case VT_ERROR: case VT_NULL: return false; break;
        default: return true; break;
        }

    case VT_ERROR:
        switch (rhs.vt) {
        default: return true; break;
        case VT_NULL: return false; break;
        case VT_ERROR: return false; break;
        }

    case VT_EMPTY:  // always last
        return false; break;

    case VT_NULL:   // always first
        return true; break; 

    default: return false;

    }

}
  

Пожалуйста, обратитесь к следующей таблице с примерами данных.

В первом столбце показаны элементы массива с НЕСОРТИРОВАННЫМИ данными.

Во втором столбце показано, как данные должны быть отсортированы в порядке возрастания.

Третий столбец показывает, как данные должны быть отсортированы в порядке убывания.

  ----------------- ----------------- ----------------- 
|  Data In Array  | Asc Sort Order  | Desc Sort Order |
 ----------------- ----------------- ----------------- 
| anchorage       | -78.96          | #NAME?          |
| 123             | -1              | #N/A            |
| FALSE           | 0               | #DIV/0!         |
| #NAME?          | 0.60625         | TRUE            |
| 0               | 1               | FALSE           |
| 2/18/2019 11:55 | 99.01           | zimmer          |
| #N/A            | 123             | Major Tom       |
| 99.01           | 4/15/2017       | anchorage       |
|                 | 2/18/2019 11:55 | ABC             |
| #DIV/0!         |                 | 888.87          |
|                 | $%^%$^          | $%^%$^          |
| ABC             | 888.87          |                 |
| -78.96          | ABC             | 2/18/2019 11:55 |
| Major Tom       | anchorage       | 4/15/2017       |
| TRUE            | Major Tom       | 123             |
| 4/15/2017       | zimmer          | 99.01           |
| zimmer          | FALSE           | 1               |
| 1               | TRUE            | 0.60625         |
|                 | #NAME?          | 0               |
| -1              | #N/A            | -1              |
| 0.60625         | #DIV/0!         | -78.96          |
| 888.87          |                 |                 |
| $%^%$^          |                 |                 |
 ----------------- ----------------- ----------------- 
  

Обратите внимание, что пустой элемент в 10-й позиции порядка сортировки Asc является VT_BSTR нулевой длины, в то время как два пустых элемента в нижней части столбца являются VT_EMPTY.

Мои функции компаратора, описанные выше, успешно создают эти точные порядки сортировки. Но я хотел бы знать, можно ли это сделать более кратко и / или более эффективно.

Функции компаратора в настоящее время вызываются следующим образом:

    std::stable_sort(Data, Data   Rows, CompareVariantsDescending);
  

Комментарии:

1. Похоже, вы могли бы извлечь некоторые распространенные варианты переключения. Значение Null и ошибка в rhs, похоже, всегда возвращают false во второй функции, за исключением случаев, когда lhs является ошибкой. Вы также свернули некоторые из вариантов, а не другие, которые могли быть свернуты. Кроме этого, вам нужно обработать множество разных случаев, и для этого вам нужен код.

2. Вам не нужны break инструкции после return и комбинировать / переписывать некоторые проверки (Check: ideone.com/6xmuiK ). Еще одна вещь, которую вы можете сделать, это переставить case операторы, которые, по вашему мнению, будут иметь больше совпадений. Код выглядит нормально как есть. Однако MCVE был бы отличным вариантом для экспериментов, если бы вы могли добавить в свой вопрос вместе с заполненными тестовыми данными, например, std::vector и т.д.

3. Кажется нормальным иметь немного кода, если вам нужен конкретный код из-за Excel, поэтому я не вижу, какие большие улучшения вы могли бы внести, кроме незначительных. Тем не менее, если производительность очень важна, вы могли бы: 1) прекратить использование смарт-оболочек (_variant_t) и передавать варианты с использованием указателей (ВАРИАНТ большой, 16 или 24 байта в x64), вы сэкономите немного памяти и немного производительности, и 2) заменить VarCmp (или уменьшить его использование) путем прямого сравнения ВАРИАНТА. Вы немного сэкономите производительность (и мы не знаем стажеров VarCmp). Но это сделает ваши функции больше…