Контейнеры STL без учета регистра (например, std :: unordered_set)

Каков самый короткий, самый кроссплатформенный способ создать контейнер std :: unordered_set, НЕЗАВИСИМЫЙ от CASE-INSENSITIVE?

my_set.insert("Apples");  
my_set.insert("apples"); //Insert doesn't occur because of duplicate item

Я знаю, что STL предоставляет Hash и Pred. Каким должен быть Хеш? Каким должен быть Pred? если они не являются встроенными, укажите для них код вместе с примером их использования (т.е. как мне объявить std::unordered_set?).

Из-за критики я подробно остановлюсь на том, что пытаюсь сделать. Мне нужен высокопроизводительный прозрачный HTTP-прокси-сервер, одна из вещей, которые он выполняет, - это быстро просматривает поля заголовка HTTP. Поля заголовка HTTP определены как нечувствительные к регистру, поэтому мне нужен контейнер без учета регистра.


person unixman83    schedule 25.12.2011    source источник
comment
Вы пробовали унаследовать его и переопределить методы добавления и получения элементов из него?   -  person davogotland    schedule 25.12.2011
comment
@davogotland, контейнеры STL не предназначены для наследования.   -  person Don Reba    schedule 25.12.2011


Ответы (3)


Определение unordered_set:

  template <class Value,
            class Hash = hash<Value>,
            class Pred = std::equal_to<Value>,
            class Alloc = std::allocator<Value> >
  class unordered_set;

Если вы предоставите функторы Hash и Pred без учета регистра, то набор также станет таким.

Это простой пример, строка хеш-функция проста c, но вы можете изменить ее по своему усмотрению.

struct MyHash
{
    size_t operator()(const std::string& Keyval) const
    {
        //You might need a better hash function than this
        size_t h = 0;
        std::for_each( Keyval.begin() , Keyval.end() , [&](char c )
        {
            h += tolower(c);
        });
        return h;
    }
};

struct MyEqual
{
    bool operator()(const std::string& Left, const std::string& Right) const
    {
        return Left.size() == Right.size() 
             && std::equal ( Left.begin() , Left.end() , Right.begin() ,
            []( char a , char b )
        {
            return tolower(a) == tolower(b); 
        }
        );
    }
};


int main()
{
    std::unordered_set< std::string , MyHash , MyEqual > m;

    m.insert( "Apple" );
    m.insert( "apple" );

    return 0;
}
person parapura rajkumar    schedule 25.12.2011
comment
Кстати, вы можете сделать то же самое с классом set с функтором, который он использует для сравнения значений. - person ; 25.12.2011
comment
Хотя вам следует придерживаться tolower или toupper как в MyHash, так и в MyEqual, потому что они не всегда транзитивны. Еще лучше использовать правильное сворачивание регистра, описанное в спецификации Unicode. - person dalle; 25.12.2011
comment
Проблема в том, что значения не чувствительны к регистру. Если вы перебираете набор, он вернет «яблоко» или «яблоко»? - person Martin York; 25.12.2011
comment
@LokiAstari Что нужно поставить первым? второй insert потерпит неудачу. - person unixman83; 25.12.2011
comment
@ unixman83: вторая вставка не завершилась ошибкой (ошибка указывает на ошибку). Он просто не меняет набор (как определено в Pred). Так что вполне возможно (хотя я думаю маловероятно), чтобы реализация перезаписала исходное (первое значение). - person Martin York; 25.12.2011
comment
@dalle Я изменил свой ответ, чтобы использовать tolower повсюду, и кажется, что std :: equal также требует сравнения размера и сначала - person parapura rajkumar; 25.12.2011
comment
@LokiAstari: en.cppreference.com/w/cpp/container/unordered_set/insert возвращает пару, состоящую из логического значения, указывающего, произошла ли вставка, и итератора для вставленного элемента. - person dalle; 25.12.2011
comment
@dalle: Пожалуйста, не цитируйте мне этот сайт (он ужасен, содержит множество неточностей и мнение разработчиков (которое не всегда верно)). Вам нужно n3242 23.2.4 Associative containers [associative.reqmts] Где вы найдете Table 103 — Unordered associative container requirements (in addition to container). Если у вас нет копии стандарта, получите ее: stackoverflow.com/a/4653479/14065 - person Martin York; 26.12.2011
comment
@LokiAstari: Хорошо, тогда вы можете прочитать это сами. Возвращает: компонент bool возвращенного объекта пары указывает, имела ли место вставка, и компонент итератора указывает на элемент с ключом, эквивалентным ключу value_type (obj). - person dalle; 26.12.2011
comment
@LokiAstari: И таблица, на которую вы ссылаетесь, состояния. Эффекты: Вставляет t тогда и только тогда, когда в контейнере нет элемента с ключом, эквивалентным ключу t. Компонент bool возвращаемой пары указывает, имеет ли место вставка, а компонент итератора указывает на элемент с ключом, эквивалентным ключу t. который отвечает на ваш собственный вопрос. - person dalle; 26.12.2011
comment
@dalle: Да. Я указывал, что вы были правы. Просто используя дерьмовый источник. - person Martin York; 26.12.2011

Лично я бы определил тип значения, который нечувствителен к регистру и превращается в строку при простейшем намеке. Это позволило мне использовать стандартные модели хеширования и предикатов.

#include <string>
#include <unordered_set>
#include <iostream>
#include <algorithm>
#include <iterator>

class LCString
{
    std::string  data;

    public:
        operator std::string&()             {return data;}
        operator std::string const&() const {return data;}

        LCString(char const* init)
        {
            std::transform(init, init + strlen(init),
                           std::back_inserter(data), &::tolower);
        }
};

int main()
{
    typedef std::unordered_set<LCString, 
                               std::hash<std::string>, 
                               std::equal_to<std::string> >  MySet;
    MySet  data;
    data.insert("Apples");
    data.insert("apples");

    std::copy(data.begin(), data.end(),
              std::ostream_iterator<std::string>(std::cout, " - "));
    std::cout << "\n";
}

Таким образом, мы помещаем в набор только строчные значения:

> g++ pl.cpp
> ./a.out
apples -
>

Редактировать сохранение регистра:

class LCStringOriginalPreserved
{
    std::string  original;
    std::string  data;

    public:
        operator std::string&()             {return data;}
        operator std::string const&() const {return data;}

        std::string& getOriginal()          {return original;}

        LCString(char const* init)
          : original(init)
        {
            std::transform(original.begin(), original.end(),
                           std::back_inserter(data), &::tolower);
        }
};
person Martin York    schedule 25.12.2011
comment
Это хорошая идея, но что, если мне нужно поведение с сохранением регистра / без учета регистра. Это не сохраняет дело. - person unixman83; 25.12.2011
comment
@ unixman83: Если зайдете: data.insert("Apple");data.insert("apple"); Какую версию вы ожидаете в комплекте? Теперь у вас не может быть сохранения случая, так как случайный будет потерян. Таким образом, концепция сохранения регистра для нечувствительного к регистру набора не имеет смысла. Теперь, если бы вы сказали unordered_multiset, это имело бы смысл. Но сказанное выше легко расширить. Просто добавьте еще одно поле в LCString, чтобы сохранить исходное значение, чтобы вы могли получить его явно. - person Martin York; 25.12.2011
comment
Я ожидаю, что первый, который я вставил, будет в наборе, с нужным регистром. - person unixman83; 25.12.2011
comment
@ unixman83: Это то, что вы, вероятно, получите в большинстве ситуаций, но в стандарте нет ничего, что могло бы гарантировать это с чем-либо, представленным здесь. Чтобы обеспечить эту гарантию, вам придется перепрыгнуть еще пару обручей. Но, как я сказал ранее, это требование не имеет смысла. - person Martin York; 25.12.2011
comment
@LokiAstari А? Если он просто использует нечувствительный к регистру компаратор, он гарантированно получит первую вставку в наборе, потому что вторая ничего не вставляет, потому что она уже есть в наборе. Совершенно стандартное поведение. - person Christian Rau; 25.12.2011
comment
@ChristianRau: Хорошо, я посмотрел, да, это поведение (хотя я не думаю, что очевидно, что это должно быть определено таким образом, и я мог видеть, что реализации определяют вставку в терминах оператора []). Но я рад, что это так определено .n3242 23.2.4 Associative containers [associative.reqmts] - person Martin York; 26.12.2011

Мне это больше нравится.

Работает на Linux.

#include <strings.h>
#include <ctype.h>

#include <string>
#include <functional>
#include <tr1/functional_hash.h>

struct iequal_to : public std::binary_function <std::string,std::string,bool>
{
  bool operator() (const std::string& x, const std::string& y) const
  {
    return (!strcasecmp(x.c_str(), y.c_str()));
  }
};

const std::string LC(const std::string& x)
{
  std::string ret(x);
  std::string::size_type i;
  for(i = 0; i < x.size(); ++i)
    ret[i] = tolower(x[i]);
  return ret;
}

struct ihash : public std::unary_function <std::string,size_t>
{
  size_t ihash::operator() (const std::string& x) const
  {
    return std::tr1::hash<std::string>()(LC(x));
  }
};
person unixman83    schedule 25.12.2011
comment
Лучше чем что? Это не ответ! Разве это не просто перефразированная версия ответа парапуры? Полностью устарело! Что такое strcasecmp? И действительно ли вы создаете новую строку каждый раз при вызове хеш-функции? - person Christian Rau; 25.12.2011
comment
strcasecmp является стандартным в библиотеке Linux glibc C (см. stricmp). - person unixman83; 25.12.2011