digitalmars.D - C++'s std::rotate
- Andrei Alexandrescu (59/59) Aug 10 2014 Hello,
- Dragos Carp (12/17) Aug 10 2014 sameTail is also in the Phobos docs. overlap is not documented.
- =?UTF-8?B?Ik5vcmRsw7Z3Ig==?= (5/10) Aug 11 2014 Shouldn't the function arguments of sliceOf be reversed to given
- Andrei Alexandrescu (2/11) Aug 11 2014 isSliceOf -> yum
- Dragos Carp (2/3) Aug 11 2014 PR created:
- =?UTF-8?B?Ik5vcmRsw7Z3Ig==?= (4/4) Aug 11 2014 On Monday, 11 August 2014 at 14:45:09 UTC, Andrei Alexandrescu
- =?UTF-8?B?Ik5vcmRsw7Z3Ig==?= (2/6) Aug 11 2014 I get it, Andrei :)
- Andrei Alexandrescu (3/9) Aug 11 2014 Yah, all about making "a.isSliceOf(b)" a nice phrase and keeping the
- monarch_dodra (10/24) Aug 22 2014 While "sameHead" and "sameTail" *could* have a "good enough"
- =?UTF-8?B?Ik5vcmRsw7Z3Ig==?= (9/14) Aug 11 2014 Correction: This is what I think you mean:
- Dragos Carp (3/17) Aug 11 2014 Yes, of course. I had lhs, rhs and messed up the renaming of
- Dragos Carp (1/12) Aug 11 2014 https://github.com/dcarp/phobos/compare/sliceOf
- =?UTF-8?B?Ik5vcmRsw7Z3Ig==?= (4/5) Aug 11 2014 Why not use something like "part" and "whole" instead of "lhs"
- Peter Alexander (4/4) Aug 11 2014 This reminds me, we still need "allBefore" to implement
- Ary Borenszweig (2/3) Aug 11 2014 In which algorithms would one use std::rotate?
- "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= (2/5) Aug 11 2014 http://en.wikipedia.org/wiki/Block_sort
- Peter Alexander (3/6) Aug 11 2014 Pushing N items to the front of a vector is implemented as
- Andrei Alexandrescu (19/22) Aug 11 2014 Depends on whom you ask :o). I think it's a fairly obscure algorithm,
- Fool (6/7) Aug 17 2014 Is your design final with respect to handling degenerate cases?
- Andrei Alexandrescu (21/27) Aug 17 2014 Yah, it's a good option. Relevant code:
- Fool (10/26) Aug 19 2014 A cleaned-up version is here: http://dpaste.dzfl.pl/a8c36a0c36d0
- Andrei Alexandrescu (3/19) Aug 19 2014 FWIW just added https://issues.dlang.org/show_bug.cgi?id=13335
- Peter Alexander (12/17) Aug 18 2014 This doesn't work for ranges that visit the same element twice,
- Andrei Alexandrescu (6/21) Aug 18 2014 Correct. Though maybe that's a good thing - rotating a cycle is unlikely...
Hello,
In the discussion of iterators vs. ranges in D, ranges "won" in the 
sense that there hasn't been strong evidence of a need for iterators to 
complement ranges; people have merrily used std.algorithm and friends 
and all was good.
However, it has been long acknowledged that iterators are sometimes more 
expressive/economical than ranges, in the same way pointers are more so 
than slices: iterators are a lower-level building block so they can be 
used to build ranges and also other things. There's an inclusion 
relationship between what can be done with iterators and what can be 
done with ranges.
Amid these observations, C++'s std::rotate (http://goo.gl/z8FjV) and 
derivative algorithms have been a prime category of examples. C++'s 
std::rotate takes a "three-legged range", i.e. three iterators begin, 
middle, and end, and rotates the range [middle, end) to precede [begin, 
middle). It returns (as of C++11) the new middle. For example, given the 
range [0, 1, 2, 3, 4, 5] with begin -> 0, mid -> 2, and end -> just past 
5, rotate rearranges elements as [2, 3, 4, 5, 0, 1, 2] and returns a 
pointer to the new position of 0.
This is a very challenging algorithm for ranges because it's difficult 
to express both the input and the output appropriately. My first insight 
into the matter has been that the ranges [begin, middle) and [middle, 
end) don't even need to be adjacent, so I defined bringToFront 
(http://goo.gl/5HUCiV) as a slightly different take on std::rotate. It 
rotates any two ranges, adjacent or not, and returns the number of 
elements in the right-hand range.
That's in a way more general than std::rotate but at the same time less 
satisfactory because it returns only a number, giving no access to the 
new positions of the two elements.
Following an exchange with Eric Niebler I've scored one more 
mini-breakthrough today: a variant of std::rotate that rivals C++'s one, 
without needing iterators. The only abstraction needed is 
r1.sameFront(r2), which returns true iff the forward ranges r1 and r2 
have fronts pointing to the same element. The function can be 
implemented generically for ranges that offer front as a reference:
bool sameFront(R1, R2)(R1 r1, R2 r2) {
   return &r1.front == &r2.front;
}
Ranges that offer rvalues from front (proxying etc) must implement 
sameFront as a primitive.
Implementation is at http://dpaste.dzfl.pl/a0effbaee0a9. For historical 
reasons I've reused an undocumented function sameHead. It has the 
meaning of sameFront above. Signature is:
Tuple!(typeof(whole.takeExactly(0)), R)
rotateTail(R)(R whole, R right);
The algorithm assumes that "right" is a subrange of "whole" sitting at 
its tail, i.e. calling while.popFront a number of times will make 
whole.sameFront(right) true. It moves right to the front of whole and 
returns (here's the beauty of it) the new positions of the two subranges.
For example, say whole = [0, 1, 2, 3, 4, 5] and right = whole[4 .. $]. 
Then rotateTail(whole, right) makes whole = [4, 5, 0, 1, 2, 3] and 
returns tuple(whole[0 .. 2], whole[2 .. $]).
An essential role in this all is takeExactly (just like take, but knows 
the length in O(1)). It's been an essential component in a few 
range-based algorithms and it seems an important abstraction that closes 
the circle on rotateTail as well, almost too fittingly. Eric mentioned 
that he independently saw a need for it and defined what he calls 
SizedIteratorRange.
Andrei
 Aug 10 2014
Nice work!Implementation is at http://dpaste.dzfl.pl/a0effbaee0a9. For historical reasons I've reused an undocumented function sameHead.sameHead is documented. I already use it a couple of times.The algorithm assumes that "right" is a subrange of "whole" sitting at its tail, ...sameTail is also in the Phobos docs. overlap is not documented. A couple of times writing contracts, I missed something like: bool sliceOf(T)(in T[] whole, in T[] slice) { return whole.ptr <= slice.ptr && whole.ptr + slice.length <= whole.ptr + slice.length; } The alternative using overlap: overlap(whole, slice) is slice is a little bit too expensive for my use case.
 Aug 10 2014
On Monday, 11 August 2014 at 06:56:52 UTC, Dragos Carp wrote:
 bool sliceOf(T)(in T[] whole, in T[] slice)
 {
     return whole.ptr <= slice.ptr &&
         whole.ptr + slice.length <= whole.ptr + slice.length;
 }
Shouldn't the function arguments of sliceOf be reversed to given 
a more intuitive UCFS as
     if (slice.sliceOf(whole) { ... }
?
 Aug 11 2014
On 8/11/14, 2:11 AM, "Nordlöw" wrote:On Monday, 11 August 2014 at 06:56:52 UTC, Dragos Carp wrote:isSliceOf -> yumbool sliceOf(T)(in T[] whole, in T[] slice) { return whole.ptr <= slice.ptr && whole.ptr + slice.length <= whole.ptr + slice.length; }Shouldn't the function arguments of sliceOf be reversed to given a more intuitive UCFS as if (slice.sliceOf(whole) { ... }
 Aug 11 2014
isSliceOf -> yumPR created: https://github.com/D-Programming-Language/phobos/pull/2416
 Aug 11 2014
On Monday, 11 August 2014 at 14:45:09 UTC, Andrei Alexandrescu wrote: isSliceOf -> yum Can you elaborate on that?
 Aug 11 2014
On Monday, 11 August 2014 at 18:11:19 UTC, Nordlöw wrote:On Monday, 11 August 2014 at 14:45:09 UTC, Andrei Alexandrescu wrote: isSliceOf -> yum Can you elaborate on that?I get it, Andrei :)
 Aug 11 2014
On 8/11/14, 11:12 AM, "Nordlöw" wrote:On Monday, 11 August 2014 at 18:11:19 UTC, Nordlöw wrote:Yah, all about making "a.isSliceOf(b)" a nice phrase and keeping the tradition of other isXxx names in Phobos. -- AndreiOn Monday, 11 August 2014 at 14:45:09 UTC, Andrei Alexandrescu wrote: isSliceOf -> yum Can you elaborate on that?I get it, Andrei :)
 Aug 11 2014
On Monday, 11 August 2014 at 14:45:09 UTC, Andrei Alexandrescu wrote:On 8/11/14, 2:11 AM, "Nordlöw" wrote:While "sameHead" and "sameTail" *could* have a "good enough" generic implementation for ranges, there is absolutely no way to make "isSliceOf" or "overlap" work for a generic range. That said, sameHead and sameTail is just the iterator equivalent of "first1 == first2" and "last1 == last2", which is used a lot with iterators. You rarely see operator "<" used with iterators though, so I have doubts about why those two functions (isSliceOf and overlap) would actually be of any use.On Monday, 11 August 2014 at 06:56:52 UTC, Dragos Carp wrote:isSliceOf -> yumbool sliceOf(T)(in T[] whole, in T[] slice) { return whole.ptr <= slice.ptr && whole.ptr + slice.length <= whole.ptr + slice.length; }Shouldn't the function arguments of sliceOf be reversed to given a more intuitive UCFS as if (slice.sliceOf(whole) { ... }
 Aug 22 2014
On Monday, 11 August 2014 at 06:56:52 UTC, Dragos Carp wrote:
 bool sliceOf(T)(in T[] whole, in T[] slice)
 {
     return whole.ptr <= slice.ptr &&
         whole.ptr + slice.length <= whole.ptr + slice.length;
 }
Correction: This is what I think you mean:
bool sliceOf(T)(in T[] part,
                 in T[] whole)
{
     return (whole.ptr <= part.ptr &&
             part.ptr + part.length <=
             whole.ptr + whole.length);
}
 Aug 11 2014
On Monday, 11 August 2014 at 10:09:53 UTC, Nordlöw wrote:On Monday, 11 August 2014 at 06:56:52 UTC, Dragos Carp wrote:Yes, of course. I had lhs, rhs and messed up the renaming of those.bool sliceOf(T)(in T[] whole, in T[] slice) { return whole.ptr <= slice.ptr && whole.ptr + slice.length <= whole.ptr + slice.length; }Correction: This is what I think you mean: bool sliceOf(T)(in T[] part, in T[] whole) { return (whole.ptr <= part.ptr && part.ptr + part.length <= whole.ptr + whole.length); }
 Aug 11 2014
https://github.com/dcarp/phobos/compare/sliceOfCorrection: This is what I think you mean: bool sliceOf(T)(in T[] part, in T[] whole) { return (whole.ptr <= part.ptr && part.ptr + part.length <= whole.ptr + whole.length); }Yes, of course. I had lhs, rhs and messed up the renaming of those.
 Aug 11 2014
On Monday, 11 August 2014 at 11:00:41 UTC, Dragos Carp wrote:https://github.com/dcarp/phobos/compare/sliceOfWhy not use something like "part" and "whole" instead of "lhs" and "rhs"? It is more self-documenting.
 Aug 11 2014
This reminds me, we still need "allBefore" to implement nextPermutation correctly for bidirectional ranges. https://issues.dlang.org/show_bug.cgi?id=12188 I think this would help here also.
 Aug 11 2014
On 8/11/14, 12:29 AM, Andrei Alexandrescu wrote:Hello,In which algorithms would one use std::rotate?
 Aug 11 2014
On Monday, 11 August 2014 at 13:55:07 UTC, Ary Borenszweig wrote:On 8/11/14, 12:29 AM, Andrei Alexandrescu wrote:http://en.wikipedia.org/wiki/Block_sortHello,In which algorithms would one use std::rotate?
 Aug 11 2014
On Monday, 11 August 2014 at 13:55:07 UTC, Ary Borenszweig wrote:On 8/11/14, 12:29 AM, Andrei Alexandrescu wrote:Pushing N items to the front of a vector is implemented as pushing N to the back then rotating them to the front.Hello,In which algorithms would one use std::rotate?
 Aug 11 2014
On 8/11/14, 6:55 AM, Ary Borenszweig wrote:On 8/11/14, 12:29 AM, Andrei Alexandrescu wrote:Depends on whom you ask :o). I think it's a fairly obscure algorithm, better suited as representative of a class rather than frequently useful. Facebook's C++ code base only has few uses, all in the same pattern rotate(b, b + 1, e) i.e. bubble the front all the way to the back with random iterators. (The result is not used and is trivially e - 1.) (One frequent use is in ranges/iterators debates; out of the woodwork come people whose livelihood apparently depends on rotate, in particular on using rotate with non-random iterators (random access ranges make it easy to implement) AND needing the result.) I do think that, like partition or partialSort, std::rotate is one of those algorithms that's good to know about because it may save some otherwise awkward solutions. Sean Parent has a nice talk http://channel9.msdn.com/Events/GoingNative/2013/Cpp-Seasoning on such. For my money I think http://dpaste.dzfl.pl/a0effbaee0a9 is fine (after being of course productionized to take advantage of random access if available, improve on the recursion, etc). Requiring sameHead or random access seems to me a good sweet spot. AndreiHello,In which algorithms would one use std::rotate?
 Aug 11 2014
