简体   繁体   中英

How to sort a range based on the value of some transformation of its values?

Let arr be an array of some type T and func(const &T) some computationally expensive function. To sort arr by the value of func(T) , we could naively write.

std::sort(arr.begin(), arr.end(), [](const T& a, const T&b) {
    return func(a) < func(b);
}

However this will invoke func O(nlogn) times on average. Obviously, we need no more than exactly n invocations. Is there any idiomatic and concise way to do this?

I am aware of the following two solutions but I would like to find one that is better.

  • Packing the T s and the return value of func in a struct and sorting that. This is optimal computationally but it also does one full copy of the initial array before sorting and another full copy afterwards to put the values back into arr

  • Creating a second array and sorting the arrays in parallel. As far as I know there is no idiomatic way to sort two arrays that way. It could be done by writing custom iterators and a swap function. That's good enough but it also requires a bit more boilerplate code than ideal.

The ideal solution would be STL-based with as little boilerplate code as possible, but all contributions are welcome.

First non-generic solution

What you need is kind of memoization of function result. If your function fun does not have side effects and from using it in std::sort I infer it does not I would think of something like this:

#include <unordered_map>

template<class T, class U>
class caller{
public:
    const U &call(const T& t) const{
        auto found = memoized.find(t);
        if (found != memoized.end())
            return found->second;

        return memoized.insert(std::make_pair(t, fun(t))).first->second;
    }
private:
    mutable std::unordered_map<T, U> memoized;
};

Its usage would look like this:

caller<T, decltype(func(std::declval<T>()))> c;
std::sort(arr.begin(), arr.end(), [](const T& a, const T&b) {
    return c.call(a) < c.call(b);
}

More generic solution

After making first solution I played a bit and made a bit more generic, C++17 compliant one. It should work with every function taking one argument that is coypable and hashable. Take a look:

#include <unordered_map>

int fii(int){
    return 0;
}

template<class T, auto fun>
class memoizer{
public:
    const auto &call(const T& t) const{
        auto found = memoized.find(t);
        if (found != memoized.end())
            return found->second;

        return memoized.insert(std::make_pair(t,  fun(t)))
            .first->second;
    }
private:
    mutable std::unordered_map<T, decltype(fun(T()))> memoized;
};

auto memoized_fii = memoizer<int, fii>{};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM