Перебрать std::vector в отсортированном порядке

Я получаю от API вектор Foo следующим образом:

std::vector<Foo> foos;

Затем я написал функцию с именем

std::vector<std::string> getKeys(const std::vector<Foo>&)

который перебирает контейнер и извлекает ключ типа std::string для каждого объекта Foo.

Как бы вы перебирали объекты Foo в foos в отсортированном порядке, где сортировка выполняется по ключу и без учета регистра. Кроме того, я бы предпочел не делать отсортированную копию foos, поскольку она имеет большой размер.

Вот моя попытка, которая работает, но мне интересно, можно ли это сделать лучше.

struct CaseInsensitiveComparitor {
    bool operator ()(const std::pair<std::string, Foo&> lhs, const std::pair<std::string, Foo&> rhs) const {
        std::string str1 = lhs.first;
        boost::algorithm::to_lower(str1);
        std::string str2 = rhs.first;
        boost::algorithm::to_lower(str2);
        return (str1 < str2);
    }
};

// map key to Foo
std::vector<std::pair<std::string, Foo*> > tempFoos;
{
   std::vector<std::string> keys = getKeys(foos);
   std::vector<std::string>::iterator begin = keys.begin();
   std::vector<std::string>::iterator i = keys.begin();
   std::vector<std::string>::iterator end = keys.end();
   for(;i!=end;++i)
   {
       tempFoos.push_back(*i, &foos[distance(begin,i)]);
   }

   std::sort(tempFoos.begin(), tempFoos.end(), CaseInsensitiveComparitor());
}

std::vector<Foo*> sortedFoos;
std::vector<std::pair<std::string, Foo*> >::iterator i = tempFoos.begin();
std::vector<std::pair<std::string, Foo*> >::iterator end = tempFoos.end();   
for(;i!=end;++i)
{
   sortedFoos.push_back(i->second);
}

person Baz    schedule 05.09.2013    source источник
comment
Что не работает в вашей попытке?   -  person Jim Jeffries    schedule 05.09.2013
comment
@jamesj Это работает, но мне было интересно, можно ли это сделать лучше или можно ли его улучшить.   -  person Baz    schedule 05.09.2013
comment
Пожалуйста, опубликуйте код, который вы используете, и то, что вы ожидаете / хотите, чтобы произошло. В коде, который вы разместили, вы получаете доступ к sortedFoos, прежде чем объявить его, вы увеличиваете end, а не i, это определенно не (часть) кода, который вы пытались   -  person Pieter    schedule 05.09.2013
comment
Как ключи связаны с Foos?   -  person juanchopanza    schedule 05.09.2013
comment
@Pieter Теперь я исправил ошибки компиляции в приведенном выше коде, но мой вопрос касается дизайна. Приведенный выше код дает представление о том, как я решил бы эту проблему.   -  person Baz    schedule 05.09.2013


Ответы (4)


Вы заботитесь о том, чтобы в настоящее время перебирать foos три раза и сортировать его один раз. Это то, что сделает ваш алгоритм менее производительным на больших массивах. Почему бы не изменить его, чтобы сделать следующее

  1. перебрать его, чтобы извлечь указатели в std::vecotr<Foo*> с именем fooPtrVec
  2. Измените функцию сравнения, чтобы разыменовать Foo*, и используйте ключевое поле в Foo для сравнения. Вызовите функцию YourNewComparisonFunction
  3. используйте std::sort(fooPtrVec.begin(), fooPtrVec.end(), YourNewComparisonFunction()) для сортировки вектора Foo*
person Jim Jeffries    schedule 05.09.2013
comment
Звучит как лучший подход! - person Baz; 05.09.2013
comment
Меньше памяти, меньше циклов процессора, меньше кода, чем в других реализациях... Что не нравится? ;) - person Jim Jeffries; 05.09.2013
comment
Это зависит от того, как вычисляются ключи... - person Jarod42; 05.09.2013
comment
@ Jarod42, ты прав. Я сделал предположение, что ключ был просто полем на объектах Foo. Если он рассчитан, ваш ответ имеет больше смысла (+1 вам!) - person Jim Jeffries; 06.09.2013

В качестве альтернативы вашей попытке вы можете создать массив индексов

std::vector<size_t> indexes;
for (size_t i = 0; i != keys.size(); ++i) { indexes.push_back(i); }

с помощью компаратора:

struct Comparator {
    explicit Comparator(const std::vector<string>& keys) : keys(&keys) {}

    bool operator ()(size_t lhs, size_t rhs) const {
        std::string str1 = (*keys)[lhs];
        boost::algorithm::to_lower(str1);
        std::string str2 = (*keys)[rhs];
        boost::algorithm::to_lower(str2);
        return (str1 < str2);
    }
private:
    const std::vector<string>* keys;
};

отсортировать этот массив индексов

std::sort(indexes.begin(), indexes.end(), Comparator(keys));

Теперь вы можете перебирать foos и/или ключи с косвенным индексом:

std::vector<Foo*> sortedFoos;
for (size_t i = 0; i != indexes.size(); ++i) {
    sortedFoos.push_back(&foos[indexes[i]]);
}
person Jarod42    schedule 05.09.2013
comment
+1 за инверсию логики сравнения - person fjardon; 05.09.2013
comment
Вам все равно придется перебирать коллекцию еще раз, чтобы получить указатели. Почему бы не отсортировать указатели в первую очередь? - person Jim Jeffries; 05.09.2013
comment
Вместо создания локальной копии строк для каждого сравнения (чтобы сделать их строчными), как насчет того, чтобы написать компаратор char, который вызывает std::tolower перед сравнением, а затем вызывает std::lexicographical_compare для двух строк (которые теперь можно хранить локально как const& с сохранением копирование и динамическое выделение памяти). - person SirGuy; 05.09.2013
comment
@GuyGreer: если бы это был я, я бы преобразовал keys один раз с tolower, но я не знаю, был ли компаратор OP просто примером и можно ли изменить ключи. - person Jarod42; 05.09.2013
comment
@jamesj: вычисление keys может быть дорогостоящим, поэтому его нельзя вычислять каждый раз в компараторе (см. существующие примечания по оптимизации этой части, чтобы избежать копирования строк в предыдущих комментариях). - person Jarod42; 05.09.2013
comment
@ Jarod42 Jarod42 Достаточно честно, по какой-то причине я подумал, что у него std::map<std::string, Foo>, и хотел выводить в порядке без учета регистра. Просто позвонить std::for_each(keys.begin(), keys.end(), boost::to_lower) определенно было бы проще. - person SirGuy; 05.09.2013
comment
Не меньше кода, но в качестве альтернативы этому первому циклу for C++11 предоставляет iota для заполнения индексов: std::vector<size_t> indexes(keys.size()); std::iota(indexes.begin(), indexes.end(), 0); - person Jonathan Lidbeck; 26.12.2019

for(;i!=end;++end)

вы должны увеличить свой я не ваш конец!

person retinotop    schedule 05.09.2013
comment
Извините, я ошибся при копировании кода. Я исправил это в своем вопросе. - person Baz; 05.09.2013
comment
@Baz Как вы делаете такую ​​​​ошибку при копировании? - person BЈовић; 05.09.2013
comment
@BЈовић Ну, мне пришлось заменить свой код на Foo, поэтому я не скопировал точно :) - person Baz; 05.09.2013

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

class Foo
{
  public :
    Foo(const std::string & key) : key(key) {}
    const std::string & get_key() const { return key; }
  private :
    std::string key;
};

std::ostream & operator<<(std::ostream & stream, const Foo & foo) { stream << foo.get_key(); return stream; }

class SortedFoo
{
  typedef std::set<std::pair<std::string,Foo*> > SortedFoos;
  SortedFoos mFoos;

public :
  SortedFoo(std::vector<Foo> & foos)
  {
    const std::vector<Foo>::iterator end = foos.end();
    for(std::vector<Foo>::iterator iter = foos.begin(); iter != end; ++iter)
    {
      mFoos.insert(std::make_pair(boost::algorithm::to_lower_copy(iter->get_key()), &(*iter)));
    }
  }

  class Iterator : public std::iterator<std::forward_iterator_tag, Foo>
  {
    private:
      Iterator(SortedFoos::iterator iter) : mIter(iter) {}
      SortedFoos::iterator mIter;

    public :
      Iterator & operator ++ () { ++mIter; return *this; }
      bool operator != (const Iterator & other) const { return mIter != other.mIter; }
      Foo & operator * () { return *mIter->second; }
      Foo * operator -> () { return mIter->second; }

      friend class SortedFoo;
  };

  typedef Iterator iterator;

  iterator begin() { return Iterator(mFoos.begin()); }
  iterator end() { return Iterator(mFoos.end()); }
};

int main(int argc, const char** argv)
{
  std::vector<Foo> foos;
  foos.push_back(Foo("def"));
  foos.push_back(Foo("Jkl"));
  foos.push_back(Foo("yz "));
  foos.push_back(Foo("pqr"));
  foos.push_back(Foo("Mno"));
  foos.push_back(Foo("ghi"));
  foos.push_back(Foo("vwx"));
  foos.push_back(Foo("Abc"));
  foos.push_back(Foo("stu"));

  SortedFoo sorted(foos);
  std::copy(sorted.begin(), sorted.end(), std::ostream_iterator<Foo>(std::cout, " "));

  return 0;
}

Если у вас есть повторяющиеся ключи, вы не можете использовать набор. Вы можете заменить его вектором с небольшими изменениями:

typedef std::vector<std::pair<std::string,Foo*> > SortedFoos;
//...
SortedFoo(std::vector<Foo> & foos)
{
  const std::vector<Foo>::iterator end = foos.end();
  for(std::vector<Foo>::iterator iter = foos.begin(); iter != end; ++iter)
  {
    mFoos.push_back(std::make_pair(boost::algorithm::to_lower_copy(iter->get_key()), &(*iter)));
  }
  std::sort(mFoos.begin(), mFoos.end());
}
//...
person Jyhess    schedule 05.09.2013