From: Dave Harris on
nathancbaker(a)gmail.com (Nathan) wrote (abridged):
> > � while ( data[xInd][yInd][zInd] != value && result != 0)
> > � {
> > � � � if {++zInd == data[xInd][yInd].size()) {
> > � � � � � zInd = 0;
> > � � � � � if (++yInd == data[xInd].size()) {
> > � � � � � � � yInd = 0;
> > � � � � � � � if (++xInd == data.size())
> > � � � � � � � � � result = 0;
> > � � � � � }
> > � � � }
> > � ...
> >
>
> Isn't <something>.size() a method or function call? We'd also want
> to eliminate that needless activity from the loop by defining "zMax =
> data[xInd][yInd].size()", and etc., before the loop.

That would introduce another bug. We're not told that all the arrays are
the same size, so for example data[0][0].size() might be different to
data[0][0].size().

We could cache the result of size(), but we'd then have to update it when
the corresponding index changed. That's easy enough in the original
nested-loop code, but gets quite complicated in the single-loop version.
(I was going to post some example code, but it was frankly too long and
ugly.)

This thread has been cross-posted to a lot of groups, and some people
seem to be commenting without knowing C++ or knowing what an expression
like data[xInd][yInd].size() probably means. The data structure might be
declared like:

typedef std::vector<int> z_vec;
typedef std::vector<z_vec> y_vec;
typedef std::vector<y_vec> x_vec;
x_vec data;

The memory is unlikely to be contiguous.

-- Dave Harris, Nottingham, UK.
From: Daniel T. on
Nick <3-nospam(a)temporary-address.org.uk> wrote:
> "Daniel T." <daniel_t(a)earthlink.net> writes:
> > Nick <3-nospam(a)temporary-address.org.uk> wrote:
> > > "Daniel T." <daniel_t(a)earthlink.net> writes:
> > > > Seebs <usenet-nospam(a)seebs.net> wrote:
> > > > > On 2010-05-08, Richard Heathfield <rjh(a)see.sig.invalid> wrote:
> > > > > > Seebs wrote:
> > > > > > > On 2010-05-02, Richard Heathfield <rjh(a)see.sig.invalid> wrote:
> > > > > > >
> > > > > > > Because, in some cases, the checks against a[i] make it
> > > > > > > harder to follow the code.
> > > > >
> > > > > > In my experience, it is generally easier to follow the code
> > > > > > if it has a clear structure.
> > > > >
> > > > > I would agree that it is *GENERALLY* easier.
> > > > >
> > > > > But in some cases it's not.
> > > > >
> > > > > The vanilla loop idiom ("for (i = 0; i < N; ++i)") has a HUGE
> > > > > weight of idiomatic clarity -- it does not require noticable
> > > > > attention to model it, and that clarity is lost as soon as you
> > > > > change the loop condition.
> > > > >
> > > > > In short:
> > > > > for (i = 0; i < N; ++i)
> > > > > if (foo(i))
> > > > > return i;
> > > > > return -1;
> > > > >
> > > > > is nearly always much easier for a programmer to grok in fullness
> > > > > than
> > > > > for (i = 0; i < N && !foo(i); ++i)
> > > > > ;
> > > > > if (i != N)
> > > > > return i;
> > > > > else
> > > > > return -1;
> > > >
> > > > While both of the above are much worse than:
> > > >
> > > > int i = 0;
> > > > while (i < N && !foo(i))
> > > > ++i;
> > > > return i;
> > > >
> > > > The above is a standard idiom in C++.
> > >
> > > That one doesn't distinguish between "found" and "not found".
> >
> > Of course it does, and it does so more efficiently. For the other
> > examples "if (i != N)" is checked and then later "if (result != -1)"
> > must be checked. With the example I presented only one "if" is
> > necessary.
>
> Am I being particularly dumb here (it's been known). Are you saying
> that you elsewhere in the code you use i!=N as the check? In which
> case, I suppose so, but it's a pain to use if 'N' isn't easily
> calculable or available later (say it's not actually i<N but a test
> against a sentinal).

In this particular case 'N' (as opposed to 'n') implies that it's a
macro. In any case a specific range is being checked, and it would be
very poor design to define the range solely within the function in
question. Typically it is passed into the function, which means it is
available to the caller.

> > (Note here, I'm not arguing for single exit in every case, but it's
> > clearly a better choice in *this* example.
>
> Only if you're prepared to check against N whenever you need to (or - as
> I've said - I'm missing the blindingly obvious).

Again, the calling code must do an 'if' check in any case, so the
question is, why is the function *also* making the check? If by some odd
stroke the calling code doesn't know the value of 'N' that can easily be
fixed.
From: Daniel T. on
brangdon(a)cix.co.uk (Dave Harris) wrote:
> daniel_t(a)earthlink.net (Daniel T.) wrote (abridged):
> >
> > > I considered submitting a single loop solution as a joke. It
> > > never crossed my mind someone would seriusly propose it!
> >
> > I seriously proposed it. I think it is the best solution for the
> > job (not your code specifically of course, but the idea of a single
> > loop traversing a single container.)
>
> Yes. It led to simple code, but that was partly because all the
> complexity was hidden behind an iterator. Nathan Baker's version exposes
> some of that complexity, and I think illustrates how hard it is to get it
> right.
>
> For example, you can't just initialise the iterator's members to 0,0,0
> because data.size() might be 0 and data[0][0][0] may not exist. So you
> need a loop inside the iterator's constructor to find the first iterator
> value that can be dereferenced, and another loop inside the iterator's
> increment operator to find the next one.
>
> I think. If someone posted actual, correct code, I missed it. Did you try
> implementing the iterator you suggested? Did it still have 3 loops
> really?

I didn't post anymore than I did because there isn't enough context to
tell what the best solution is. Even though .size() is checked at each
level, there may still be an invariant such that all the sizes are the
same.

People often advocate using nested vectors to implement
multi-dimensional arrays, even when they are not ragged arrays, and the
OP may have been expressing that sort of code. In a situation where
ragged arrays are necessary, it is highly unlikely that they will be
treated as a contiguous unit as the OP's code does.

But in any case, no more can be said unless a real use-case is
presented. All I'm saying is that the assumption that three loops are
necessary to express an O(n) algorithm is inappropriate.
From: Nick on
"Daniel T." <daniel_t(a)earthlink.net> writes:

> Nick <3-nospam(a)temporary-address.org.uk> wrote:
>> Only if you're prepared to check against N whenever you need to (or - as
>> I've said - I'm missing the blindingly obvious).
>
> Again, the calling code must do an 'if' check in any case, so the
> question is, why is the function *also* making the check? If by some odd
> stroke the calling code doesn't know the value of 'N' that can easily be
> fixed.

But the function is only *also* making the check because we've
eliminated the early return. With the early return there's no need for
the second check.

Taking Richard H's joke that I missed last time, here we go round the
loop again.
--
Online waterways route planner | http://canalplan.eu
Plan trips, see photos, check facilities | http://canalplan.org.uk
From: Daniel T. on
Nick <3-nospam(a)temporary-address.org.uk> wrote:
> "Daniel T." <daniel_t(a)earthlink.net> writes:
> > Nick <3-nospam(a)temporary-address.org.uk> wrote:
> >
> > > Only if you're prepared to check against N whenever you need to
> > > (or - as I've said - I'm missing the blindingly obvious).
> >
> > Again, the calling code must do an 'if' check in any case, so the
> > question is, why is the function *also* making the check? If by some
> > odd stroke the calling code doesn't know the value of 'N' that can
> > easily be fixed.
>
> But the function is only *also* making the check because we've
> eliminated the early return. With the early return there's no need for
> the second check.

All I'm saying is that you can remove the early return *and* the extra
'if' check, if you aren't dogmatic about the not-found flag being -1.

> Taking Richard H's joke that I missed last time, here we go round the
> loop again.

Not at all. If for some reason the function *must* return -1 for a
not-found flag, then an early return is the correct choice; however, I'm
questioning that assumption. I'm not going back around the loop, I'm
moving up a level of abstraction.