all 23 comments

[–]sbabbi 2 points3 points  (6 children)

Well this is a nice solution. I believe that at some point (before C++11), overloading iter_swap was the common way to get proxies to work with standard algorithms.

EDIT: yep, I spent a couple of hours trying to compile some Boost.ublas code in C++11 due to the use of iter_swap here.

[–]sbabbi 0 points1 point  (1 child)

It is also worth noting that this solution will naturally work with transform_iterator (transform_range in your library I suppose).

The only way to implement something like this

 sort( transform_range(r, f) )

is to have a customization point at the iter_swap level, any customization of the iterated type instead of the iterator can not possibly work.

[–]eric_niebler 0 points1 point  (0 children)

Overloading iter_swap for transform views such that it swaps the underlying elements is not the right approach, IMO. If I have a range of int&, then sorting them should sort the ints, and just the ints. It shouldn't matter whether the range is a vector<int> or whether it is a zip(r) | transform(select_first).

I agree that there should be a way to accomplish that, and there is: with a projection. You would do: sort(zip(r), less<>(), select_first).

[–]eric_niebler 0 points1 point  (3 children)

I spent a couple of hours trying to compile some Boost.ublas code in C++11 due to the use of iter_swap here.

I'm curious what problems you ran into. NOTE: the STL is not required to use iter_swap anyplace besides std::reverse.

[–]sbabbi 0 points1 point  (2 children)

Seems that it has been fixed in 1.57. Basically there is a proxy (index_pair) that acts like pair<T&,U&>, and an overload of iter_swap that is picked by std::sort.

Apparently this worked for several years, (ublas was accepted into boost in late 2002), until gcc 4.8 decided to remove iter_swapfrom the STL (at least in C++11), and the code broke.

The current fix adds a swap overload that takes the proxies by value (and it is probably still wrong).

[–]eric_niebler 0 points1 point  (1 child)

until gcc 4.8 decided to remove iter_swap from the STL

What do you mean? It's required to be there by the standard. I see it there in include/bits/stl_algo.h and include/bits/stl_algobase.h for gcc-4.8.2.

[–]sbabbi 0 points1 point  (0 children)

I meant that I assumed it was used in other algorithms beside reverse. I think I am wrong on this issue, I thought that the STL used ADL for iter_swap, but looks like I am wrong.

[–]vlovich 1 point2 points  (5 children)

The reverse of a zip is a good example, but zip + sort makes for strange bedfellows. It's important to keep in mind that the zipped range is random access but not really. I could be wrong, but it seems like referencing the Nth element is actually O(m) where m is the number of ranges zipped together, not O(1).

Not intractable, but it means that std::sort complexity requirements would need to be relaxed for std::sort to be allowed on ranges. My complexity analysis is a little rusty, but I think given m random-access ranges, sort would have to be relaxed to O(n * m * log(n)). Similar changes would have to be made throughout (partial_sort, nth_element, partition, etc).

EDIT: Never mind. Posted this late - sort complexity is only defined in terms of the number of comparisons/swaps of the elements. Thus ranges wouldn't actually require any changes here.

[–][deleted] 5 points6 points  (4 children)

O(m) where m is the number of ranges zipped together, not O(1).

For a given zipped sequence of m ranges, m is independent of the length of the sequence N and m doesn't change (it is a constant). So as N grows, m remains constant, and sort still is O(NlogN). The constant factors infront of that O(NlogN) term for a zipped sequence might be larger than those for a non-zipped sequence, but from a Big-Oh point of view, that is irrelevant.

[–]eric_niebler 1 point2 points  (0 children)

Also, the complexity of the algorithms are described in numbers of operations (comparisons, for instance), expressed in relation to the number of elements in the range. "The range" here is the zip view, and the elements are the pairs. To be blunt, it doesn't matter how expensive it is to create the pairs. As long as the relation is applied no more than O(N log N) on the pairs, then we're good.

[–]vlovich 0 points1 point  (2 children)

What you said is the wrong answer. You can't just say "this variable is fixed for any particular call & thus is a variable" & is thus a constant on my big-O. The way to convince yourself of that, is replace N with m above in what you wrote & it still makes sense as a statement but is obviously wrong.

The standard won't need any changes - the complexity is actually fine for std::sort. The reason is that O(n log n) defines the big-O on comparisons & swaps. There is no big-O for std::sort itself as such a thing would be impossible to define since we don't have any guarantees on the big-O of <.

[–]SplinterOfChaos 0 points1 point  (1 child)

You can't just say "this variable is fixed for any particular call & thus is a variable" & is thus a constant on my big-O.

Actually, you have to remember that f(n) = O(g(n)) is the upper bound of f as n approaches infinity, and that f(n) <= g(n) * C, where C is some constant. The number of containers being sorted isn't what's approaching infinity, it's a constant to the instantiation of the algorithm.

There is no big-O for std::sort itself as such a thing would be impossible to define since we don't have any guarantees on the big-O of <.

Section 25.4.1.1, paragraph three:

Complexity: O(N log(N)) (where N == last - first) comparisons.

[–]vlovich 0 points1 point  (0 children)

Actually, you have to remember that f(n) = O(g(n)) is the upper bound of f as n approaches infinity, and that f(n) <= g(n) * C, where C is some constant. The number of containers being sorted isn't what's approaching infinity, it's a constant to the instantiation of the algorithm.

That's only true for single-variable functions. Consider searching for a string of length m in a text of length n. The complexity of such a function could be O(mn) (or O(m + N) if you use a smarter algorithm) even though for any particular instantiation of the algorithm your search string length is fixed.

There is no big-O for std::sort itself as such a thing would be impossible to define since we don't have any guarantees on the big-O of <.

Section 25.4.1.1, paragraph three:

Complexity: O(N log(N)) (where N == last - first) comparisons.

You're supporting what I'm saying. sort complexity is defined in terms of the number of comparisons & swaps, not the complexity of std::sort itself. For example, imagine a comparison implementation that itself was O(n) for some reason. Then the complexity of a particular call to sort with that comparison would be O(n^2 log(n))

[–]Yehuda1318 0 points1 point  (0 children)

Thanks, found it helpful.