473,387 Members | 1,897 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,387 software developers and data experts.

Reverse Iteration


Looking at the following function:

#include <cstddef>
#include <iostream>

template<typename T, std::size_t len>
void PrintOutStrings( const char* const (&Array)[len] )
{
for (std::size_t i = 0; i < len; ++i) std::cout << Array[i];
}

Let's say we want to print the strings out backwards... how do you
usually do it? I've a way of doing it and I'm wondering what you think of
it?

#include <cstddef>
#include <iostream>

template<typename T, std::size_t len>
void PrintOutStringsBackwards( const char* const (&Array)[len] )
{
for (std::size_t i = len - 1; ~i; --i) std::cout << Array[i];
}

My rationale here is that the counter variable "i" will get smaller and
smaller. It will become 0, and then when it's decremented again, it will
become 0xFFFF (or 0xFFFFFFFF, or 0xFFFFFFFFFFFFFFF...). If we then invert
the bits using the "~" operator, then we'll get 0; so the loop should
stop then.

Any potential pitfalls?

A quiet nod of approval would do me.

-Tomás
Feb 8 '06 #1
10 2022
"Tomás" <NU**@NULL.NULL> wrote in message
news:ax******************@news.indigo.ie...
: Let's say we want to print the strings out backwards... how do you
: usually do it? I've a way of doing it and I'm wondering what you think
of
: it?
:
: #include <cstddef>
: #include <iostream>
:
: template<typename T, std::size_t len>
: void PrintOutStringsBackwards( const char* const (&Array)[len] )
: {
: for (std::size_t i = len - 1; ~i; --i) std::cout << Array[i];
: }
:
: My rationale here is that the counter variable "i" will get smaller and
: smaller. It will become 0, and then when it's decremented again, it will
: become 0xFFFF (or 0xFFFFFFFF, or 0xFFFFFFFFFFFFFFF...). If we then
invert
: the bits using the "~" operator, then we'll get 0; so the loop should
: stop then.
:
: Any potential pitfalls?

It is ok and portable, but excessively "smart" in my opinion,
with too many "moving parts" (/opportunities for error):
if a signed integer type is used, code will be broken

I find the following loop to be simpler and more "natural":
for ( std::size_t i = len; i-- ; ) std::cout << Array[i];

: A quiet nod of approval would do me.

Personally, I would not accept your version in a code review.

Cheers,
Ivan
--
http://ivan.vecerina.com/contact/?subject=NG_POST <- email contact form
Brainbench MVP for C++ <> http://www.brainbench.com
Feb 8 '06 #2
Tomás wrote:
template<typename T, std::size_t len>
void PrintOutStrings( const char* const (&Array)[len] )
{
for (std::size_t i = 0; i < len; ++i) std::cout << Array[i];
}
Actually, I consider the above function already to be "wrong". The
"correct" version looks like this:

template<typename T, std::size_t len>
void PrintOutStrings( const char* const (&Array)[len] )
{
std::copy(array + 0, array + len,
std::ostream_iterator<char const*>(std::cout));
}
Let's say we want to print the strings out backwards...
.... which kind of prescribes how the reversed version will look like:

template<typename T, std::size_t len>
void PrintOutStrings( const char* const (&Array)[len] )
{
std::copy(std::reverse_iterator<char const*>(array + len),
std::reverse_iterator<char const*>(array + 0),
std::ostream_iterator<char const*>(std::cout));
}
for (std::size_t i = len - 1; ~i; --i) std::cout << Array[i];


I think this should work. However, the more conventional version
is actually much less error prone and requires less thinking about:

for (std::size_t i = len; i--; ) std::cout << Array[i];
--
<mailto:di***********@yahoo.com> <http://www.dietmar-kuehl.de/>
<http://www.eai-systems.com> - Efficient Artificial Intelligence
Feb 8 '06 #3
"Dietmar Kuehl" <di***********@yahoo.com> wrote in message
news:44************@individual.net...
I think this should work. However, the more conventional version
is actually much less error prone and requires less thinking about:

for (std::size_t i = len; i--; ) std::cout << Array[i];


I don't think there's anything specifically wrong with this approach.
However, it doesn't translate to the iterator domain, because it generates
an off-the-beginning value. Doing so is OK for integers, but not for
iterators.

