Сделал контейнер для ключей типа "double" без чёткого совпадения ключа. Интересно было бы получить конструктивной критики

alexgauss1994
Offline
Зарегистрирован: 28.07.2014

Возникла задача, для которой понадобилось хранить данные в таком виде (почти буквально) :

array.set(0.0, 0);
array.set(1.0, 1);
array.set(2.0, 2);
array.set(3.0, 3);

И чтобы при этом такой код :

array.get(0.3)

Вернул 0 - т.е. содержимое по ближайшему к 0.3 ключу.

Готовых контейнеров такого типа не нашёл, потому написал свой. Также, т.к. fpu нет, а значений которые требуется различить немного - сделал сведение "ключа" к целому виду (с домножением на коофицент - т.е. допустим 0*50 = 0, 1*50 = 50, ... 3*50=150, 0.3*50 = 15 - ближе к 0, и притом ключ можно уложить в 1 байт).  Можно и сводить к double с коофицентом 1, но в моей задаче не трубуется. Однако устройство позволяет.

Ну и до кучи - загрузка/сохранение в EEPROM, возможно есть более простые методы?

Код тут или на гитхабе

#ifndef FloatKeyContainer_H
#define FloatKeyContainer_H 

#include <EEPROM.h>
typedef unsigned int size_t;
typedef uint8_t byte;

/*
 * size - максимальный размер
 * key_trim_cooficient - коофицент для приведения ключа к key_type (например, для ключа между 0.0 и 1.0, и типа ключа "байт" - 255)
 * key_type - тип ключа
 * value_type - тип хранимого значения
 */