On Monday, 11 August 2014 at 15:04:52 UTC, Andrei Alexandrescu wrote:http://dpaste.dzfl.pl/a0effbaee0a9Is your design final with respect to handling degenerate cases? What do you think about http://dpaste.dzfl.pl/8fc83cb06de8 ?
 Aug 17 2014
On 8/17/14, 9:06 AM, Fool wrote:On Monday, 11 August 2014 at 15:04:52 UTC, Andrei Alexandrescu wrote:Yah, it's a good option. Relevant code: if (right is whole) { //writeln("case identical"); return tuple(right, whole.dropExactly(whole.length)); } (Prolly it would be better to use "whole.sameHead(right)" instead of "right is whole". Also whole may not define length so the actual algo is just a tad different.) Trouble is it takes O(n) for that trivial case and for what may be in all likelihood a useless return (iterate right all the way through the end and return the empty balance). On the bright side, client code does have the option to make the test before calling rotateTail so in a way the function is more consistent/complete while still leaving the caller the option to avoid the corner case. Not sure what's the best call here yet. Loosely related, Xinok wrote a nice piece on in-place merge: http://goo.gl/AS2P4A. A great use of rotateTail. Andreihttp://dpaste.dzfl.pl/a0effbaee0a9Is your design final with respect to handling degenerate cases? What do you think about http://dpaste.dzfl.pl/8fc83cb06de8 ?
 Aug 17 2014
