根据其他列表订购列表

时间:2017-03-10 13:24:32

标签: c++ stl

假设:

struct Object {
    int id;
    ...
};

list<Object> objectList;
list<int> idList;

根据objectList的顺序订购idList的最佳方法是什么?

示例(伪代码):

INPUT
    objectList = {o1, o2, o3};
    idList = {2, 3, 1};

ACTION
    sort(objectList, idList);

OUTPUT
    objectList = {o2, o3, o1};

我在文档中进行了搜索,但我只找到了对元素进行比较的方法。

6 个答案:

答案 0 :(得分:2)

您可以将对象存储在std::map中,并以id为键。然后遍历idList,使用mapid中获取对象。

std::map<int, Object> objectMap;
for (auto itr = objectList.begin(); itr != objectList.end(); itr++)
{
    objectMap.insert(std::make_pair(itr->id, *itr));
}

std::list<Object> newObjectList;
for (auto itr = idList.begin(); itr != idList.end(); itr++)
{
    // here may fail if your idList contains ids which does not appear in objectList
    newObjectList.push_back(objectMap[*itr]);
}

// now newObjectList is sorted as order in idList

答案 1 :(得分:2)

这是另一种变体,适用于O(n log n)。这是渐近最优的。

#include <list>
#include <vector>
#include <algorithm>
#include <iostream>
#include <cassert>

int main() {
  struct O {
    int id;
  };
  std::list<O> object_list{{1}, {2}, {3}, {4}};
  std::list<int> index_list{4, 2, 3, 1};
  assert(object_list.size() == index_list.size());

  // this vector is optional. It is needed if sizeof(O) is quite large.
  std::vector<std::pair<int, O*>> tmp_vector(object_list.size());
  // this is O(n)
  std::transform(begin(object_list), end(object_list), begin(tmp_vector),
                 [](auto& o) { return std::make_pair(o.id, &o); });
  // this is O(n log n)
  std::sort(begin(tmp_vector), end(tmp_vector), 
            [](const auto& o1, const auto& o2) {
            return o1.first < o2.first;
            });
  // at this point, tmp_vector holds pairs in increasing index order. 
  // Note that this may not be a contiguous list.

  std::list<O> tmp_list(object_list.size());
  // this is again O (n log n), because lower_bound is O (n)
  // we then insert the objects into a new list (you may also use some 
  // move semantics here).
  std::transform(begin(index_list), end(index_list), begin(tmp_list),
                 [&tmp_vector](const auto& i) {
                   return *std::lower_bound(begin(tmp_vector), end(tmp_vector),
                                            std::make_pair(i, nullptr),
                                            [](const auto& o1, const auto& o2) {
                                              return o1.first < o2.first;
                                            })->second;
                 });

  // As we just created a new list, we swap the new list with the old one.
  std::swap(object_list, tmp_list);

  for (const auto& o : object_list)
    std::cout << o.id << std::endl;

}

我认为O非常大而且不易移动。因此,我首先创建仅包含对的tmp_vector。然后我对这个向量进行排序。

之后我可以简单地浏览index_list并使用二分查找找到匹配的索引。

让我详细说明为什么地图不是最好的解决方案,尽管你得到的是一小段代码。如果使用地图,则需要在每次插入后重新平衡树。这不会花费太多的代价(因为n次重新平衡会使您与排序一次相同),但常数会更大。 A&#34;常量地图&#34;没有多大意义(除了访问它可能更容易)。

然后,我将&#34;简单&#34;地图方法反对我的&#34;不那么简单&#34;向量的方法。我创建了一个随机排序的index_list条目N。这就是我得到的(在我们身上):

N         map         vector
1000      90          75
10000     1400        940
100000    24500       15000
1000000   660000      250000   

注意:此测试显示最坏的情况,因为在我的情况下,只有index_list被随机排序,而object_list(按顺序插入到地图中)被排序。所以重新平衡显示了它的所有效果。如果object_list是随机的,性能将表现得更相似,尽管性能总是更差。当对象列表完全随机时,向量列表甚至会表现得更好。

因此已经有1000个条目,差异已经非常大了。因此,我强烈投票支持基于矢量的方法。

答案 2 :(得分:1)

假设从外部处理数据并且您无法选择容器:

assert( objectList.size() == idList.size() );
std::vector<std::pair<int,Object>> wrapper( idList.size() );
auto idList_it     = std::begin( idList );
auto objectList_it = std::begin( objectList );
for( auto& e: wrapper )
    e = std::make_pair( *idList_it++, *objectList_it++ );
std::sort(
    std::begin(wrapper),
    std::end(wrapper),
    []
    (const std::pair<int,Object>& a, const std::pair<int,Object>& b) -> bool
    { return a.first<b.first; }
);

然后,复制回原始容器。

{
   auto objectList_it = std::begin( objectList );
   for( const auto& e: wrapper )
      *objectList_it++ = e;
}

但是这个解决方案并不是最优的,我确信有人会提供更好的解决方案。

编辑:对的默认比较运算符要求为第一个和第二个成员定义它。因此,最简单的方法是提供一个lambda。

Edit2 :出于某种原因,如果使用std::list作为包装器,则不会构建。但是,如果您使用std::vectorsee here)。

,则可以

答案 3 :(得分:0)

std::list有一个sort成员函数,您可以将其与自定义比较函数一起使用。

该自定义仿函数必须在id中查找对象的idList,然后可以使用std::distance来计算idList中元素的位置。它对两个要比较的对象都这样做,如果第一个位置小于第二个位置,则返回true。

以下是一个例子:

#include <iostream>
#include <list>
#include <algorithm>
#include <stdexcept>

struct Object
{
    int id;
};

int main()
{
    Object o1 = { 1 };
    Object o2 = { 2 };
    Object o3 = { 3 };
    std::list<Object> objectList = { o1, o2, o3 };
    std::list<int> const idList = { 2, 3, 1 };

    objectList.sort([&](Object const& first, Object const& second)
    {
        auto const id_find_iter1 = std::find(begin(idList), end(idList), first.id);
        auto const id_find_iter2 = std::find(begin(idList), end(idList), second.id);
        if (id_find_iter1 == end(idList) || id_find_iter2 == end(idList))
        {
            throw std::runtime_error("ID not found");
        }
        auto const pos1 = std::distance(begin(idList), id_find_iter1);
        auto const pos2 = std::distance(begin(idList), id_find_iter2);
        return pos1 < pos2;
    });

    for (auto const& object : objectList)
    {
        std::cout << object.id << '\n';
    }
}

它可能效率不高,但你可能永远不会注意到它。如果它仍然困扰您,您可能希望寻找具有std::vector的解决方案,这与std::list不同,提供随机访问迭代器。这将std::distance从O(n)变为O(1)。

答案 4 :(得分:0)

我觉得最终会遇到这种情况很奇怪,因为我会使用指针代替ID。虽然;可能会有用例。

请注意,在下面的所有示例中,我假设ids-list包含所有id。

自己写作

您要解决的问题是根据另一个列表中ID的顺序创建/排序对象列表。

这种天真的做法,就是自己写一下:

<div>

            <ul data-bind="foreach: shops">
                <li>
                    <span data-bind="text: $data.title, click: $root.shopMarker"></span>
                </li>
            </ul>

        </div>

如果使用排序向量作为输入,则可以优化此代码以获得最佳性能。我把它留给你去做。

使用sort

对于这个实现,我假设这是std :: vector而不是std :: list,因为这是请求元素索引的更好容器。 (您可以使用更多代码对列表执行相同操作)

void sortByIdVector(std::list<Object> &list, const std::list<int> &ids)
{
    auto oldList = std::move(list);
    list = std::list<Object>{};
    for (auto id : ids)
    {
        auto itElement = std::find_if(oldList.begin(), oldList.end(), [id](const Object &obj) { return id == obj.id; });
        list.emplace_back(std::move(*itElement));
        oldList.erase(itElement);
    }
}

插入

另一种方法,也更适合std :: vector,只需在正确的位置插入元素,并且比std :: sort更高效。

size_t getIntendedIndex(const std::vector<int> &ids, const Object &obj)
{
    auto itElement = std::find_if(ids.begin(), ids.end(), [obj](int id) { return id == obj.id; });
    return itElement - ids.begin();
}

void sortByIdVector(std::list<Object> &list, const std::vector<int> &ids)
{
     list.sort([&ids](const Object &lhs, const Object &rhs){ return getIntendedIndex(ids, lhs) < getIntendedIndex(ids, rhs); });
}

答案 5 :(得分:0)

<input id="idname" data-bind="value: name, css: name.isDirty() == true ? 'changed' : 'notchanged'" />

我们的想法是检查objectList.sort([&idList] (const Object& o1, const Object& o2) -> bool { return std::find(++std::find(idList.begin(), idList.end(), o1.id), idList.end(), o2.id) != idList.end(); }); o1.id之前是否找到o2.id。 我们搜索idList,增加找到的位置,然后搜索o1.id:如果找到,则表示o1&lt; O 2。

<强>测试

o2.id