For that reason, I would be inclined to write the loop as follows:

{ std::size_t i = len; while (i) { --i; std::cout << Array[i]; }}

Yes it's a little longer, but (for example) you can translate it to

{ const Element* p = Array + len; while (p != Array) { --p; std::cout <<
*p; }}

without affecting its validity.
Feb 8 '06 #4
Personally, I would not accept your version in a code review.


That input is very helpful. I consider myself to be a very proficient
programmer, but I've never been employed as a programmer or had to submit a
project, so it's good to see where "The Boss" will reject my code.

Maybe I'm being naive here, but what if I replace my code with the
following:

#include <cstddef>
#include <iostream>

template<typename T, std::size_t len>
void PrintOutStringsBackwards( const char* const (&Array)[len] )
{
/************************************************** *
The array has "len" amount of items; therefore the
final item's index is "len - 1". This shall be
our starting value for "i".
************************************************** */

/************************************************** *
Before each iteration, the following expression is
tested to be true: "~i".
The expression will be false when "i" had the
value 0... but was then decremented. On all platforms
(for unsigned types), 0 decremented becomes 0xFF.
The invertor operator then inverts all bits, turning
0xFF into 0, which is false.
************************************************** */

for (std::size_t i = len - 1; ~i; --i) std::cout << Array[i];
}
Are the comments enough to make my boss accept the code?

How about if I do this:

inline
bool NotGoneBelowZero(std::size_t const i)
{
return ~i;
}

And then change the loop to:

for (std::size_t i = len - 1; NotGoneBelowZero(i); --i)
Why am I so anxious to use this code? I suppose I'm an efficiency freak. For
instance, you'll almost never see me use the postincrement/postdecrement
operators. Instead of:

k = j++;

I'll write:

k = j;
++j;

Because I think to myself, "Why invite the creation of an unnecessary
temporary"?

Another thing I wanted to try for efficiency reasons was to replace the
boolean OR operator with the bitwise OR operator in "if" statements:

char* a;
char* b;
....
....
if (a || b)
{

}
I created a thread here recently asking if it would be:
a) more efficient
b) advisable

to replace that with:

if ( a | b )
but sadly I never got a response.
-Tomás
Feb 8 '06 #5
"Andrew Koenig" <ar*@acm.org> wrote in message
news:cy****************@bgtnsc04-news.ops.worldnet.att.net...
: "Dietmar Kuehl" <di***********@yahoo.com> wrote in message
: news:44************@individual.net...
:
: > I think this should work. However, the more conventional version
: > is actually much less error prone and requires less thinking about:
: >
: > for (std::size_t i = len; i--; ) std::cout << Array[i];
:
: I don't think there's anything specifically wrong with this approach.
: However, it doesn't translate to the iterator domain, because it
generates
: an off-the-beginning value. Doing so is OK for integers, but not for
: iterators.

First of all, in the iterator case, maybe we should be using
reverse_iterator instead of iterating backwards ?
: For that reason, I would be inclined to write the loop as follows:
:
: { std::size_t i = len; while (i) { --i; std::cout << Array[i]; }}

Is it worth using an extra scope only to replace the <for> with a <while>
?

: Yes it's a little longer, but (for example) you can translate it to
:
: { const Element* p = Array + len; while (p != Array) { --p; std::cout
: << *p; }}
:
: without affecting its validity.

[ could use a for(;;) as well ]
I get that the point is to avoid decrementing the iterator past "begin".
But the translation seems non-trivial to me in any case,
and I am unsure of the overall readability.
In the iterator case, in practice, I tend to check for the
empty case upfront, then write:
const Element* p = Array+len;
do {
std::cout << *p;
} while( --p != Array );
When some kind of loop setup is required, this comes naturally
with index-based iterations as well.
Would anyone want to write:

for( const Element* p = Array+len ; p!=Array && (--p,true) ; )

?
Quite a particular idiom, but I would still prefer it to your example,
as it keeps all the iteration logic within the for statement...

Let's all hope for better for_each / functor support in C++0x,
as it seems pathetic to have so many ways to iterate backwards,
none appearing to be clearly superior.

Regards,
Ivan
--
http://ivan.vecerina.com/contact/?subject=NG_POST <- email contact form
Feb 8 '06 #6
Tomás wrote:
Personally, I would not accept your version in a code review.
That input is very helpful. I consider myself to be a very proficient
programmer, but I've never been employed as a programmer or had to submit a
project, so it's good to see where "The Boss" will reject my code.

Maybe I'm being naive here, but what if I replace my code with the
following:

#include <cstddef>
#include <iostream>

template<typename T, std::size_t len>
void PrintOutStringsBackwards( const char* const (&Array)[len] )
{
/************************************************** *
The array has "len" amount of items; therefore the
final item's index is "len - 1". This shall be
our starting value for "i".
************************************************** */

/************************************************** *
Before each iteration, the following expression is
tested to be true: "~i".
The expression will be false when "i" had the
value 0... but was then decremented. On all platforms
(for unsigned types), 0 decremented becomes 0xFF.
The invertor operator then inverts all bits, turning
0xFF into 0, which is false.
************************************************** */

for (std::size_t i = len - 1; ~i; --i) std::cout << Array[i];
}
Are the comments enough to make my boss accept the code?


I doubt it. Code should be self documenting for the most part. A
simple for loop should not require bit operations like that, or a
comment spanning over 10 lines of comment just for the loop variable.
How about if I do this:

inline
bool NotGoneBelowZero(std::size_t const i)
{
return ~i;
}

And then change the loop to:

for (std::size_t i = len - 1; NotGoneBelowZero(i); --i)
Although the function name is sort of descriptive, the mere presence of
the function is strange.

It's also misleading. -2 is below zero, and size_t i = -2 would not be
caught. If you were doing:
for (std::size_t i = len - 1; i -= 2
Why am I so anxious to use this code? I suppose I'm an efficiency freak. For
instance, you'll almost never see me use the postincrement/postdecrement
operators. Instead of:

k = j++;

I'll write:

k = j;
++j;

Because I think to myself, "Why invite the creation of an unnecessary
temporary"?
I'm sure the compiler will optimise these minutiae.
Another thing I wanted to try for efficiency reasons was to replace the
boolean OR operator with the bitwise OR operator in "if" statements:

char* a;
char* b;
...
...
if (a || b)
{

}
I created a thread here recently asking if it would be:
a) more efficient
b) advisable

to replace that with:

if ( a | b )
but sadly I never got a response.


My guess would be that it depends on the value of a.

I think your concerns about efficiency are vastly over estimated.

You'd do much better to concern yourself with architectural and
algorithmic problems then these which may or may not shave off a clock
cycle or two.

Personally, I think the following is really neat to solve your problem,
it's partly stolen from Dietmar, but I use reverse_copy instead of
reverse iterators, which are just odd:

#include <string>
#include <iostream>
#include <algorithm>

int main() {
int arr[4] = {0,1,2,3};

std::cout << "forwards" << "\n";
std::copy(arr, arr+4,
std::ostream_iterator<int>(std::cout, "\n"));

std::cout << "backwards" << "\n";
std::reverse_copy(arr, arr+4,
std::ostream_iterator<int>(std::cout, "\n"));
}
Ben Pope
--
I'm not just a number. To many, I'm known as a string...
Feb 8 '06 #7
Andrew Koenig wrote:
"Dietmar Kuehl" <di***********@yahoo.com> wrote:
for (std::size_t i = len; i--; ) std::cout << Array[i];


I don't think there's anything specifically wrong with this approach.
However, it doesn't translate to the iterator domain, because it generates
an off-the-beginning value.


However, typically the algorithms cannot be translated between integer
and iterator based versions easily anyway: when not using an iterator,
the integer is generally necessary for some reason, e.g. because the
relative offset between elements is important. As stated in my
article, I would use an algorithm or, although this was not stated,
an iterator based loop unless I specifically need the iterator. And
the obvious approach to traverse in the reverse direction is actually
to use the same algorithm like when traversing in the normal direction
but with reverse iterators.
--
<mailto:di***********@yahoo.com> <http://www.dietmar-kuehl.de/>
<http://www.eai-systems.com> - Efficient Artificial Intelligence
Feb 8 '06 #8
"Tomás" <NU**@NULL.NULL> wrote in message
news:mB******************@news.indigo.ie...
:
: > Personally, I would not accept your version in a code review.
:
: That input is very helpful. I consider myself to be a very proficient
: programmer, but I've never been employed as a programmer or had to
submit a
: project, so it's good to see where "The Boss" will reject my code.
:
: Maybe I'm being naive here, but what if I replace my code with the
: following:
....
: Are the comments enough to make my boss accept the code?
Still a reject.
Why use all the extra commenting when a concise alternative exists?

: How about if I do this:
:
: inline
: bool NotGoneBelowZero(std::size_t const i)
: {
: return ~i;
: }
:
: And then change the loop to:
:
: for (std::size_t i = len - 1; NotGoneBelowZero(i); --i)

Still a reject.
Using a custom function only adds to the confusion, for no benefit.

: Why am I so anxious to use this code? I suppose I'm an efficiency freak.

You may well find out that i--
is actually evaluated faster than ~--i !!

: For instance,
: you'll almost never see me use the postincrement/postdecrement
: operators. Instead of:
:
: k = j++;
:
: I'll write:
:
: k = j;
: ++j;

That makes sense for user-defiend types. Not for built-ins.
To get a most efficient bakwards loop, you would probably want
to use a decremented pointer rather than an index ( the p[i]
offset calculation is often relatively expensive).
But the best result is likely to depend on the specific compiler
that you are using.

: Another thing I wanted to try for efficiency reasons was to replace the
: boolean OR operator with the bitwise OR operator in "if" statements:
:
: char* a;
: char* b;
: ...
: ...
: if (a || b)
: {
:
: }
:
:
: I created a thread here recently asking if it would be:
: a) more efficient
: b) advisable
:
: to replace that with:
:
: if ( a | b )
:
: but sadly I never got a response.

My bet would be that the latter will rather slow things down.
But again, you'd have to measure this on a specific platform.
I would strongly recommend postponing creative and heroic
optimization efforts until you have a piece of code that
really needs a runtime speed boost.
Then investigate it seriously, and get advice (here or
elsewhere).

Or at least first study the current state of the art.
http://www.google.com/search?q=prema...zation+C%2B%2B
Cheers,
Ivan
--
http://ivan.vecerina.com/contact/?subject=NG_POST <- email contact form
Feb 8 '06 #9
Tomás wrote:
Maybe I'm being naive here, but what if I replace my code with the
following:

#include <cstddef>
#include <iostream>

template<typename T, std::size_t len>
void PrintOutStringsBackwards( const char* const (&Array)[len] )
{
/************************************************** *
The array has "len" amount of items; therefore the
final item's index is "len - 1". This shall be
our starting value for "i".
************************************************** */

/************************************************** *
Before each iteration, the following expression is
tested to be true: "~i".
The expression will be false when "i" had the
value 0... but was then decremented. On all platforms
(for unsigned types), 0 decremented becomes 0xFF.
The invertor operator then inverts all bits, turning
0xFF into 0, which is false.
************************************************** */

for (std::size_t i = len - 1; ~i; --i) std::cout << Array[i];
}
Are the comments enough to make my boss accept the code?
Personally, I would reject the code for the simple reason that a
trivial operation needs far too much explanation! I need *no*
comment at all for the simple loop

for (std::size_t i = len; i--; ) std::cout << Array[i];

and I would expect most professional programmers to gather the
intend and operation immediately (well, I know that many
professional programmers are not up to the task of understanding
this loop immediately but I consider this not a fault of the code
but the fault of the person who made them professional programmers
in the first place). As stated before, to make it plain what the
intend is, I would use

std::copy(Array.rbegin(), Array.rend(),
std::ostream_iteartor<char const*>(std::cout));

(this is a different version assuming that the sequence is
maintained by a container).
How about if I do this:

inline
bool NotGoneBelowZero(std::size_t const i)
{
return ~i;
}

And then change the loop to:

for (std::size_t i = len - 1; NotGoneBelowZero(i); --i)
This is, IMO, going from bad to worse: now I need to look-up even
another function to figure out what this trivial operation does!
Why am I so anxious to use this code? I suppose I'm an efficiency freak.
Then use the algorithms! The can do interesting optimizations you
might not even dream up.
Another thing I wanted to try for efficiency reasons was to replace the
boolean OR operator with the bitwise OR operator in "if" statements:

char* a;
char* b;
...
...
if (a || b)
{

}
I created a thread here recently asking if it would be:
a) more efficient
b) advisable

to replace that with:

if ( a | b )

but sadly I never got a response.


The semantics of 'a || b' and 'a | b' are different. For example,
'if (a == 0 || a->next == val)' can work while the result of changing
the expression to use the bitwise operator is rather likely to fail.
In general, it is almost always better to write the easy to read
code because this version tends to be correct and also tends to
survive maintenance correct. Tricky code is much easier to break.
Optimization is in order when profiling shows the need. Only where
equivalent approaches are obvious (e.g. using 'i++' or '++i' without
using the result of the expression) the typically more efficient
(i.e. '++i') should be used.

In addition, it always pays off to measure: if you are performance
freak, you would *not* use your "clever" approach as it is, at least
on my machine, consistently slower than the other approach:

kuehl@codegard:/tmp/c++> time tst 2
test2: 1000000000

real 0m11.128s
user 0m10.981s
sys 0m0.072s
kuehl@codegard:/tmp/c++> time tst 1
test1: 1000000000

real 0m11.489s
user 0m11.353s
sys 0m0.084s

.... and here is the source:

#include <vector>
#include <iostream>

enum { size = 10000000 };
enum { repeat = 100 };

void test1()
{
int array[size];
std::size_t count = 0;
for (std::size_t c = 0; c < repeat; ++c)
{
for (std::size_t i = size - 1; ~i; --i)
array[i] = 1;
for (std::size_t i = size - 1; ~i; --i)
count += array[i];
}
std::cout << "test1: " << count << "\n";
}

void test2()
{
int array[size];
std::size_t count = 0;
for (std::size_t c = 0; c < repeat; ++c)
{
for (std::size_t i = size; i--; )
array[i] = 1;
for (std::size_t i = size; i--; )
count += array[i];
}
std::cout << "test2: " << count << "\n";
}

int main(int ac, char* av[])
{
ac == 1 || av[1][0] == '1'? test1(): test2();
}
--
<mailto:di***********@yahoo.com> <http://www.dietmar-kuehl.de/>
<http://www.eai-systems.com> - Efficient Artificial Intelligence
Feb 8 '06 #10
On Wed, 08 Feb 2006 19:27:21 +0100, Dietmar Kuehl
<di***********@yahoo.com> wrote:

Wow. "Now that's what I'm talkin' about!" :-)

When I have understanding of computers I shall be the Supreme Being!
- Evil, Time Bandits :-)
Feb 11 '06 #11

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

35
by: Raymond Hettinger | last post by:
Here is a discussion draft of a potential PEP. The ideas grew out of the discussion on pep-284. Comments are invited. Dart throwing is optional. Raymond Hettinger ...
59
by: Raymond Hettinger | last post by:
Please comment on the new PEP for reverse iteration methods. Basically, the idea looks like this: for i in xrange(10).iter_backwards(): # 9,8,7,6,5,4,3,2,1,0 <do something with i> The...
31
by: Raymond Hettinger | last post by:
Based on your extensive feedback, PEP 322 has been completely revised. The response was strongly positive, but almost everyone preferred having a function instead of multiple object methods. The...
14
by: Raymond Hettinger | last post by:
Based on the feedback here on comp.lang.python, the pep has been updated: www.python.org/peps/pep-0322.html The key changes are: * reversed() is being preferred to ireverse() as the best...
4
by: John A Grandy | last post by:
Is there a performance difference between forward iteration and reverse iteration through a List<string? for ( i = 0; i < myList.Count; i++ ) { // do work, such as forward iterate through a...
0
by: taylorcarr | last post by:
A Canon printer is a smart device known for being advanced, efficient, and reliable. It is designed for home, office, and hybrid workspace use and can also be used for a variety of purposes. However,...
0
by: aa123db | last post by:
Variable and constants Use var or let for variables and const fror constants. Var foo ='bar'; Let foo ='bar';const baz ='bar'; Functions function $name$ ($parameters$) { } ...
0
by: ryjfgjl | last post by:
If we have dozens or hundreds of excel to import into the database, if we use the excel import function provided by database editors such as navicat, it will be extremely tedious and time-consuming...
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.