Sort by proxy (or: sort one container by the contents of another) in C++

前端 未结 7 894
失恋的感觉
失恋的感觉 2020-12-19 02:34

I have a set of data which is split into two arrays (let\'s call them data and keys). That is, for any given item with an index i, I c

7条回答
  •  别那么骄傲
    2020-12-19 03:18

    This problem really got me thinking. I came up with a solution that makes use of some C++0x features to get a very STL-like parallel_sort algorithm. In order to perform the sort "in-place", I had to write a back_remove_iterator as the counterpart of back_insert_iterator to allow the algorithm to read from and write to the same container. You can skip over those parts and go straight to the interesting stuff.

    I haven't put it through any hardcore testing, but it seems reasonably efficient in both time and space, principally due to the use of std::move() to prevent unnecessary copying.

    #include 
    #include 
    #include 
    #include 
    
    
    //
    // An input iterator that removes elements from the back of a container.
    // Provided only because the standard library neglects one.
    //
    template
    class back_remove_iterator :
        public std::iterator {
    public:
    
    
        back_remove_iterator() : container(0) {}
        explicit back_remove_iterator(Container& c) : container(&c) {}
    
        back_remove_iterator& operator=
            (typename Container::const_reference value) { return *this; }
    
        typename Container::value_type operator*() {
    
            typename Container::value_type value(container->back());
            container->pop_back();
            return value;
    
        } // operator*()
    
        back_remove_iterator& operator++() { return *this; }
        back_remove_iterator operator++(int) { return *this; }
    
    
        Container* container;
    
    
    }; // class back_remove_iterator
    
    
    //
    // Equivalence operator for back_remove_iterator. An iterator compares equal
    // to the end iterator either if it is default-constructed or if its
    // container is empty.
    //
    template
    bool operator==(const back_remove_iterator& a,
        const back_remove_iterator& b) {
    
        return !a.container ? !b.container || b.container->empty() :
            !b.container ? !a.container || a.container->empty() :
            a.container == b.container;
    
    } // operator==()
    
    
    //
    // Inequivalence operator for back_remove_iterator.
    //
    template
    bool operator!=(const back_remove_iterator& a,
        const back_remove_iterator& b) {
    
        return !(a == b);
    
    } // operator!=()
    
    
    //
    // A handy way to default-construct a back_remove_iterator.
    //
    template
    back_remove_iterator back_remover() {
    
        return back_remove_iterator();
    
    } // back_remover()
    
    
    //
    // A handy way to construct a back_remove_iterator.
    //
    template
    back_remove_iterator back_remover(Container& c) {
    
        return back_remove_iterator(c);
    
    } // back_remover()
    
    
    //
    // A comparison functor that sorts std::pairs by their first element.
    //
    template
    struct sort_pair_by_first {
    
        bool operator()(const std::pair& a, const std::pair& b) {
    
            return a.first < b.first;
    
        } // operator()()
    
    }; // struct sort_pair_by_first
    
    
    //
    // Performs a parallel sort of the ranges [keys_first, keys_last) and
    // [values_first, values_last), preserving the ordering relation between
    // values and keys. Sends key and value output to keys_out and values_out.
    //
    // This works by building a vector of std::pairs, sorting them by the key
    // element, then returning the sorted pairs as two separate sequences. Note
    // the use of std::move() for a vast performance improvement.
    //
    template
    void parallel_sort(I keys_first, I keys_last, J values_first, J values_last,
                       K keys_out, L values_out) {
    
        typedef std::vector< std::pair > Pairs;
        Pairs sorted;
    
        while (keys_first != keys_last)
            sorted.push_back({std::move(*keys_first++), std::move(*values_first++)});
    
        std::sort(sorted.begin(), sorted.end(), sort_pair_by_first());
    
        for (auto i = sorted.begin(); i != sorted.end(); ++i)
            *keys_out++ = std::move(i->first),
            *values_out++ = std::move(i->second);
    
    } // parallel_sort()
    
    
    int main(int argc, char** argv) {
    
        //
        // There is an ordering relation between keys and values,
        // but the sets still need to be sorted. Sounds like a job for...
        //
        std::vector keys{0, 3, 1, 2};
        std::vector values{"zero", "three", "one", "two"};
    
        //
        // parallel_sort! Unfortunately, the key and value types do need to
        // be specified explicitly. This could be helped with a utility
        // function that accepts back_remove_iterators.
        //
        parallel_sort
            (back_remover(keys), back_remover>(),
            back_remover(values), back_remover>(),
            std::back_inserter(keys), std::back_inserter(values));
    
        //
        // Just to prove that the mapping is preserved.
        //
        for (unsigned int i = 0; i < keys.size(); ++i)
            std::cout << keys[i] << ": " << values[i] << '\n';
    
        return 0;
    
    } // main()
    

    I hope this proves useful, or at least entertaining.

提交回复
热议问题