On Tue, Dec 15, 2009 at 10:00 AM, me22 <
me2...@gmail.com> wrote:
> 2009/12/14 Dean Michael Berris <
mikhai...@gmail.com>:
>> On Tue, Dec 15, 2009 at 7:23 AM, me22 <
me2...@gmail.com> wrote:
>>>
>>> A BackwardIterator provides operator-- instead of operator++.
>>>
>>
>> Hmmm... That's not a big fundamental difference.
>>
>
> I agree -- with just one range it's a completely pointless
> distinction. (Except for the fact that I think reverse should be an
> involution on forward ranges too, which means that the intermediate
> type should have a concept too, even if it's just
> CanBeReversedToGetAForwardRangeRange.)
>
I think reverse would only make sense on a Range if the range is
finite though. This is what I mean by having sentinels in the range
implementation which allows you to modify the range from either
direction (which pre-supposes a Bidirectional Range).
> If you have 2 ranges that are supposed to represent the same sequence
> of elements (but aren't over the same storage), though, then it makes
> sense for the primary "direction" of one to be the opposite of the
> other's.
>
But the Range concept that I have in my mind has nothing to do with
storage -- just the representation of a range of values. Now whether
your range returns rvalues instead of references (which I would
personally prefer, because you really can simulate lvalue references
with first-class objects with the (additional?) cost of storing a
pointer instead of the actual value aka a reference proxy) is
completely a different matter altogether. ;)
Ah, alright. :) I have yet to completely drink the Alexandrescu
Kool-Aid on this whole range as primitive business though. I don't
also see why you need the STL to be implemented in other programming
languages too. :D
>>
>> If you have sentinels to mark your data structure bounds and use that
>> to implement the reverse operation (assuming you actually have optimal
>> storage to store just one direction links -- in tries, you'd have the
>> parent link)
>
> I do have a parent link, but I want to traverse the paths from root to
> specific nodes, since those are the "sequences" stored in the trie. I
> have optimal storage for that path backwards -- the parent links --
> but not forwards, since that would require an oracle to tell me which
> child pointer to take at each step.
>
Yes, but if you consider your Range to represent a state machine (or a
permutation machine) instead of just representing values in the trie,
then you may have an optimal (read, logarithmic time) implementation
of a forward traversal of the "permutations" or "traversals" of your
trie. This means for a trie:
h - e ---- l - l - o
\ \
a - y p
You can then have a range that represents intermediate traversals. For
instance, a range 'r' that starts at the root of the trie will
represent just 'h'. If you move forward, you then get as the next
element of the range the concatenation of 'h' and a pre-determined
traversal of the trie, for instance 'ha', then next element is 'hay',
next is 'he', 'hel', 'help', 'hell', 'hello'.
Then what you get is a range of ranges instead of a "flattened" view
of your data structure. You can do away with storing the concatenated
range too and just yield the most recent element in the traversal
(when accessing range.front) just taking note the traversal algorithm
state as you go shrinking the range *from one direction*. If you used
sentinels you can make that range a BidirectionalRange.
It helps if you think about non-linear coordinate structures
represented using iterators. What you'll find is for example on a
binary tree that you want to traverse using a single iterator (let me
get away from ranges for a while):
a
b c
You then have a projection of the 2-dimensional coordinate structure
on-to a 1-dimensional coordinate structure using a state-ful iterator;
just like the way you "flatten" a binary tree is to traverse it using
any one of the available traversal algorithms (pre-, in-, post-).
However that would mean your traversal algorithm is embedded in the
iterator, and keeps state on an incremental basis -- which is really
the "trick" or "approach" that Stepanov and McJones present in their
book.
>> then you can provably traverse the data structure using normal
>> forward traversal algorithms -- but your range should implement
>> the reverse internally. Something like:
>>
>> container c;
>> auto r = reverse(c.all());
>>
>> Then you can apply the normal forward traversal on the range just like
>> you will any other range that can be traversed in one direction. That
>> immediately makes your range usable in other algorithms without
>> creating a special type of Iterator or Range.
>>
>> [...]
>>
>> I still don't see why you need the BackwardRange when you already have
>> a ForwardRange. I just don't think there's any value to an analog when
>> if I'm reading what you're explaining correctly, conceptually:
>>
>> BackwardRange = ForwardRange<Reversed<Range> >
>>
>> Except for operator-- instead of operator++, I don't see any real
>> difference worth highlighting.
>>
>
> Ah, ok. I get what you've been saying, now.
>
> I suppose my complaint is then, "Why not have a concept for
> SomethingThatIfYouPassItToReverseGivesYouAForwardRange?"
>
> Right now I can't pass it to reverse_copy, since that demands a
> BidirectionalRange, not a ForwardRange<Reversed<Range> >.
>
Actually, you should still be able to do that. Let me explain a little.
ForwardRange<Reversed<Range> > can still model a BidirectionalRange
using a concept map. Take note that it requires Range to be Finite.
This happens because you can theoretically already deduce that a Range
that is Reversed implies that it can be traversed from either
direction. And since reverse(...) is reversed by another call to
reverse (yeah, pun not intended) then it already makes your
Reversed<Range> a BidirectionalRange. You can then lift this to
"override" the "ForwardRange" constraint but only for the specific
case.
This means all you need to do is map a ForwardRange<Reversed<Range> >
to adapt it to be conceptually equivalent to a BidirectionalRange only
because it's proven that only a Finite Range can be Reverse<>'d.
> I suppose calling it a ForwardRange<Reversed<Range> > is sufficient,
> but why shouldn't they be more orthogonal? Alexandrescu points out
> that infiniteness means that RandomAccessRange is a refinement of
> ForwardRange, not BidirectionalRange, and I think a similar
> observation is valid for BidirectionalRange.
>
> Why shouldn't Bidi be defined like this?
>
> BidirectionalRange<Range> = And< ForwardRange<Range>,
> BackwardRange<Range> >
>
Because BackwardRange can already be modeled as
ForwardRange<Reverse<Range> >. ;) Also because it implies that Range
is already finite for it to be reversible.
>>
>> Hold on.
>>
>> Given:
>> r = [0 .. infinity)
>> popBack(range) -> range' , where size(range') = size(range) - 1 and
>> front(range') == front(range)
>> Then:
>> popBack(r) -> r
>> Proof:
>> Because size(r) = infinity
>> and infinity - 1 == infinity
>> and front(r) == front(popBack(r)) == 0
>> QED
>>
>> I don't see why popBack has to do anything to an infinite ForwardRange. ;)
>>
>
> You're right; it only has something to do with a reversed infinite ForwardRange.
>
But that doesn't make sense. You can't really reverse an infinite
range, because you don't know the "other end" from which to start
from.
> reverse(r).popBack() === r.popFront()
>
> So when you do the same thing with operator[]:
>
> reverse(r)[i] === r[f(i)], for some appropriate involution f
>
> But using the idea of your proof, when r is [0, inf),
>
> reverse(r)[0] === reverse(r)[1] === ...
>
> So the only useful definition of [] means it needs to take a negative number.
>
But you're forgetting that [0..infinity) cannot be reversed to become
[infinity..-1) and still model ForwardRange. If infinity means real
infinity and not some machine-induced storage bound, then you're out
of luck. But if you want to model all the numbers representable by a
given bounded integral type, then this is what you call a Group, and
from there you can definitely implement reverse(...).
> If you take the infinite range of Naturals, ℕ = (0, 1, 2, ...), then I
> doubt anyone would complain about this (other than abuse of notation):
>
> ∀ i ∈ ℕ, ℕ[i] = i.
>
> So extending that by letting it "overflow" infinity (thanks to some
> more notation abuse), we get all the integers, ℤ = (0, 1, 2, 3, ...,
> -4, -3, -2, -1), and can keep an analogous property:
>
> ∀ i ∈ ℤ, ℤ[i] = i.
>
Although that's not mathematically sound, there's really no way (using
Euclidean geometry and flat 1-d numerical domain spaces) for you to
"wrap around infinity". Unless you're dealing with hyper-parabolic
mathematical number spaces, in which case you may have to throw out
all the notions of infinity you may have in the Euclidean sense. ;)
> That suggests that the involution of choice is the twos-complement:
>
> reverse(r)[i] === r[~i]
>
> Which seems pleasing to me, since already in C++03, for a signed integer i:
>
> v.begin() + i == v.end() + ~i
>
> (Assuming its magnitude is appropriately small.)
>
Here, it also assumes that v is finite. ;)
>> * I don't see why offset-addressing is fundamentally different from
>> successive range shrinking from one end (i.e., from the start element
>> or start sentinel).
>>
>
> Yup; there's no fundamental reason for operator[] to be a fundamental one, since
>
> r[i] === (t = r, t.pop_front(i), t.front())
>
> But what if, instead, rr = (-inf, -1], so there are no *_front functions?
>
> rr[i] === (t = r, t.pop_back(f(i)), t.back())
>
> (Which is just a different perspective on what I said above.)
>
> Hoping he made more sense this time,
Ah, so you want to be able to turn a ForwardRange<Range> where
FrontBoundedInifiniteRange<Range> into a BackwardRange<Range> where
front() is not defined. There may be some merit to that though but
only for ForwardRange<Range> where FrontBoundedInfiniteRange<Range>.
Now I think I understand the motivation for a
BackwardRange/BackwardIterator, and I must say thinking about it more,
there would be some merit to that. Now to implement that
FrontBoundedInfiniteRange Concept... :D