使用标准算法库实现二元关系的唯一等价
Using std algorithm library for unique equivalence with respect to binary relation
我在某些类型 T
上有二元关系,由函数 equivalent
:
诱导
bool equivalent(T const& a, T const& b); // returns true if a and b are equivalent
它具有
的属性
equivalent(a, a) == true
和
equivalent(a, b) == equivalent(b, a)
所有a
,b
。
对于类型为 T
的给定元素集合,我想删除除每个等价项 class 第一次出现以外的所有元素。我想出了以下代码,但一直在徘徊:
有没有没有显式循环的解决方案?
std::vector<T> filter_all_but_one_for_each_set_of_equivalent_T(std::vector<T> const& ts) {
std::vector<T> result;
for (auto iter = ts.begin(); iter != ts.end(); ++iter) {
auto const& elem = *iter;
bool has_equivalent_element_at_earlier_position = std::any_of(
ts.begin(),
iter,
&equivalent
);
if (not has_equivalent_element_at_earlier_position) {
result.push_back(routing_pin);
}
}
return result;
}
更新
据我所知,std::unique
不行,因为我的类型 T
无法排序。因为我只有 C++11,但我也会对其他教育选项感兴趣。
struct S {
int eq;
int value;
bool operator==(const S& other) const { return eq == other.eq; }
};
namespace std {
template <> struct hash<S>
{
size_t operator()(const S &s) const
{
return hash<int>()(s.eq);
}
};
}
array<S, 6> as{ { {1,0},{2,0},{3,0},{ 1,1 },{ 2,1 },{ 3,1 } } };
unordered_set<S> us(as.cbegin(), as.cend());
先想出另一个loop版本,对比你自己的,统一就地,你可能会觉得有趣:
std::vector<int> v({1, 7, 1, 8, 9, 8, 9, 1, 1, 7});
auto retained = v.begin();
for(auto i = v.begin(); i != v.end(); ++i)
{
bool isFirst = true;
for(auto j = v.begin(); j != retained; ++j)
{
if(*i == *j)
{
isFirst = false;
break;
}
}
if(isFirst)
{
*retained++ = *i;
}
}
v.erase(retained, v.end());
这是使用 std::remove_if
和 std::find_if
的版本的基础:
auto retained = v.begin();
auto c = [&v, &retained](int n)
{
if(std::find_if(v.begin(), retained, [n](int m) { return m == n; }) != retained)
return true;
// element remains, so we need to increase!!!
++retained;
return false;
};
v.erase(std::remove_if(v.begin(), v.end(), c), v.end());
在这种情况下你需要 lambda,因为我们需要一个 unique-predicate,而等效的(在我的 int 示例中由 operator==
表示)是一个二进制的...
扩展我在 AndyG 的回答中的评论:
template<class T, class A, class Equivalent>
auto deduplicated2(std::vector<T, A> vec, Equivalent&& equivalent) -> std::vector<T, A>
{
auto current = std::begin(vec);
// current 'last of retained sequence'
auto last = std::end(vec);
while (current != last)
{
// define a predicate which checks for equivalence to current
auto same = [&](T const& x) -> bool
{
return equivalent(*current, x);
};
// move non-equivalent items to end of sequence
// return new 'end of valid sequence'
last = std::remove_if(std::next(current), last, same);
}
// erase all items beyond the 'end of valid sequence'
vec.erase(last, std::end(vec));
return vec;
}
感谢 AndyG。
对于 T 可散列的非常大的向量,我们可以寻求 O(n) 的解决方案:
template<class T, class A, class Equivalent>
auto deduplicated(std::vector<T, A> const& vec, Equivalent&& equivalent) -> std::vector<T, A>
{
auto seen = std::unordered_set<T, std::hash<T>, Equivalent>(vec.size(), std::hash<T>(), std::forward<Equivalent>(equivalent));
auto result = std::vector<T, A>();
result.resize(vec.size());
auto current = std::begin(vec);
while (current != std::end(vec))
{
if (seen.insert(*current).second)
{
result.push_back(*current);
}
}
return result;
}
最后,重温第一个方案,重构为sub-concerns(忍不住):
// in-place de-duplication of sequence, similar interface to remove_if
template<class Iter, class Equivalent>
Iter inplace_deduplicate_sequence(Iter first, Iter last, Equivalent&& equivalent)
{
while (first != last)
{
// define a predicate which checks for equivalence to current
using value_type = typename std::iterator_traits<Iter>::value_type;
auto same = [&](value_type const& x) -> bool
{
return equivalent(*first, x);
};
// move non-equivalent items to end of sequence
// return new 'end of valid sequence'
last = std::remove_if(std::next(first), last, same);
}
return last;
}
// in-place de-duplication on while vector, including container truncation
template<class T, class A, class Equivalent>
void inplace_deduplicate(std::vector<T, A>& vec, Equivalent&& equivalent)
{
vec.erase(inplace_deduplicate_sequence(vec.begin(),
vec.end(),
std::forward<Equivalent>(equivalent)),
vec.end());
}
// non-destructive version
template<class T, class A, class Equivalent>
auto deduplicated2(std::vector<T, A> vec, Equivalent&& equivalent) -> std::vector<T, A>
{
inplace_deduplicate(vec, std::forward<Equivalent>(equivalent));
return vec;
}
这是一种只有一个非常简单的循环的方法:
首先定义我们的 class,我将其称为 A
而不是 T
因为 T
通常用于模板:
class A{
public:
explicit A(int _i) : i(_i){};
int get() const{return i;}
private:
int i;
};
然后我们的 equivalent
函数只是比较整数是否相等:
bool equivalent(A const& a, A const& b){return a.get() == b.get();}
接下来定义过滤函数。
这里的想法是利用 std::remove
为我们高效地进行循环和擦除(它通常将元素交换到末尾,这样您就不会为每次删除移动向量)。
我们首先删除与第一个元素匹配的所有内容,然后删除与第二个元素匹配的所有内容(现在保证 != 到第一个元素),依此类推。
std::vector<A> filter_all_but_one_for_each_set_of_equivalent_A(std::vector<A> as) {
for(size_t i = 1; i < as.size(); ++i){
as.erase(std::remove_if(as.begin() + i, as.end(), [&as, i](const A& next){return equivalent(as[i-1], next);}), as.end());
}
return as;
}
Demo
编辑:正如 Richard Hodges 提到的,可以将任何擦除延迟到最后。虽然我无法让它看起来那么漂亮:
std::vector<A> filter_all_but_one_for_each_set_of_equivalent_A(std::vector<A> as) {
auto end = as.end();
for(size_t i = 1; i < std::distance(as.begin(), end); ++i){
end = std::remove_if(as.begin() + i, end, [&as, i](const A& next){return equivalent(as[i-1], next);});
}
as.erase(end, as.end());
return as;
}
你可以试试这个。这里的技巧是在谓词内部获取索引。
std::vector<T> output;
std::copy_if(
input.begin(), input.end(),
std::back_inserter(output),
[&](const T& x) {
size_t index = &x - &input[0];
return find_if(
input.begin(), input.begin() + index, x,
[&x](const T& y) {
return equivalent(x, y);
}) == input.begin() + index;
});
由于性能不是问题,您可以使用 std::accumulate
扫描元素并将它们添加到累加器向量 xs
(如果还没有)
xs
.
中的等价元素
有了这个,你根本不需要任何 hand-written 原始循环。
std::vector<A> filter_all_but_one_for_each_set_of_equivalent_A(std::vector<A> as) {
return std::accumulate(as.begin(), as.end(),
std::vector<A>{}, [](std::vector<A> xs, A const& x) {
if ( std::find_if(xs.begin(), xs.end(), [x](A const& y) {return equivalent(x,y);}) == xs.end() ) {
xs.push_back(x);
}
return xs;
});
}
有了两个辅助函数,这实际上变得可读了:
bool contains_equivalent(std::vector<A> const& xs, A const& x) {
return std::find_if(xs.begin(), xs.end(),
[x](A const& y) {return equivalent(x,y);}) != xs.end();
};
std::vector<A> push_back_if(std::vector<A> xs, A const& x) {
if ( !contains_equivalent(xs, x) ) {
xs.push_back(x);
}
return xs;
};
函数本身只是对std::accumulate
的调用:
std::vector<A> filter_all_but_one_for_each_set_of_equivalent_A(std::vector<A> as) {
return std::accumulate(as.begin(), as.end(), std::vector<A>{}, push_back_if);
}
I've modified AndyG's example code with my proposed function.
如上定义,std::accumulate
调用push_back_if
并复制累加器变量,return值再次move-assigned到累加器。这是非常低效的,但是可以通过改变push_back_if
取一个引用来优化向量从而修改in-place。需要将初始值作为引用包装器传递给 std::ref
以消除剩余的副本。
std::vector<A>& push_back_if(std::vector<A>& xs, A const& x) {
if ( !contains_equivalent(xs, x) ) {
xs.push_back(x);
}
return xs;
};
std::vector<A> filter_all_but_one_for_each_set_of_equivalent_A(std::vector<A> const& as) {
std::vector<A> acc;
return std::accumulate(as.begin(), as.end(), std::ref(acc), push_back_if);
}
You can see in the example that the copy-constructor is almost completely eliminated.
我在某些类型 T
上有二元关系,由函数 equivalent
:
bool equivalent(T const& a, T const& b); // returns true if a and b are equivalent
它具有
的属性equivalent(a, a) == true
和
equivalent(a, b) == equivalent(b, a)
所有a
,b
。
对于类型为 T
的给定元素集合,我想删除除每个等价项 class 第一次出现以外的所有元素。我想出了以下代码,但一直在徘徊:
有没有没有显式循环的解决方案?
std::vector<T> filter_all_but_one_for_each_set_of_equivalent_T(std::vector<T> const& ts) {
std::vector<T> result;
for (auto iter = ts.begin(); iter != ts.end(); ++iter) {
auto const& elem = *iter;
bool has_equivalent_element_at_earlier_position = std::any_of(
ts.begin(),
iter,
&equivalent
);
if (not has_equivalent_element_at_earlier_position) {
result.push_back(routing_pin);
}
}
return result;
}
更新
据我所知,std::unique
不行,因为我的类型 T
无法排序。因为我只有 C++11,但我也会对其他教育选项感兴趣。
struct S {
int eq;
int value;
bool operator==(const S& other) const { return eq == other.eq; }
};
namespace std {
template <> struct hash<S>
{
size_t operator()(const S &s) const
{
return hash<int>()(s.eq);
}
};
}
array<S, 6> as{ { {1,0},{2,0},{3,0},{ 1,1 },{ 2,1 },{ 3,1 } } };
unordered_set<S> us(as.cbegin(), as.cend());
先想出另一个loop版本,对比你自己的,统一就地,你可能会觉得有趣:
std::vector<int> v({1, 7, 1, 8, 9, 8, 9, 1, 1, 7});
auto retained = v.begin();
for(auto i = v.begin(); i != v.end(); ++i)
{
bool isFirst = true;
for(auto j = v.begin(); j != retained; ++j)
{
if(*i == *j)
{
isFirst = false;
break;
}
}
if(isFirst)
{
*retained++ = *i;
}
}
v.erase(retained, v.end());
这是使用 std::remove_if
和 std::find_if
的版本的基础:
auto retained = v.begin();
auto c = [&v, &retained](int n)
{
if(std::find_if(v.begin(), retained, [n](int m) { return m == n; }) != retained)
return true;
// element remains, so we need to increase!!!
++retained;
return false;
};
v.erase(std::remove_if(v.begin(), v.end(), c), v.end());
在这种情况下你需要 lambda,因为我们需要一个 unique-predicate,而等效的(在我的 int 示例中由 operator==
表示)是一个二进制的...
扩展我在 AndyG 的回答中的评论:
template<class T, class A, class Equivalent>
auto deduplicated2(std::vector<T, A> vec, Equivalent&& equivalent) -> std::vector<T, A>
{
auto current = std::begin(vec);
// current 'last of retained sequence'
auto last = std::end(vec);
while (current != last)
{
// define a predicate which checks for equivalence to current
auto same = [&](T const& x) -> bool
{
return equivalent(*current, x);
};
// move non-equivalent items to end of sequence
// return new 'end of valid sequence'
last = std::remove_if(std::next(current), last, same);
}
// erase all items beyond the 'end of valid sequence'
vec.erase(last, std::end(vec));
return vec;
}
感谢 AndyG。
对于 T 可散列的非常大的向量,我们可以寻求 O(n) 的解决方案:
template<class T, class A, class Equivalent>
auto deduplicated(std::vector<T, A> const& vec, Equivalent&& equivalent) -> std::vector<T, A>
{
auto seen = std::unordered_set<T, std::hash<T>, Equivalent>(vec.size(), std::hash<T>(), std::forward<Equivalent>(equivalent));
auto result = std::vector<T, A>();
result.resize(vec.size());
auto current = std::begin(vec);
while (current != std::end(vec))
{
if (seen.insert(*current).second)
{
result.push_back(*current);
}
}
return result;
}
最后,重温第一个方案,重构为sub-concerns(忍不住):
// in-place de-duplication of sequence, similar interface to remove_if
template<class Iter, class Equivalent>
Iter inplace_deduplicate_sequence(Iter first, Iter last, Equivalent&& equivalent)
{
while (first != last)
{
// define a predicate which checks for equivalence to current
using value_type = typename std::iterator_traits<Iter>::value_type;
auto same = [&](value_type const& x) -> bool
{
return equivalent(*first, x);
};
// move non-equivalent items to end of sequence
// return new 'end of valid sequence'
last = std::remove_if(std::next(first), last, same);
}
return last;
}
// in-place de-duplication on while vector, including container truncation
template<class T, class A, class Equivalent>
void inplace_deduplicate(std::vector<T, A>& vec, Equivalent&& equivalent)
{
vec.erase(inplace_deduplicate_sequence(vec.begin(),
vec.end(),
std::forward<Equivalent>(equivalent)),
vec.end());
}
// non-destructive version
template<class T, class A, class Equivalent>
auto deduplicated2(std::vector<T, A> vec, Equivalent&& equivalent) -> std::vector<T, A>
{
inplace_deduplicate(vec, std::forward<Equivalent>(equivalent));
return vec;
}
这是一种只有一个非常简单的循环的方法:
首先定义我们的 class,我将其称为 A
而不是 T
因为 T
通常用于模板:
class A{
public:
explicit A(int _i) : i(_i){};
int get() const{return i;}
private:
int i;
};
然后我们的 equivalent
函数只是比较整数是否相等:
bool equivalent(A const& a, A const& b){return a.get() == b.get();}
接下来定义过滤函数。
这里的想法是利用 std::remove
为我们高效地进行循环和擦除(它通常将元素交换到末尾,这样您就不会为每次删除移动向量)。
我们首先删除与第一个元素匹配的所有内容,然后删除与第二个元素匹配的所有内容(现在保证 != 到第一个元素),依此类推。
std::vector<A> filter_all_but_one_for_each_set_of_equivalent_A(std::vector<A> as) {
for(size_t i = 1; i < as.size(); ++i){
as.erase(std::remove_if(as.begin() + i, as.end(), [&as, i](const A& next){return equivalent(as[i-1], next);}), as.end());
}
return as;
}
Demo
编辑:正如 Richard Hodges 提到的,可以将任何擦除延迟到最后。虽然我无法让它看起来那么漂亮:
std::vector<A> filter_all_but_one_for_each_set_of_equivalent_A(std::vector<A> as) {
auto end = as.end();
for(size_t i = 1; i < std::distance(as.begin(), end); ++i){
end = std::remove_if(as.begin() + i, end, [&as, i](const A& next){return equivalent(as[i-1], next);});
}
as.erase(end, as.end());
return as;
}
你可以试试这个。这里的技巧是在谓词内部获取索引。
std::vector<T> output;
std::copy_if(
input.begin(), input.end(),
std::back_inserter(output),
[&](const T& x) {
size_t index = &x - &input[0];
return find_if(
input.begin(), input.begin() + index, x,
[&x](const T& y) {
return equivalent(x, y);
}) == input.begin() + index;
});
由于性能不是问题,您可以使用 std::accumulate
扫描元素并将它们添加到累加器向量 xs
(如果还没有)
xs
.
有了这个,你根本不需要任何 hand-written 原始循环。
std::vector<A> filter_all_but_one_for_each_set_of_equivalent_A(std::vector<A> as) {
return std::accumulate(as.begin(), as.end(),
std::vector<A>{}, [](std::vector<A> xs, A const& x) {
if ( std::find_if(xs.begin(), xs.end(), [x](A const& y) {return equivalent(x,y);}) == xs.end() ) {
xs.push_back(x);
}
return xs;
});
}
有了两个辅助函数,这实际上变得可读了:
bool contains_equivalent(std::vector<A> const& xs, A const& x) {
return std::find_if(xs.begin(), xs.end(),
[x](A const& y) {return equivalent(x,y);}) != xs.end();
};
std::vector<A> push_back_if(std::vector<A> xs, A const& x) {
if ( !contains_equivalent(xs, x) ) {
xs.push_back(x);
}
return xs;
};
函数本身只是对std::accumulate
的调用:
std::vector<A> filter_all_but_one_for_each_set_of_equivalent_A(std::vector<A> as) {
return std::accumulate(as.begin(), as.end(), std::vector<A>{}, push_back_if);
}
I've modified AndyG's example code with my proposed function.
如上定义,std::accumulate
调用push_back_if
并复制累加器变量,return值再次move-assigned到累加器。这是非常低效的,但是可以通过改变push_back_if
取一个引用来优化向量从而修改in-place。需要将初始值作为引用包装器传递给 std::ref
以消除剩余的副本。
std::vector<A>& push_back_if(std::vector<A>& xs, A const& x) {
if ( !contains_equivalent(xs, x) ) {
xs.push_back(x);
}
return xs;
};
std::vector<A> filter_all_but_one_for_each_set_of_equivalent_A(std::vector<A> const& as) {
std::vector<A> acc;
return std::accumulate(as.begin(), as.end(), std::ref(acc), push_back_if);
}
You can see in the example that the copy-constructor is almost completely eliminated.