template<size_t size, int key_trim_cooficient, typename key_type, typename value_type> class FloatKeyContainer {
  private:
    size_t elements;
    key_type keys[size];
    value_type values[size];
  public:
    /*
     * Создадим и зальём нулями
     */
    FloatKeyContainer() {
      for(size_t i=0;i<size;i++) {
        this->keys[i] = 0.0;
        this->values[i] = 0;
      }
      this->elements = 0;
    }
    
    /* 
     * Приведем от double к нашему типу
     */ 
    key_type key_converted(double key) {
      return (key_type)(key*key_trim_cooficient);
    }
    
    /*
     * Вставка пары "ключ-значение"
     */
    void insert(double dkey, value_type value) {
      key_type key = this->key_converted(dkey);
      if(this->elements==0) {
        this->keys[0] = key;
        this->values[0] = value;
        this->elements = 1;
      }
      else { //Т.к. используется бинарный поиск ключа - массив ключей должен быть отсортирован. И значения должны располагаться в соответсвующих ключу позициях
        size_t i;
        for(i=0;i<this->elements;i++) { //Ищем позицию для вставки
          if(this->keys[i]>=key) {
            break;
          }
        }
        for(size_t j=i; j<size-1; j++) { //Сдвигаем элементы вправо
          this->keys[j+1] = this->keys[j];
          this->values[j+1] = this->values[j];
        }
        //Добавляем пару
        this->elements++;
        this->keys[i] = key;
        this->values[i] = value;
      }
    }
    
    /*
     * Получение номера "ближайшего" ключа (сравнением с 2 существующими)
     * key - результат приведения ключа от double к внутреннему типу
     * first - идекс меньшего ключа из this->keys
     * second - индекс большего из this->keys
     */
    inline size_t getNearestKey(key_type key, size_t first, size_t second) {
      return abs(key-this->keys[first]) < abs(this->keys[second]-key) ? first : second;
    }
    
    /*
     * Номер ближайшего ключа (во всем массиве this->keys)
     * dkey - ключ для сравнения (в непреобразованном виде)
     */
    size_t getIndex(double dkey) {
      key_type key = this->key_converted(dkey);
      if(this->elements==1) {
        return 0;
      }
      else {
        //Бинарный поиск
        size_t left = 0,
               right = this->elements,
               middle;
        while(left<=right) {
          middle = left+(right-left)/2;
          if(key<this->keys[middle]) {
            right = middle-1;
          }
          else if(key>this->keys[middle]) {
            left = middle+1;
          }
          else {
            break;
          }
        }
        
        //Сравнение исходного числа, найденного ключа и 2 соседних (если они есть)
        if( (middle!=0 && middle!=elements-1) && this->elements>2) {
          return this->getNearestKey(key,
            this->getNearestKey(key, middle-1, middle),
            this->getNearestKey(key, middle, middle+1)
          );
        }
        else if(middle==0) {
          if(elements<2) {
            return 0;
          }
          else {
            return this->getNearestKey(key, 0, 1);
          }
        }
        else if(middle==elements-1) {
          if(size<2) {
            return 0;
          }
          else {
            return this->getNearestKey(key, 0, 1);
          }
        }
      }
    }
    
    /*
     * Получение значения по ключу
     */
    value_type get(double key) {
      return this->values[this->getIndex(key)];
    }
    
    /*
     * Задание значения по ключу
     */
    void set(double key, value_type value) {
      this->values[this->getIndex(key)] = value;
    }
    
    /*
     * Загрузка из EEPROM
     */
    void load(size_t address) {
      //Чтение числа элементов
      byte* elements_count_memory = (byte*)&this->elements; 
      size_t elements_count_memory_size = sizeof(size_t);
      for(size_t i=0; i<elements_count_memory_size; i++) {
        elements_count_memory[i] = EEPROM.read(address + i);
      }
      
      //Ключи
      byte* keys_memory = (byte*)this->keys;
      size_t keys_memory_size = sizeof(key_type)*size;
      for(size_t i=0; i<keys_memory_size; i++) {
        keys_memory[i] = EEPROM.read(address + elements_count_memory_size + i);
      }

      //Значения
      byte* values_memory = (byte*)this->values;
      size_t values_memory_size = sizeof(value_type)*size;
      for(size_t i=0; i<values_memory_size; i++) {
        values_memory[i] = EEPROM.read(address + elements_count_memory_size + keys_memory_size + i);
      }
    }
    
    /*
     * Загрузка в EEPROM
     */
    void save(size_t address) {
      //Сохранение числа элементов
      byte* elements_count_memory = (byte*)&this->elements;
      size_t elements_count_memory_size = sizeof(size_t);
      for(size_t i=0; i<elements_count_memory_size; i++) {
        EEPROM.write(address + i, elements_count_memory[i]);
      }
      
      //Сохранение ключей
      byte* keys_memory = (byte*)this->keys;
      size_t keys_memory_size = sizeof(key_type)*size;
      for(size_t i=0; i<keys_memory_size; i++) {
        EEPROM.write(address + elements_count_memory_size + i, keys_memory[i]);
      }

      //Сохранение значений
      byte* values_memory = (byte*)this->values;
      size_t values_memory_size = sizeof(value_type)*size;
      for(size_t i=0; i<values_memory_size; i++) {
        EEPROM.write(address + elements_count_memory_size + keys_memory_size + i, values_memory[i]);
      }
    }
};

#endif

Вот пример

  FloatKeyContainer<4, 50, byte, int> dict;
  dict.insert(0.0, 0);
  dict.insert(1.0, 1);
  dict.insert(2.0, 2);
  dict.insert(3.0, 3);
  dict.save(2);

...
  FloatKeyContainer<4, 50, byte, int> other;
  other.load(2);
  Debug("Found next : ", other.get(1.3));

Выдает, как и следовал ждать - 1.

Может кто-то предложит, что можно здесь улучшить?

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Скорее всего специальное применение. getNearest можно вынести как функтор или параметр шаблона, тогда можно искать ближайший меньший или больший.
Ещё, бинарный поиск кажется слегка сложная реализация, но я щас уже сплю, потому могу ошибаться.

alexgauss1994
Offline
Зарегистрирован: 28.07.2014

kisoft пишет:
Скорее всего специальное применение.

Угадали, да. Но есть на примете пара идей :-)

kisoft пишет:
getNearest можно вынести как функтор или параметр шаблона, тогда можно искать ближайший меньший или больший.

Возможно, есть смысл. Но по моему целесообазнее сделать её переопределяемой - чтобы можно было создать класс-потомок. Может накидаю завтра обновленный вариант.

kisoft пишет:
Ещё, бинарный поиск кажется слегка сложная реализация, но я щас уже сплю, потому могу ошибаться.

По моему тут больше заняло сравнение не только с найденным ключом, но и 2 соседними (ну нашло оно 1, допустим. Неплохо бы с 0 и 2 сравнить же :-) )