On Monday, 18 August 2014 at 05:27:11 UTC, Andrei Alexandrescu wrote:On 8/17/14, 9:06 AM, Fool wrote:A cleaned-up version is here: http://dpaste.dzfl.pl/a8c36a0c36d0http://dpaste.dzfl.pl/8fc83cb06de8Yah, it's a good option. Relevant code: if (right is whole) { //writeln("case identical"); return tuple(right, whole.dropExactly(whole.length)); } (Prolly it would be better to use "whole.sameHead(right)" instead of "right is whole". Also whole may not define length so the actual algo is just a tad different.)Trouble is it takes O(n) for that trivial case and for what may be in all likelihood a useless return (iterate right all the way through the end and return the empty balance).Yah, on the other hand you can only expect to get what you pay for. The strategy that naturally arises from the basic algorithm is even worse. In the case at hand, it suggests executing n trivial swaps.Loosely related, Xinok wrote a nice piece on in-place merge: http://goo.gl/AS2P4A. A great use of rotateTail.Thanks for that link. It's a nice writeup. -- Fool
 Aug 19 2014
On 8/19/14, 12:32 PM, Fool wrote:On Monday, 18 August 2014 at 05:27:11 UTC, Andrei Alexandrescu wrote:FWIW just added https://issues.dlang.org/show_bug.cgi?id=13335 AndreiOn 8/17/14, 9:06 AM, Fool wrote:A cleaned-up version is here: http://dpaste.dzfl.pl/a8c36a0c36d0http://dpaste.dzfl.pl/8fc83cb06de8Yah, it's a good option. Relevant code: if (right is whole) { //writeln("case identical"); return tuple(right, whole.dropExactly(whole.length)); } (Prolly it would be better to use "whole.sameHead(right)" instead of "right is whole". Also whole may not define length so the actual algo is just a tad different.)
 Aug 19 2014
After having some fun with rotateTail and friends a draft 
implementation [2] is now available and ready for comments aiming 
at formal specification, algorithmics, and implementation details.
While most ideas were taken from [1], all mistakes are mine. 
Since this is my first D code, more or less, please take it with 
a grain of salt and don't hesitate to express the obvious.
Summary
The original recursive algorithm applicable to ForwardRange 
experienced another pass [B]. It was put out of contention by an 
iterative transcription [D] which turned out to be notably faster.
For bidirectional iterators, there is a beautiful reduction to 
three calls of the reverse function. It was not hard to come up 
with a corresponding version for BidirectionalRange [E].
Finally, I looked at a third approach which prefers moving to 
swapping. My implementation [C], however, does not seem to be 
competitive in practice.
Compared to their iterator analogues, the class of range based 
rotation algorithms considered comprise a certain amount of 
additional cost. Practice will show whether it is relevant. On 
the other hand it seems to me that this cost could be minimized 
if ranges were based on iterators.
At this point, I refrain from posting results of my biased 
performance tests. It is safe to assume that the implementation 
can be tuned based on profiling information.
All algorithms preliminarily handle degenerate cases as proposed 
by myself. I believe now that rotateTail(r, r) should return 
r.popFrontAll [A] if and only if it is not more expensive than 
returning some empty range. Is there a way to check whether 
r.popFrontExactly(r.walkLength) is O(1)?
Fool
--
[1] A. Stepanov: Rotate - Lecture 19. in Notes on Programming, 
2007, pp.154--167
     URL http://www.stepanovpapers.com/notes.pdf
[2] http://dpaste.dzfl.pl/514a1ef77d0a
[A] popFrontAll, ll.47ff
[B] rotateTailRecursive, ll.75ff
[C] rotateTailCycle, ll.147ff
[D] rotateTail, ll.227ff, and rotateTailAux, ll.325ff
[E] rotateTail, ll.227ff, and rotateTailAux, ll.407ff
 Aug 23 2014
On Saturday, 23 August 2014 at 18:07:43 UTC, Fool wrote:[2] http://dpaste.dzfl.pl/514a1ef77d0aThe 'three reverses' algorihm could be actually improved. Updated code (along with additional unit tests and a strengthened assertion) is available at [3]. I compared this implementation to an iterator based C++ translation and std::rotate [4]. Considering C++ std::vector and D dynamic array, here are the results: compiler | algorithm | execution time ----------+--------------------------+---------------- clang++ | std::rotate | 1.62s clang++ | my_rotate / std::reverse | 1.45s clang++ | my_rotate / my_reverse | 0.58s ldc2 | rotateTail | 1.64s g++ | std::rotate | 0.57s g++ | my_rotate / std::reverse | 1.43s g++ | my_rotate / my_reverse | 0.36s gdc | rotateTail | 0.38s Here, my_reverse is an adaption of D's reverse for RandomAccessRange. These results make me think that the implementation proposed for RandomAccessRange is not too bad. It needs to be investigated why, in this particular situation, ldc2 produces significantly slower code than gdc and clang. System: GNU/Linux x86_64 Compiler versions: clang version 3.4.2 LDC - the LLVM D compiler (0.14.0): based on DMD v2.065 and LLVM 3.4.2 g++ (GCC) 4.9.1 gdc (GCC) 4.9.1 Compiler flags: clang++ -std=c++11 -O3 -march=native ldc2 -O3 -mcpu=native -release -disable-boundscheck g++ -std=c++11 -O3 -march=native gdc -O3 -march=native -frelease -fno-bounds-check [3] http://dpaste.dzfl.pl/b8e47941a007 [4] C++ translation of rotateTail for RandomAccessRange: template <typename TIterator> void my_reverse ( TIterator b, TIterator e ) { auto steps = std::distance(b, e) / 2; if (steps) { auto l = b; auto r = std::prev(e); do { std::iter_swap(l, r); ++l; --r; } while (--steps); } } template <typename TIterator> TIterator my_rotate ( TIterator b, TIterator m, TIterator e ) { if (m == e) return b; if (b == m) return e; // my_reverse(b, m); std::reverse(b, m); auto s = std::next(b, std::distance(m, e)); // my_reverse(b, e); std::reverse(b, e); // my_reverse(s, e); std::reverse(s, e); return s; }
 Aug 24 2014
There was a bug in my C++ translation of rotateTail. The only significant change in execution time is marked below: On Sunday, 24 August 2014 at 09:20:59 UTC, Fool wrote:compiler | algorithm | execution time ----------+--------------------------+---------------- clang++ | std::rotate | 1.62s clang++ | my_rotate / std::reverse | 1.44s clang++ | my_rotate / my_reverse | 0.38s <- ldc2 | rotateTail | 1.64s g++ | std::rotate | 0.57s g++ | my_rotate / std::reverse | 1.43s g++ | my_rotate / my_reverse | 0.37s gdc | rotateTail | 0.38sThe fixed implementation of my_rotate:template <typename TIterator> TIterator my_rotate ( TIterator b, TIterator m, TIterator e ) { if (m == e) return b; if (b == m) return e; // my_reverse(m, e); // <- std::reverse(m, e); // <- auto s = std::next(b, std::distance(m, e)); // my_reverse(b, e); std::reverse(b, e); // my_reverse(s, e); std::reverse(s, e); return s; }
 Aug 24 2014
On Monday, 11 August 2014 at 03:29:56 UTC, Andrei Alexandrescu 
wrote:
[...] can be implemented generically for ranges that offer
 front as a reference:
 bool sameFront(R1, R2)(R1 r1, R2 r2) {
   return &r1.front == &r2.front;
 }
This doesn't work for ranges that visit the same element twice, 
e.g.
cycle(arr).take(arr.length + 1)
[0, 0].map!(i => arr[i])
I suspect most ranges will have to implement the sameFront 
primitive manually, usually forwarding to the underlying range.
Related: most mutating algorithms won't work for these kinds of 
ranges, as we usually presume lvalue ranges never visit the same 
lvalue twice. Perhaps this needs to be mentioned on affected 
algorithms?
 Aug 18 2014
On 8/18/14, 1:44 AM, Peter Alexander wrote:On Monday, 11 August 2014 at 03:29:56 UTC, Andrei Alexandrescu wrote:Correct. Though maybe that's a good thing - rotating a cycle is unlikely to produce something interesting.[...] can be implemented generically for ranges that offer front as a reference: bool sameFront(R1, R2)(R1 r1, R2 r2) { return &r1.front == &r2.front; }This doesn't work for ranges that visit the same element twice, e.g. cycle(arr).take(arr.length + 1) [0, 0].map!(i => arr[i])I suspect most ranges will have to implement the sameFront primitive manually, usually forwarding to the underlying range.Only those that return rvalues from front.Related: most mutating algorithms won't work for these kinds of ranges, as we usually presume lvalue ranges never visit the same lvalue twice. Perhaps this needs to be mentioned on affected algorithms?Looks like a benign effect to me. Andrei
 Aug 18 2014








 
  
  
 
 "Dragos Carp" <dragoscarp gmail.com>
 "Dragos Carp" <dragoscarp gmail.com> 