【问题标题】:Iterate over a std::vector in sorted order [closed]按排序顺序迭代 std::vector [关闭]
【发布时间】:2025-12-31 09:05:13
【问题描述】:

我从 API 收到 Foo 的向量,如下所示:

std::vector<Foo> foos;

然后我写了一个函数叫

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

遍历容器并为每个 Foo 对象提取 std::string 类型的键。

您将如何按排序顺序遍历 foos 中的 Foo 对象,其中排序是在键上完成的,并且以不区分大小写的方式进行。此外,我不想制作 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);
}

【问题讨论】:

  • 您的尝试有什么问题?
  • @jamesj 它有效,但我想知道它是否可以做得更好或是否可以改进。
  • 请发布您使用的代码以及您期望/希望发生的事情。在您发布的代码中,您在声明之前访问sortedFoos,增加end 而不是i,这绝对不是您尝试的代码(部分)
  • 密钥与Foos 有什么关系?
  • @Pieter 我现在已经更正了上面代码中的编译错误,但我的问题是从设计的角度来看的。上面的代码给出了我将如何解决这个问题的想法。

标签: c++ sorting loops c++98


【解决方案1】:

除了您的尝试之外, 你可以创建一个索引数组

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));

现在您可以使用索引间接迭代 foo 和/或键:

std::vector<Foo*> sortedFoos;
for (size_t i = 0; i != indexes.size(); ++i) {
    sortedFoos.push_back(&foos[indexes[i]]);
}

【讨论】:

  • +1 用于反转比较逻辑
  • 您仍然需要额外的时间来遍历集合以获取指针。为什么不首先对指针进行排序呢?
  • 不要为每次比较制作字符串的本地副本(使它们小写),不如编写一个char比较器,在比较之前调用std::tolower,然后调用std::lexicographical_compare这两个字符串(现在可以在本地存储为 const&amp; 保存复制和动态内存分配)。
  • @GuyGreer:如果是我,我会将keys 转换为tolower 一次,但我不知道 OP 的比较器是否只是一个示例,是否可以修改键。
  • @jamesj:keys 的计算可能很昂贵,所以不能在比较器中每次都计算(参见现有注释优化这部分以避免之前的 cmets 中的字符串复制)。
【解决方案2】:

您关心当前迭代 foos 三次并排序一次。这将使您的算法在大型数组上的性能降低。为什么不将其更改为执行以下操作

  1. 对其进行迭代以将指针提取到名为 fooPtrVec 的 std::vecotr&lt;Foo*&gt;
  2. 更改比较函数以取消引用 Foo* 并使用 Foo 上的关键字段进行比较。调用函数 YourNewComparisonFunction
  3. 使用std::sort(fooPtrVec.begin(), fooPtrVec.end(), YourNewComparisonFunction())对Foo*的向量进行排序

【讨论】:

  • 听起来更好的方法!
  • 与其他实现相比,内存更少、CPU 周期更少、代码更少……有什么不喜欢的? ;)
  • 这取决于如何计算密钥...
  • @Jarod42 你是对的。我假设密钥只是 Foo 对象上的一个字段。如果经过计算,您的答案更有意义(+1 给您!)
【解决方案3】:

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

你必须增加你的 i 而不是你的目的!

【讨论】:

  • 对不起,我复制代码时出错了。我已经在我的问题中更正了这一点。
  • @Baz 复制的时候怎么会出现这样的错误?
  • @BЈовић 好吧,我不得不用 Foo 替换我的代码,所以我没有完全复制:)
【解决方案4】:

您可以使用集合为您排序键,并将它们封装在自定义容器中以更方便地使用:

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());
}
//...

【讨论】: