Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Where only a GOTO loop will work

0 views
Skip to first unread message

Ed Ferris

unread,
Nov 10, 2005, 12:19:11 AM11/10/05
to
If you think using GOTO is bad programming, tell me how to do this
better:

REM Delete all occurrences of X$ in A$
310 A = INSTR(A$, X$)
IF A = 0 THEN RETURN 'this is invoked by GOSUB
A$ = LEFT$(A$, A - 1) + MID$(A$, A + 1)
GOTO 310

If you use
DO WHILE INSTR(A$, X$)
you have use INSTR twice to get the char. position.

Buck Huffman

unread,
Nov 10, 2005, 3:39:17 AM11/10/05
to
Ed Ferris wrote:
> If you think using GOTO is bad programming, tell me how to do this
> better:

this eliminates the goto
310 ' Delete all occurences of x$ in a$
A = INSTR(A$, X$)
DO
A$ = LEFT$(A$, A-1) + MID$(A$, A+1)
A = INSTR(A$, X$)
LOOP UNTIL A = 0
RETURN

or this is even better
' Delete all occurences of x$ in a$
SUB Remove$(A$, X$)
A = INSTR(A$, X$)
DO
A$ = LEFT$(A$, A-1) + MID$(A$, A+1)
A = INSTR(A$, X$)
LOOP UNTIL A = 0
END SUB


called like this:

remove$ a$, x$

has the added advantage of eliminating gosub.

Buck Huffman

unread,
Nov 10, 2005, 4:10:35 AM11/10/05
to
sorry about that just change all occurances of remove$ to remove

Buck Huffman wrote:
> Ed Ferris wrote:
>
>> If you think using GOTO is bad programming, tell me how to do this
>> better:
>
>
> this eliminates the goto
> 310 ' Delete all occurences of x$ in a$
> A = INSTR(A$, X$)
> DO
> A$ = LEFT$(A$, A-1) + MID$(A$, A+1)
> A = INSTR(A$, X$)
> LOOP UNTIL A = 0
> RETURN
>
> or this is even better
> ' Delete all occurences of x$ in a$
> SUB Remove$(A$, X$)

^


> A = INSTR(A$, X$)
> DO
> A$ = LEFT$(A$, A-1) + MID$(A$, A+1)
> A = INSTR(A$, X$)
> LOOP UNTIL A = 0
> END SUB
>
>
> called like this:
>
> remove$ a$, x$

^


>
> has the added advantage of eliminating gosub.

next time i'll test before i post.

Michael Mattias

unread,
Nov 10, 2005, 8:16:16 AM11/10/05
to
"Ed Ferris" <no...@nowhere.com> wrote in message
news:Xns970A3459B...@216.168.3.44...

> If you think using GOTO is bad programming, tell me how to do this
> better:
>
> REM Delete all occurrences of X$ in A$
> 310 A = INSTR(A$, X$)
> IF A = 0 THEN RETURN 'this is invoked by GOSUB
> A$ = LEFT$(A$, A - 1) + MID$(A$, A + 1)
> GOTO 310

Mr. Huffman has shown you constructions avoiding GOTO.

BUT.....If you think using GOTO is bad, IMNSHO your conditional execution of
a RETURN is worse; and using a single-line IF to do so is worse-er!

MCM


Judson McClendon

unread,
Nov 10, 2005, 9:07:18 AM11/10/05
to


What if X$ is more than one character?

REM Delete all occurrences of X$ in A$

L = LEN(X$)
DO
A = INSTR(A$, X$)
IF (A > 0) THEN
A$ = LEFT$(A$, A - 1) + MID$(A$, A + L)
ELSE
EXIT DO
END IF
LOOP
RETURN

If you know that X$ will ALWAYS be 1 character, the following will probably
be more efficient if, X$ appears frequently in A$. This version minimizes
string space cleanup.

REM Delete all occurrences of X$ in A$

T$ = SPACE$(LEN(A$)) : B = 0
FOR A = 1 TO LEN (A$)
IF (MID$(A$,A,1) <> X$) THEN
B = B + 1
MID$(T$,B,1) = MID$(A$, A,1)
END IF
NEXT
A$ = LEFT$(T$,B)
RETURN

Structured programming requires THINKING in a structured way. Generally, If
you run into a situation where GOTO seems to be required, you probably got
there through non-structured thinking.
--
Judson McClendon ju...@sunvaley0.com (remove zero)
Sun Valley Systems http://sunvaley.com
"For God so loved the world that He gave His only begotten Son, that
whoever believes in Him should not perish but have everlasting life."


Judson McClendon

unread,
Nov 10, 2005, 9:18:08 AM11/10/05
to
The following version is a bit more efficient, because the INSTR doesn't
rescan the part of A$ that has been scanned already.

REM Delete all occurrences of X$ in A$

A = 1
L = LEN(X$)
DO
A = INSTR(A,A$, X$)


IF (A > 0) THEN
A$ = LEFT$(A$, A - 1) + MID$(A$, A + L)
ELSE
EXIT DO
END IF
LOOP
RETURN

--
Judson McClendon ju...@sunvaley0.com (remove zero)
Sun Valley Systems http://sunvaley.com
"For God so loved the world that He gave His only begotten Son, that
whoever believes in Him should not perish but have everlasting life."


"Judson McClendon" <ju...@sunvaley0.com> wrote in message
news:ksIcf.12308$Dk....@bignews5.bellsouth.net...

Tom Lake

unread,
Nov 10, 2005, 9:29:14 AM11/10/05
to
> REM Delete all occurrences of X$ in A$
> 310 A = INSTR(A$, X$)
> IF A = 0 THEN RETURN 'this is invoked by GOSUB
> A$ = LEFT$(A$, A - 1) + MID$(A$, A + 1)
> GOTO 310
>
> If you use
> DO WHILE INSTR(A$, X$)
> you have use INSTR twice to get the char. position.

Try this:

DO
A=INSTR(A$, X$)
IF A = 0 THEN EXIT DO


A$ = LEFT$(A$, A - 1) + MID$(A$, A + 1)

LOOP
RETURN

According to Kemeny and Kurtz (the creators of BASIC)
studies have shown that using a loop exit is easier for students
to understand than having to use convoluted logic to avoid a
middle exit.

Tom Lake


Michael Mattias

unread,
Nov 10, 2005, 9:56:04 AM11/10/05
to
"Judson McClendon" <ju...@sunvaley0.com> wrote in message
news:ksIcf.12308$Dk....@bignews5.bellsouth.net...
> Structured programming requires THINKING in a structured way.

Does this mean just plain old "programming" requires just plain old
"thinking?"

MCM

Judson McClendon

unread,
Nov 10, 2005, 10:40:28 AM11/10/05
to
"Michael Mattias" <michael...@gte.net> wrote:

> "Judson McClendon" <ju...@sunvaley0.com> wrote:
>> Structured programming requires THINKING in a structured way.
>
> Does this mean just plain old "programming" requires just plain old
> "thinking?"


Hi Michael. Only if you want your programs to work. :-)

Dan Barclay

unread,
Nov 10, 2005, 11:40:32 AM11/10/05
to

"Tom Lake" <tom_...@srmtenv.org> wrote in message
news:%23ayVhMg...@TK2MSFTNGP14.phx.gbl...

Good advice. It also applies to GoTo's used as loop exits (deeply nested
loops for example). GoTo isn't bad, but it can have some bad usage. Ditto
for GoSub.

Dan

Ethan Winer

unread,
Nov 10, 2005, 12:13:47 PM11/10/05
to
Ed,

> If you think using GOTO is bad programming <

I agree with Dan that GOTO is not bad, but it can be used badly. I love
assembly language, and there GOTO (Jmp) is the ONLY way to get around (aside
from Loop and Loopz).

> tell me how to do this better <

Here ya go:

SUB RemoveChar (Text$, Char$) STATIC
DO
Found = INSTR(Text$, Char$)
IF Found THEN Text$ = LEFT$(Text$, Found - 1) + MID$(Text$, Found + 1)
LOOP WHILE Found
END SUB

This could easily be adapted to remove a string of any length by changing
Found + 1 to LEN(Char$).

Note that this uses INSTR only once, and is a bit more elegant than some of
the other solutions because it uses LOOP WHILE rather than IF .. EXIT which
to my way of thinking is about the same as a GOTO. :->) Indeed, if you look
at the underlying compiled code for LOOP and DO and so forth, it's a rats
nest of GOTOs (Jmps). Even a simple FOR loop uses Jmp internally. Of course,
when a compiler writes a GOTO it's okay. Sort of like, "Don't try this at
home, kids."

--Ethan


Judson McClendon

unread,
Nov 10, 2005, 1:27:25 PM11/10/05
to


As usual, Ethan, I like your code; clear and concise. The reason I used EXIT
DO is because it saved a second test for found (e.g. "IF Found" and "WHILE
Found"). I started programming just after the last Ice Age, when bytes and
cycles were precious, and I still code to conserve them, almost without
thinking. I also avoid one line IF statements, in order to maintain
consistent form. :-)

Stephen Howe

unread,
Nov 10, 2005, 1:42:57 PM11/10/05
to
> If you think using GOTO is bad programming...

Ed Dikstra - I don't agree with him. I will use GOTO if other choices appear
worse
But as a whole if I can write without GOTO, it looks good, I will.

> REM Delete all occurrences of X$ in A$
> 310 A = INSTR(A$, X$)
> IF A = 0 THEN RETURN 'this is invoked by GOSUB
> A$ = LEFT$(A$, A - 1) + MID$(A$, A + 1)
> GOTO 310

How about (making sure the index is an integer for max performance,
restarting looking where left off)

A% = 1
DO
A% = INSTR(A%, A$, X$)
IF A% = 0 THEN EXIT DO
A$ = LEFT$(A$, A% - 1) + MID$(A$, A% + 1)
LOOP
RETURN

>If you use
>DO WHILE INSTR(A$, X$)
>you have use INSTR twice to get the char. position.

Well observed. I don't like that either.
The most general form of loop is

STARTLOOP
Block A of Code
LOOP exit test condition
Block B of Code
ENDLOOP

If Block A of Code does not exist then you have BASIC's
DO WHILE condition
LOOP

If Block B of Code does not exist then you have BASIC's
DO
LOOP WHILE condition

But what if Block A & Block B both exist and you wish to terminate the loop
in the middle?
Then in BASIC I do an endless loop with an IF test in the middle
So

DO
'Block A of Code
IF exitcondition THEN EXIT DO
'Block B of Code
LOOP

which avoids having to repeat code as you observed

Stephen Howe


Ethan Winer

unread,
Nov 10, 2005, 1:56:43 PM11/10/05
to
Judson,

> The reason I used EXIT DO is because it saved a second test for found <

Agreed, though an integer test and branch is only a couple of bytes.

> I started programming just after the last Ice Age, when bytes and cycles
were precious, and I still code to conserve them <

Me too. Even in this age of 1 GB of RAM and 200 GB hard drives, I still
write QB and ASM code with an eye toward the lowest byte count and fastest
execution.

--Ethan


Tom Lake

unread,
Nov 10, 2005, 2:29:16 PM11/10/05
to
> Here ya go:
>
> SUB RemoveChar (Text$, Char$) STATIC
> DO
> Found = INSTR(Text$, Char$)
> IF Found THEN Text$ = LEFT$(Text$, Found - 1) + MID$(Text$, Found + 1)
> LOOP WHILE Found
> END SUB
>
> This could easily be adapted to remove a string of any length by changing
> Found + 1 to LEN(Char$).
>
> Note that this uses INSTR only once

but now you're testing Found twice. Once in the IF and once at the end of
the loop in the WHILE.

Tom Lake


Stephen Howe

unread,
Nov 10, 2005, 2:40:59 PM11/10/05
to
> Me too. Even in this age of 1 GB of RAM and 200 GB hard drives, I still
> write QB and ASM code with an eye toward the lowest byte count and fastest
> execution.

Yup same here.
I was dismayed recently on looking up at how quoted strings were implemented
for Far Strings.
The compiler basically builds an artificial string descriptor just so that

PRINT "Hello World"

works the same as

a$ = "Hello World"
PRINT a$

But the artificial string descriptor costs memory, DGROUP memory

That is not what I would have done in the compiler.
I would have generated a call to a different function that takes a near/far
pointer to the quoted string and not generated the artificial string
descriptor. Saves memory and is faster. It may make the RTL fractionally
bigger (but I dont think so as the BASIC functions that works with string
descriptors - they still have to find the location of the string in memory.
That point is the location point for quoted strings).

Stephen Howe


Tom Lake

unread,
Nov 10, 2005, 3:00:43 PM11/10/05
to

You should switch compilers! PowerBasic doesn't have those problems.

Tom Lake


Ed Ferris

unread,
Nov 10, 2005, 3:18:46 PM11/10/05
to
"Ethan Winer" <ethanw at ethanwiner dot com> wrote in
news:#4TEAph5...@TK2MSFTNGP15.phx.gbl:

> SUB RemoveChar (Text$, Char$) STATIC
> DO
> Found = INSTR(Text$, Char$)
> IF Found THEN Text$ = LEFT$(Text$, Found - 1) + MID$(Text$,
Found
> + 1)
> LOOP WHILE Found
> END SUB

I like this version best, maybe because I didn't even know there was
an
EXIT DO statement. You can GOTO the next statement after the loop,
instead, I suppose; that involves giving it a line number (or label)
but
the EXIT DO has to find the end of the loop, so the interpreter
searches
for it anyway. IF ... THEN RETURN seems clear and logical to me.
Thanks for the interesting comments!

Dan Barclay

unread,
Nov 10, 2005, 3:28:13 PM11/10/05
to

"Stephen Howe" <stephenPOINThoweATtns-globalPOINTcom> wrote in message
news:Ox8Yu6i5...@TK2MSFTNGP14.phx.gbl...

>> Me too. Even in this age of 1 GB of RAM and 200 GB hard drives, I still
>> write QB and ASM code with an eye toward the lowest byte count and
>> fastest
>> execution.
>
> Yup same here.
> I was dismayed recently on looking up at how quoted strings were
> implemented
> for Far Strings.
> The compiler basically builds an artificial string descriptor just so that
>
> PRINT "Hello World"
>
> works the same as
>
> a$ = "Hello World"
> PRINT a$
>
> But the artificial string descriptor costs memory, DGROUP memory

There is more uglyness in there as well. It's been a long time so I don't
remember if this was in the VBDOS or PDS compiler (or both) but there was a
severe optimization bug with concantenation in this process of creating
hidden variables.

MyStr1$ = MyStr2$ + SomeFunction$(...) ' function can be yours Basic's
like Left$()

vs

MyStr3$ = SomeFunction$(...)
MyStr1$= MyStr2$ + MyStr3$

Which do you think runs better? Try 'em in a loop to time it <vbg>.

(I hope I'm not misremembering some detail of that)

> That is not what I would have done in the compiler.
> I would have generated a call to a different function that takes a
> near/far
> pointer to the quoted string and not generated the artificial string
> descriptor. Saves memory and is faster. It may make the RTL fractionally
> bigger (but I dont think so as the BASIC functions that works with string
> descriptors - they still have to find the location of the string in
> memory.
> That point is the location point for quoted strings).

It is what you do in a compiler (lib actually) when you already have
FunctionSetA and are in a time crunch. You stitch together stuff that at
least works using hacks to the existing function set and say "I'll optimize
it later" then never do <g>.

Dan


Tom Lake

unread,
Nov 10, 2005, 3:34:23 PM11/10/05
to

> I like this version best, maybe because I didn't even know there was
> an
> EXIT DO statement. You can GOTO the next statement after the loop,
> instead, I suppose; that involves giving it a line number (or label)
> but
> the EXIT DO has to find the end of the loop, so the interpreter
> searches
> for it anyway.

Even in an interpreter, the search only happens once and then the jump
address is replaced by the actual address of the statement following the end
of the loop. In a compiler, the address is resolved at compile time so
doesn't affect run times.

Tom Lake


Michael Mattias

unread,
Nov 10, 2005, 3:45:04 PM11/10/05
to
"Judson McClendon" <ju...@sunvaley0.com> wrote in message
news:cgMcf.12525$Dk....@bignews5.bellsouth.net...

> The reason I used EXIT DO is because it saved a second test for found
(e.g. "IF Found" and "WHILE
> Found").

SUB RemoveChar (Text$, Char$) STATIC
Found = INSTR(Text$, Char$)
WHILE Found


Text$ = LEFT$(Text$, Found - 1) + MID$(Text$, Found + 1)

Found = INSTR(Text$, Char$)
WEND

END SUB

MCM


Judson McClendon

unread,
Nov 10, 2005, 4:51:02 PM11/10/05
to
"Michael Mattias" <michael...@gte.net> wrote:
> "Judson McClendon" <ju...@sunvaley0.com> wrote:
>> The reason I used EXIT DO is because it saved a second test for found
> (e.g. "IF Found" and "WHILE
>> Found").
>
> SUB RemoveChar (Text$, Char$) STATIC
> Found = INSTR(Text$, Char$)
> WHILE Found
> Text$ = LEFT$(Text$, Found - 1) + MID$(Text$, Found + 1)
> Found = INSTR(Text$, Char$)
> WEND
>
> END SUB

That's also clear and concise, Michael, and is a classic way to set up a
loop. But now you have two calls to INSTR. :-)

ararghmai...@now.at.arargh.com

unread,
Nov 10, 2005, 5:07:53 PM11/10/05
to
On Thu, 10 Nov 2005 14:28:13 -0600, "Dan Barclay" <D...@MVPs.org>
wrote:

<snip>


>There is more uglyness in there as well. It's been a long time so I don't
>remember if this was in the VBDOS or PDS compiler (or both) but there was a
>severe optimization bug with concantenation in this process of creating
>hidden variables.
>
> MyStr1$ = MyStr2$ + SomeFunction$(...) ' function can be yours Basic's
>like Left$()
>
>vs
>
> MyStr3$ = SomeFunction$(...)
> MyStr1$= MyStr2$ + MyStr3$
>
>Which do you think runs better? Try 'em in a loop to time it <vbg>.

IIRC, the first should generate 1 less call.

PDS ALWAYS stores the return of a user function to a local variable,
weather there is any reasion to do so.

>
>(I hope I'm not misremembering some detail of that)
>
>> That is not what I would have done in the compiler.
>> I would have generated a call to a different function that takes a
>> near/far
>> pointer to the quoted string and not generated the artificial string
>> descriptor. Saves memory and is faster. It may make the RTL fractionally
>> bigger (but I dont think so as the BASIC functions that works with string
>> descriptors - they still have to find the location of the string in
>> memory.
>> That point is the location point for quoted strings).
>
>It is what you do in a compiler (lib actually) when you already have
>FunctionSetA and are in a time crunch. You stitch together stuff that at
>least works using hacks to the existing function set and say "I'll optimize
>it later" then never do <g>.
>
>Dan
>

--
ArarghMail511 at [drop the 'http://www.' from ->] http://www.arargh.com
BCET Basic Compiler Page: http://www.arargh.com/basic/index.html

To reply by email, remove the garbage from the reply address.

Tom Lake

unread,
Nov 10, 2005, 5:11:35 PM11/10/05
to
> Ed Dikstra - I don't agree with him. I will use GOTO if other choices
> appear
> worse
> But as a whole if I can write without GOTO, it looks good, I will.
>
>> REM Delete all occurrences of X$ in A$
>> 310 A = INSTR(A$, X$)
>> IF A = 0 THEN RETURN 'this is invoked by GOSUB
>> A$ = LEFT$(A$, A - 1) + MID$(A$, A + 1)
>> GOTO 310
>
> How about (making sure the index is an integer for max performance,
> restarting looking where left off)
>
> A% = 1
> DO
> A% = INSTR(A%, A$, X$)
> IF A% = 0 THEN EXIT DO
> A$ = LEFT$(A$, A% - 1) + MID$(A$, A% + 1)
> LOOP
> RETURN

Right. That's what I suggested (except for the integers)
but the A%=1 before the loop is unnecessary since the first
statement of the loop will wipe it out with a new A% value anyway.

Tom Lake


ararghmai...@now.at.arargh.com

unread,
Nov 10, 2005, 5:18:25 PM11/10/05
to
On Thu, 10 Nov 2005 14:28:13 -0600, "Dan Barclay" <D...@MVPs.org>
wrote:

Pushed the send button too soon.

<snip>


>There is more uglyness in there as well. It's been a long time so I don't
>remember if this was in the VBDOS or PDS compiler (or both) but there was a
>severe optimization bug with concantenation in this process of creating
>hidden variables.
>
> MyStr1$ = MyStr2$ + SomeFunction$(...) ' function can be yours Basic's
>like Left$()
>
>vs
>
> MyStr3$ = SomeFunction$(...)
> MyStr1$= MyStr2$ + MyStr3$
>
>Which do you think runs better? Try 'em in a loop to time it <vbg>.
>
>(I hope I'm not misremembering some detail of that)

--

ararghmai...@now.at.arargh.com

unread,
Nov 10, 2005, 5:36:48 PM11/10/05
to
On Thu, 10 Nov 2005 14:28:13 -0600, "Dan Barclay" <D...@MVPs.org>
wrote:

)(^&*^_(), did it again.

>There is more uglyness in there as well. It's been a long time so I don't
>remember if this was in the VBDOS or PDS compiler (or both) but there was a
>severe optimization bug with concantenation in this process of creating
>hidden variables.
>
> MyStr1$ = MyStr2$ + SomeFunction$(...) ' function can be yours Basic's
>like Left$()
>
>vs
>
> MyStr3$ = SomeFunction$(...)
> MyStr1$= MyStr2$ + MyStr3$
>
>Which do you think runs better? Try 'em in a loop to time it <vbg>.
>
>(I hope I'm not misremembering some detail of that)

<snip>

PDS and VBDOS generate exactly the same code except for the names. PDS
list included:
<a lot of some snips for space>

0030 0006 z$ = "..."
0030 ** I00002: push offset <const>
0033 ** push offset Z$
0036 ** call B$SASS

003B 000A MyStr1$ = MyStr2$ + SomeFunction$(z$) ' function
003B ** L00002: push offset Z$
003E ** call SOMEFUNCTION$
0043 ** push ax
0044 ** push offset t000A$
0047 ** call B$SASS
004C ** push offset MYSTR1$
004F ** push offset MYSTR2$
0052 ** push offset t000A$
0055 ** call B$SACT


005A 0016 MyStr3$ = SomeFunction$(z$)
005A ** L00004: push offset Z$
005D ** call SOMEFUNCTION$
0062 ** push ax
0063 ** push offset t0016$
0066 ** call B$SASS
006B ** push offset t0016$
006E ** push offset MYSTR3$
0071 ** call B$SASS

005A 0016 MyStr1$ = MyStr2$ + MyStr3$
0076 ** push offset MYSTR1$
0079 ** push offset MYSTR2$
007C ** push offset MYSTR3$
007F ** call B$SACT

<function code snipped>

IRC, the first does generate one less call.


BCET version:
<heavly snipped for space>

; ===> z$ = "..."

push Offset String_0001
push Offset z_$_v_0
call j$StringAssign@8


; ===> MyStr1$ = MyStr2$ + SomeFunction$(z$) ' function can be yours

push Offset z_$_v_0
call SomeFunction
push Offset MyStr2_$_v_0
push eax
push Offset MyStr1_$_v_0
call j$StringConcatAssign@12


; ===> MyStr3$ = SomeFunction$(z$)

push Offset z_$_v_0
call SomeFunction
push eax
push Offset MyStr3_$_v_0
call j$StringAssign@8

; ===> MyStr1$ = MyStr2$ + MyStr3$

push Offset MyStr2_$_v_0
push Offset MyStr3_$_v_0
push Offset MyStr1_$_v_0
call j$StringConcatAssign@12

<rest snipped>

Since I use string routines a lot, I added some extra optimizations
for string handling. They become useful only for things like:

a$ = b$ + c$ + d$ + e$

which only generates one library call, instead of three.

Michael Mattias

unread,
Nov 10, 2005, 8:13:50 PM11/10/05
to
"Judson McClendon" <ju...@sunvaley0.com> wrote in message
news:6fPcf.12618$Dk....@bignews5.bellsouth.net...

> "Michael Mattias" <michael...@gte.net> wrote:
> > "Judson McClendon" <ju...@sunvaley0.com> wrote:
> >> The reason I used EXIT DO is because it saved a second test for found
> > (e.g. "IF Found" and "WHILE
> >> Found").
> >
> > SUB RemoveChar (Text$, Char$) STATIC
> > Found = INSTR(Text$, Char$)
> > WHILE Found
> > Text$ = LEFT$(Text$, Found - 1) + MID$(Text$, Found + 1)
> > Found = INSTR(Text$, Char$)
> > WEND
> >
> > END SUB
>
> That's also clear and concise, Michael, and is a classic way to set up a
> loop. But now you have two calls to INSTR. :-)

No, I dont... As soon as INSTR returns zero, the WHILE ..WEND (home of the
'second' INSTR) does not execute.

This does exactly as many INSTR as required, no more.

So what it *appears* in the SOURCE code twice? Source code does not execute!

MCM

Derek

unread,
Nov 10, 2005, 11:13:20 PM11/10/05
to
Oh, well everybody else has weighed in. I suppose I might as well have
a "go too", <grim>.

Of course, if you <i>really</i> want to use GOSUB, and you don't mind
cryptic coding, this is slightly better

299 REM Delete all occurrences of the character X$ in the string A$

300 LET A$ = LEFT$(A$, A% - 1) + MID$(A$, A% + 1)
310 LET A% = INSTR(A$, X$): IF A% THEN 300
330 RETURN 'This routine must be invoked by GOSUB 310 -- not GOSUB
300

because it combines the IF and the GOTO from the original code into one
statement thus reducing the inner loop from four statements to three.

By the way, it's perfectly possible to program in a structured fashion
using nothing but IF statements and GOTO statements. So I don't think
"Bad programming!" just because I see a GOTO statement. It's just that
we need to be taught how to avoid bad programming when using GOTOs
whereas when using DO...LOOP and IF...THEN.ELSE...END IF bad
programming becomes more difficult -- although, sadly, not impossible.

Cheers

Derek

Derek

unread,
Nov 10, 2005, 11:23:41 PM11/10/05
to
Naah, that takes all the fun out of it. Live on the edge! Post without
testing!

Cheers

Derek

Dan Barclay

unread,
Nov 10, 2005, 11:46:37 PM11/10/05
to
Yes, I'm aware that it creates a hidden variable and should result in
identical code. So, you'd think the performance in a loop would be equal,
right?

In the case I found, they did not. As I recall, one used a single
concant/assign call on the concantenation and the other handled the
assignment and concant separately.

I don't have a DOS compiler on this machine at home so it'll be Monday
before I can look at it.

I could be missing the details of the trigger of this situation, but the
code creating the *hidden* temp variable was several X slower than the one
with the explicit variable. I could also be wrong about which compiler it
was, but I think I remember it being VBDOS. There definitely was a
performance difference, and I went through several "hot sections" of code to
put in the explicit temp just for that bug... and it worked<g>.

If I can find the old test code on this I'll update you on it. Maybe it was
function first, but I don't think so ( as in: a$=fcn$()+b$)? I'll have to
see if I can find or reproduce the test code. It's surprising that they let
this one through, but this one was definitely a big drag on performance.

Dan


<ararghmai...@NOW.AT.arargh.com> wrote in message
news:sph7n1hvs9k16dmtc...@4ax.com...

Dan Barclay

unread,
Nov 10, 2005, 11:49:57 PM11/10/05
to

"Derek" <dere...@yahoo.ca> wrote in message
news:1131682400....@f14g2000cwb.googlegroups.com...

>
> By the way, it's perfectly possible to program in a structured fashion
> using nothing but IF statements and GOTO statements.

Correct, structure is an attitude.

> So I don't think
> "Bad programming!" just because I see a GOTO statement. It's just that
> we need to be taught how to avoid bad programming when using GOTOs
> whereas when using DO...LOOP and IF...THEN.ELSE...END IF bad
> programming becomes more difficult -- although, sadly, not impossible.

Yup, I've seen really bad stuff using all sorts of tricks and tools.

Dan


Todd Vargo

unread,
Nov 11, 2005, 12:08:57 AM11/11/05
to

"Tom Lake" <tl...@twcny.rr.com> wrote in message
news:rAPcf.146246$7b6....@twister.nyroc.rr.com...

For the code above,


A% = INSTR(A%, A$, X$)

should be
A% = INSTR(A% + 1, A$, X$)
otherwise if A% is zero (as is usually the case in a SUB), the INSTR
function will throw an 'illegal function call'.

Also, as previously mentioned, the code above does not consider that X$ can
be greater than a single character. The following code does.

Sub RemoveChar (A$, X$)
LenX% = Len(X$)
Do
Found% = INSTR(Found% + 1, A$, X$)
If Found% = 0 Then Exit Do
A$ = LEFT$(A$, Found% - 1) + MID$(A$, Found% + LenX%)
Loop
End Sub

This makes single pass replacements as all(AFAIK) find/replace functions do.
Suppose we have the string, "aabcbc" and the string to remove is "abc". That
leaves the first "a" and last "bc". Additional passes would be necessary (as
should be) to remove the newly formed "abc". If desired, the start position
can be omitted from INSTR to force replacement of any newly formed X$
characters.

--
Todd Vargo (double "L" to reply by email)

ararghmai...@now.at.arargh.com

unread,
Nov 11, 2005, 3:48:03 AM11/11/05
to
On Thu, 10 Nov 2005 22:46:37 -0600, "Dan Barclay" <D...@MVPs.org>
wrote:

>Yes, I'm aware that it creates a hidden variable and should result in

>identical code. So, you'd think the performance in a loop would be equal,
>right?

I have no idea. The compiler (PDS 7.1 vs VBDOS 1.0) generated code is
exactly the same except for the variable names. However, it could be
a near-string / far-string issue, and/or there could be major
differences between PDS and VBDOS runtime code. I never bothered to
check. The only major difference I remember between them is the
handling of string constants. In PDS, they are in DGROUP, and IIRC,
in VBDOS, they live in their own far data segment.

>In the case I found, they did not. As I recall, one used a single
>concant/assign call on the concantenation and the other handled the
>assignment and concant separately.
>
>I don't have a DOS compiler on this machine at home so it'll be Monday
>before I can look at it.
>
>I could be missing the details of the trigger of this situation, but the
>code creating the *hidden* temp variable was several X slower than the one
>with the explicit variable. I could also be wrong about which compiler it
>was, but I think I remember it being VBDOS. There definitely was a
>performance difference, and I went through several "hot sections" of code to
>put in the explicit temp just for that bug... and it worked<g>.
>
>If I can find the old test code on this I'll update you on it.

It would be interesting.

>Maybe it was function first, but I don't think so ( as in: a$=fcn$()+b$)?

Doesn't appear to make any difference.

>I'll have to
>see if I can find or reproduce the test code. It's surprising that they let
>this one through, but this one was definitely a big drag on performance.

One of many. :-) I shot a really neat one in Ver 6.0. Don't remember
the details now, except that it was before MS started making you pay
to report a bug.

ararghmai...@now.at.arargh.com

unread,
Nov 11, 2005, 3:56:59 AM11/11/05
to
On Thu, 10 Nov 2005 05:19:11 -0000, Ed Ferris <no...@nowhere.com>
wrote:

>If you think using GOTO is bad programming, tell me how to do this
>better:


>
>REM Delete all occurrences of X$ in A$
>310 A = INSTR(A$, X$)
>IF A = 0 THEN RETURN 'this is invoked by GOSUB
>A$ = LEFT$(A$, A - 1) + MID$(A$, A + 1)
>GOTO 310
>

>If you use
>DO WHILE INSTR(A$, X$)
>you have use INSTR twice to get the char. position.

Hmmm. For the case where X$ is a single character, I tend to use:

ReplaceChar A$, ASC(X$), 32

which changes all occurances of the first character to the second in
the string. ReplaceChar is an asm routine that I orignally wrote in
1992.

Derek

unread,
Nov 11, 2005, 4:23:11 AM11/11/05
to
Well we've had a bunch of replies now and having nothing better to do
this evening, I've benchmarked them all. I just used the replies which
dealt with single character X$ to make the results more comparable
though. Results are as follows:

Code Time Comments
---- ---- --------
An empty loop 0 Calibrate to cut FOR/NEXT from timing
Ed's Solution 3.959961 The original
Buck's 1st solution 3.679688 Fails when X$ does not appear in A$
Buck's 2nd solution 3.799805 Also fails when X$ does not appear in
A$
Judson's 1st solution 4.009766
Judson's 2nd solution 27.0293 Slower than expected
Judson's 3rd solution 4.509766
Tom's solution 3.849609
Ethan's solution 4.449707
Stephen's solution 1.109863 Integer variables make a BIG
difference
Michael's solution 3.72998 Best of the GOTO-free non-integer
solutions
Derek's 1st solution 1.049316 Reducing the number of statements
helps too
Derek's 2nd solution 1.109863 Michael's solution but with integers

The big lesson seems to be that integer arithmetic makes more
difference than anything else but I don't suppose that that comes as a
surprise to anyone. The second lesson is that I had to run 100,000 or
more iterations to get measurable results. Modern PC's running QBASIC
1.1 make it look very fast. So in practice any of the solutions would
have been almost indistinguishable for less than 100,000 calls. That
being so readability beomes more important and one of the structured
solutions is probably best overall.

Cheers

Derek

PS. I'll post my benchmarking framework if anyone really wants to see
it but it's nothing special. Just For/Next, Timer and Print gluing
together the code that has already been posted.

ararghmai...@now.at.arargh.com

unread,
Nov 11, 2005, 5:21:57 AM11/11/05
to
On 11 Nov 2005 01:23:11 -0800, "Derek" <dere...@yahoo.ca> wrote:

<snip>


>The big lesson seems to be that integer arithmetic makes more
>difference than anything else but I don't suppose that that comes as a
>surprise to anyone.

The very reason I almost always start every program with: DEFINT A-Z
Otherwise, MS 16-bit Basics' default to Single. (YUCK)

<snip>

Tom Lake

unread,
Nov 11, 2005, 6:56:16 AM11/11/05
to

"Derek" <dere...@yahoo.ca> wrote in message
news:1131700991.4...@g43g2000cwa.googlegroups.com...

You don't specify your inputs so we can't reproduce your results exactly.
How many replacements are done in each complete loop? I use two
replacements
in my example below (two "a"s replaced in a complete loop)
Just to show that a different dialect can make a big difference, I
benchmarked the
program as ported to TrueBASIC. There's a significant difference especially
since
TrueBASIC uses IEEE double precision for all numerics!

The following program in TrueBASIC gives 0.34 ~0.35 seconds for 100000
iterations
on a 3.06GHz P4:

LET x$="a"
LET t=time
FOR i=1 to 100000
LET a$="Thomas R. Lake"
DO
LET A = pos(A$, X$)
IF A = 0 THEN EXIT DO
LET A$ = A$[1:A-1] & A$[A+1:maxnum]
LOOP
NEXT i
PRINT (time-t)
PRINT a$
END

Tom Lake


Tom Lake

unread,
Nov 11, 2005, 7:03:09 AM11/11/05
to
> Code Time Comments
> ---- ---- --------
> An empty loop 0 Calibrate to cut FOR/NEXT from timing
> Ed's Solution 3.959961 The original
> Buck's 1st solution 3.679688 Fails when X$ does not appear in A$
> Buck's 2nd solution 3.799805 Also fails when X$ does not appear in
> A$
> Judson's 1st solution 4.009766
> Judson's 2nd solution 27.0293 Slower than expected

Are you sure about Judson's 2nd solution timing? That seems way out of
line!

Tom Lake


Michael Mattias

unread,
Nov 11, 2005, 7:47:14 AM11/11/05
to
<ararghmai...@NOW.AT.arargh.com> wrote in message
news:ttr8n1lat4gaebib1...@4ax.com...

> On 11 Nov 2005 01:23:11 -0800, "Derek" <dere...@yahoo.ca> wrote:
>
> <snip>
> >The big lesson seems to be that integer arithmetic makes more
> >difference than anything else but I don't suppose that that comes as a
> >surprise to anyone.

> The very reason I almost always start every program with: DEFINT A-Z

Didn't every MS-DOS BASIC programmer?

That's like having a cup of coffee when you get out of bed... automatic!


MCM

Stephen Howe

unread,
Nov 11, 2005, 8:45:19 AM11/11/05
to
> Yes, I'm aware that it creates a hidden variable and should result in
> identical code. So, you'd think the performance in a loop would be equal,
> right?
>
> In the case I found, they did not. As I recall, one used a single
> concant/assign call on the concantenation and the other handled the
> assignment and concant separately.

It is also compounded by the /s issue which has some pluses, some minuses
(Ethan has detailed this)

Stephen


Stephen Howe

unread,
Nov 11, 2005, 8:52:34 AM11/11/05
to
> > A% = 1
> > DO
> > A% = INSTR(A%, A$, X$)
> > IF A% = 0 THEN EXIT DO
> > A$ = LEFT$(A$, A% - 1) + MID$(A$, A% + 1)
> > LOOP
> > RETURN
>
> Right. That's what I suggested (except for the integers)
> but the A%=1 before the loop is unnecessary since the first
> statement of the loop will wipe it out with a new A% value anyway.

It is crucial.
Forget the "new A%" and think about the "old A%" that goes into INSTR. If it
starts out as 0, 3-arg form of INSTR will raise illegal function call.

Stephen Howe


Stephen Howe

unread,
Nov 11, 2005, 8:58:10 AM11/11/05
to
> For the code above,
> A% = INSTR(A%, A$, X$)
> should be
> A% = INSTR(A% + 1, A$, X$)
> otherwise if A% is zero (as is usually the case in a SUB), the INSTR
> function will throw an 'illegal function call'.

Correct observation but wrong code. What I had was correct.
The code you presented above does not handle repeated characters X$ that are
moved into the same position after the string is altered. You are moving 1
beyond each time.

> Also, as previously mentioned, the code above does not consider that X$
can
> be greater than a single character.

Well yes that is true. But if you look at the original posters code sample,
it is clear from the LEFT$ and MID$, it is clear that the code is a function
for removing a _single_ character from a string, not a string from a string.
The code I presented reflects that.

Cheers

Stephen Howe


Judson McClendon

unread,
Nov 11, 2005, 10:20:16 AM11/11/05
to
"Michael Mattias" <michael...@gte.net> wrote:
> "Judson McClendon" <ju...@sunvaley0.com> wrote:
>> "Michael Mattias" <michael...@gte.net> wrote:
>> > "Judson McClendon" <ju...@sunvaley0.com> wrote:
>> >> The reason I used EXIT DO is because it saved a second test for found
>> > (e.g. "IF Found" and "WHILE
>> >> Found").
>> >
>> > SUB RemoveChar (Text$, Char$) STATIC
>> > Found = INSTR(Text$, Char$)
>> > WHILE Found
>> > Text$ = LEFT$(Text$, Found - 1) + MID$(Text$, Found + 1)
>> > Found = INSTR(Text$, Char$)
>> > WEND
>> >
>> > END SUB
>>
>> That's also clear and concise, Michael, and is a classic way to set up a
>> loop. But now you have two calls to INSTR. :-)
>
> No, I dont... As soon as INSTR returns zero, the WHILE ..WEND (home of the
> 'second' INSTR) does not execute.
>
> This does exactly as many INSTR as required, no more.

Time-wise, yes. Memory-wise, no.

> So what it *appears* in the SOURCE code twice? Source code does not
> execute!

Yes, but source code compiles, and compiled code = bytes. ;-)

Sure, I'm just being picky. :-) But seriously, I think we would agree that
far too many programmers today are oblivious about efficiency. Any version
of Windows or Microsoft Office being superb examples: huge and slow (and
buggy!).

In counterpoint, mainframe OSs, supporting thousands of simultaneous users,
plus dozens of programmers debugging code, run for many months, even years,
without crashing. I can't remember the last time one of my mainframe (Unisys
A Series) clients had a system crash. It's been several years, at least.
They reboot only when updating the operating system or for serious hardware
problems, and those are very rare. Mainframes make the best servers on the
planet, extremely reliable and secure. Surprising more people don't realize
that.

Dan Barclay

unread,
Nov 11, 2005, 10:55:16 AM11/11/05
to

"Michael Mattias" <michael...@gte.net> wrote in message
news:mp0df.6646$8W....@newssvr30.news.prodigy.com...

Heh... unless they started their programming life on FORTRAN like some of us
did <vbg>.

Dan

Michael Mattias

unread,
Nov 11, 2005, 11:05:49 AM11/11/05
to
"Dan Barclay" <D...@MVPs.org> wrote in message
news:#ZSaRht5...@TK2MSFTNGP09.phx.gbl...


Hey, my first program was written in FORTRAN. FORTRAN II, if you must know.
1967. Punch cards.

I didn't pick up BASIC until recently... maybe 1981, 1982 or so.....

MCM
(sheesh, am I an old fart or what?)

Stephen Howe

unread,
Nov 11, 2005, 11:23:46 AM11/11/05
to

> Heh... unless they started their programming life on FORTRAN like some of
us
> did <vbg>.
>
> Dan

I cut my teeth on FORTRAN in 1981 (under Multics OS) at University.
Microsoft BASIC 5.36 in 1985 for a Sirius computer (pre-IBM) running MS DOS
1.0 (the non-IBM version that Microsfot marketed). Later I saw QuickBasic
1.0 then 3.0, 4.0, 4.1 in quick succession at another company as well as
GW-BASIC/IBM BASICA

If you still love Fortran you can download the OpenWatcom FORTRAN compiler
for free.
Compiles 16-bit & 32-bit DOS, 16-bit & 32-bit Windows, 16-bit & 32-bit OS/2,
32-bit Netware programs. See www.openwatcom.org. Mostly Fortran-77 standard

Stephen Howe


Judson McClendon

unread,
Nov 11, 2005, 12:42:29 PM11/11/05
to
"Tom Lake" <tl...@twcny.rr.com> wrote:
>> Code Time Comments
>> ---- ---- --------
>> Judson's 1st solution 4.009766
>> Judson's 2nd solution 27.0293 Slower than expected
>
> Are you sure about Judson's 2nd solution timing? That seems way out of
> line!

Apparently not. I'm not sure exactly why, but that particular code is a dog.
:-)

I wrote the benchmark program below, and came up with these numbers when
compiled using QB 4.5 and run on an Athlon 64 X2 4800+. I made the
assumption that none of the posters intended to use non-integer loop
variables or pointers, so I inserted a DEFINT A-Z at the top. Note that to
compile all the code under this particular benchmark I had to slightly
modify some of the code. Please let me know if I messed up someone's code.
All of the code produced the correct result with my sample.

10,000,000 loops
Empty 1.3 seconds
Ed 10.1 seconds
Buck 10.1 seconds
Judson1 10.4 seconds
Judson2 71.0 seconds
Judson3 9.8 seconds
Tom 10.1 seconds
Ethan 10.2 seconds
Stephen 9.3 seconds
Michael 10.2 seconds
Derek 10.1 seconds

Compiled under PowerBASIC Console Compiler 4.01 and run on the same machine
yields:

10,000,000 loops
Empty 0.3 seconds
Ed 9.1 seconds
Buck 9.0 seconds
Judson1 9.4 seconds
Judson2 54.3 seconds
Judson3 8.8 seconds
Tom 8.8 seconds
Ethan 8.7 seconds
Stephen 8.7 seconds
Michael 8.7 seconds
Derek 8.9 seconds

The program is below my SIG.


--
Judson McClendon ju...@sunvaley0.com (remove zero)
Sun Valley Systems http://sunvaley.com
"For God so loved the world that He gave His only begotten Son, that
whoever believes in Him should not perish but have everlasting life."


'
' **************************************************
' * *
' * BENCH.BAS *
' * *
' * Judson D. McClendon *
' * Sun Valley Systems *
' * 4522 Shadow Ridge Pkwy *
' * Pinson, AL 35126-2192 *
' * 205-680-0460 *
' * *
' **************************************************
'
DEFINT A-Z

DECLARE SUB BenchEmpty ()
DECLARE SUB BenchEd ()
DECLARE SUB BenchBuck ()
DECLARE SUB BenchJudson1 ()
DECLARE SUB BenchJudson2 ()
DECLARE SUB BenchJudson3 ()
DECLARE SUB BenchTom ()
DECLARE SUB BenchEthan ()
DECLARE SUB BenchStephen ()
DECLARE SUB BenchMichael ()
DECLARE SUB BenchDerek ()

DIM SHARED NbrLoops AS LONG, AValue AS STRING, XValue AS STRING
DIM SHARED TimeBeg AS SINGLE, TimeEnd AS SINGLE, Loops AS LONG

DIM TimeEmpty AS SINGLE
DIM TimeEd AS SINGLE
DIM TimeBuck AS SINGLE
DIM TimeJudson1 AS SINGLE
DIM TimeJudson2 AS SINGLE
DIM TimeJudson3 AS SINGLE
DIM TimeTom AS SINGLE
DIM TimeEthan AS SINGLE
DIM TimeStephen AS SINGLE
DIM TimeMichael AS SINGLE
DIM TimeDerek AS SINGLE

NbrLoops = VAL(COMMAND$)
AValue = "12345678901234567890"
XValue = "5"

CLS
PRINT USING "###,###,### loops"; NbrLoops

CALL BenchEmpty
TimeEmpty = TimeEnd - TimeBeg
PRINT USING "Empty ###.# seconds"; TimeEmpty

CALL BenchEd
TimeEd = TimeEnd - TimeBeg - TimeEmpty
PRINT USING "Ed ###.# seconds"; TimeEd

CALL BenchBuck
TimeBuck = TimeEnd - TimeBeg - TimeEmpty
PRINT USING "Buck ###.# seconds"; TimeBuck

CALL BenchJudson1
TimeJudson1 = TimeEnd - TimeBeg - TimeEmpty
PRINT USING "Judson1 ###.# seconds"; TimeJudson1

CALL BenchJudson2
TimeJudson2 = TimeEnd - TimeBeg - TimeEmpty
PRINT USING "Judson2 ###.# seconds"; TimeJudson2

CALL BenchJudson3
TimeJudson3 = TimeEnd - TimeBeg - TimeEmpty
PRINT USING "Judson3 ###.# seconds"; TimeJudson3

CALL BenchTom
TimeTom = TimeEnd - TimeBeg - TimeEmpty
PRINT USING "Tom ###.# seconds"; TimeTom

CALL BenchEthan
TimeEthan = TimeEnd - TimeBeg - TimeEmpty
PRINT USING "Ethan ###.# seconds"; TimeEthan

CALL BenchStephen
TimeStephen = TimeEnd - TimeBeg - TimeEmpty
PRINT USING "Stephen ###.# seconds"; TimeStephen

CALL BenchMichael
TimeMichael = TimeEnd - TimeBeg - TimeEmpty
PRINT USING "Michael ###.# seconds"; TimeMichael

CALL BenchDerek
TimeDerek = TimeEnd - TimeBeg - TimeEmpty
PRINT USING "Derek ###.# seconds"; TimeDerek

END


SUB BenchEmpty
TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue
NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchEd
TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue

E310:
A = INSTR(A$, X$)
IF A = 0 THEN GOTO E999


A$ = LEFT$(A$, A - 1) + MID$(A$, A + 1)

GOTO E310
E999:

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchBuck
DEFINT A-Z
TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue

A = INSTR(A$, X$)
DO


A$ = LEFT$(A$, A - 1) + MID$(A$, A + 1)

A = INSTR(A$, X$)
LOOP UNTIL A = 0

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchJudson1
TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue

L = LEN(X$)
DO
A = INSTR(A$, X$)
IF (A > 0) THEN
A$ = LEFT$(A$, A - 1) + MID$(A$, A + L)
ELSE
EXIT DO
END IF
LOOP

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchJudson2
TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue

T$ = SPACE$(LEN(A$)): B = 0
FOR A = 1 TO LEN(A$)
IF (MID$(A$, A, 1) <> X$) THEN
B = B + 1
MID$(T$, B, 1) = MID$(A$, A, 1)
END IF
NEXT
A$ = LEFT$(T$, B)

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchJudson3
TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue

A = 1
L = LEN(X$)
DO
A = INSTR(A, A$, X$)
IF (A > 0) THEN
A$ = LEFT$(A$, A - 1) + MID$(A$, A + L)
ELSE
EXIT DO
END IF
LOOP

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchTom
TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue

DO
A = INSTR(A$, X$)
IF A = 0 THEN EXIT DO


A$ = LEFT$(A$, A - 1) + MID$(A$, A + 1)

LOOP

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchEthan
TimeBeg = TIMER
Char$ = XValue
FOR Loops = 1 TO NbrLoops
Text$ = AValue

DO
Found = INSTR(Text$, Char$)
IF Found THEN Text$ = LEFT$(Text$, Found - 1) + MID$(Text$, Found +
1)
LOOP WHILE Found

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchStephen
TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue

A% = 1
DO


A% = INSTR(A%, A$, X$)

IF A% = 0 THEN EXIT DO
A$ = LEFT$(A$, A% - 1) + MID$(A$, A% + 1)
LOOP

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchMichael
TimeBeg = TIMER
Char$ = XValue
FOR Loops = 1 TO NbrLoops
Text$ = AValue

Found = INSTR(Text$, Char$)
WHILE Found
Text$ = LEFT$(Text$, Found - 1) + MID$(Text$, Found + 1)
Found = INSTR(Text$, Char$)
WEND

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchDerek
TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue

GOTO D310
D300:
LET A$ = LEFT$(A$, A% - 1) + MID$(A$, A% + 1)
D310:
LET A% = INSTR(A$, X$): IF A% THEN GOTO D300

NEXT Loops
TimeEnd = TIMER
END SUB


Ethan Winer

unread,
Nov 11, 2005, 1:06:44 PM11/11/05
to
Tom,

> but now you're testing Found twice. <

Sure, but that's less code and much faster than INSTR.

Is there a way to do this with only one INSTR and only one Found?

Man, this has been a lot more fun than what I usually find myself doing
these days! :->)

--Ethan


Ethan Winer

unread,
Nov 11, 2005, 1:32:13 PM11/11/05
to
Derek,

> The big lesson seems to be that integer arithmetic makes more difference
than anything else <

No kidding. Found in my code was intended to be in integer. I start every
program I write with DEFINT A-Z. :->)

> The second lesson is that I had to run 100,000 or more iterations to get
measurable results. <

There's still a potential for up to 1/18th second error unless you
synchronize the timing to begin just as the system clock begins a new cycle.

This is adapted from my Basic Techniques book:

Synch! = TIMER 'synchronize to TIMER
DO
Start! = TIMER
LOOP WHILE Start! = Synch!

FOR X = 1 TO 10000
LOCATE 1
PRINT X$; Y$; Z$
NEXT

Done! = TIMER 'calculate elapsed time
Elapsed! = Done! - Start!

Even with synching the error can be 1/18th second. But that's better than
2/18th second.

--Ethan


Judson McClendon

unread,
Nov 11, 2005, 1:39:12 PM11/11/05
to
"Ethan Winer" <ethanw at ethanwiner dot com> wrote:
>
> Is there a way to do this with only one INSTR and only one Found?


Found = 1
DO
Found = INSTR(Found,A$,X$)
IF NOT Found THEN EXIT DO
A$ = LEFT$(A$, Found - 1) + MID$(A$, Found + 1)
LOOP

ararghmai...@now.at.arargh.com

unread,
Nov 11, 2005, 2:23:16 PM11/11/05
to
On Fri, 11 Nov 2005 12:47:14 GMT, "Michael Mattias"
<michael...@gte.net> wrote:

><ararghmai...@NOW.AT.arargh.com> wrote in message
>news:ttr8n1lat4gaebib1...@4ax.com...
>> On 11 Nov 2005 01:23:11 -0800, "Derek" <dere...@yahoo.ca> wrote:
>>
>> <snip>
>> >The big lesson seems to be that integer arithmetic makes more
>> >difference than anything else but I don't suppose that that comes as a
>> >surprise to anyone.
>
>> The very reason I almost always start every program with: DEFINT A-Z
>
>Didn't every MS-DOS BASIC programmer?

Only those who knew which way was up, or had been properly trained.

>That's like having a cup of coffee when you get out of bed... automatic!

--

Stephen Howe

unread,
Nov 11, 2005, 2:31:11 PM11/11/05
to
> The big lesson seems to be that integer arithmetic makes more
> difference than anything else but I don't suppose that that comes as a
> surprise to anyone. The second lesson is that I had to run 100,000 or
> more iterations to get measurable results. Modern PC's running QBASIC
> 1.1 make it look very fast. So in practice any of the solutions would
> have been almost indistinguishable for less than 100,000 calls. That
> being so readability beomes more important and one of the structured
> solutions is probably best overall.

If anyone knows C++, this is close to remove() algorithm, part of STL
(Standard Template Library).
This algorithm is badly named (difficult to get a good name), but roughly
speaking it is like squeezing toothpaste (the crud you don't want) to one
end of the container leaving what you do want at the other end of the
container, squeezed up. This is an O(N) algorithm which is very fast.

And it does this by breaking the algorithm into two parts.
Until you discover the first X$, the initial set of characters you want to
keep, you skip over them.
But after that you overwrite the first X$ with the next character you wish
to keep etc
Eventually A$ consists of all the characters you want to keep, the X$ having
been overwritten
and you have a potentially new length of A$

Where speed is lost in all the solutions above is the BASIC memory managment
overhead of changing A$. You want to do in-situ changes to A$. That really
calls for assembler.

See other post

Stephen Howe

Stephen Howe

unread,
Nov 11, 2005, 2:35:53 PM11/11/05
to
Derek, please benchtest my 2nd offering

Derek if you are reading this, bench test this (only works on QBASIC but
will work on others if we can get the address of actual string in memory)

' REM Stephen Howe, 2nd offering
I% = INSTR(A$, X$)
IF I% = 0 THEN RETURN
X% = ASC(X$)
ALenOld% = LEN(A$)
ALenNew% = I% - 1
DEF SEG = VARSEG(A$)
'read string descriptor address
APtr& = PEEK(VARPTR(A$) + 2) + PEEK(VARPTR(A$) + 3) * 256&
FOR I% = I% + 1 TO ALenOld%
Y% = PEEK(APtr& + I% - 1)
IF Y% <> X% THEN
POKE APtr& + ALenNew%, Y%
ALenNew% = ALenNew% + 1
END IF
NEXT I%
DEF SEG
A$ = LEFT$(A$, ALenNew%)
RETURN

Thanks

Stephen Howe


Stephen Howe

unread,
Nov 11, 2005, 2:44:57 PM11/11/05
to
> PS. I'll post my benchmarking framework if anyone really wants to see
> it but it's nothing special. Just For/Next, Timer and Print gluing
> together the code that has already been posted.

I would like to see. Also what strings are being fed as input.

Sample inputs that seem good to test are

(1) Big strings which do not have a single replace character
(2) Big strings which have a few replace characters scattered about
(3) Big strings which have all the replace characters as 1 contiguous block
(4) Big strings which have all the replace characters as 2+ contiguous
blocks
(5) Big strings which consist _only of the replace characters
(6) The empty string (code should not fall over)

Stephen


Tom Lake

unread,
Nov 11, 2005, 2:49:21 PM11/11/05
to

"Stephen Howe" <stephenPOINThoweATtns-globalPOINTcom> wrote in message
news:%23B%23cicv5...@TK2MSFTNGP14.phx.gbl...

> Derek, please benchtest my 2nd offering
>
> Derek if you are reading this, bench test this (only works on QBASIC but
> will work on others if we can get the address of actual string in memory)

The program below works fine in QBasic and Quick BASIC. Although
the QBasic help says SADD is not supported, it actually is. This way,
you don't have to worry about segments.

Tom Lake

testsub:


' REM Stephen Howe, 2nd offering

I% = INSTR(a$, x$)


IF I% = 0 THEN RETURN

x% = ASC(x$)
ALenOld% = LEN(a$)


ALenNew% = I% - 1

'read string descriptor address
APtr& = SADD(a$)


FOR I% = I% + 1 TO ALenOld%
Y% = PEEK(APtr& + I% - 1)

IF Y% <> x% THEN


POKE APtr& + ALenNew%, Y%
ALenNew% = ALenNew% + 1
END IF
NEXT I%

a$ = LEFT$(a$, ALenNew%)
RETURN


Derek

unread,
Nov 11, 2005, 2:56:49 PM11/11/05
to
Fair enough Tom. I should have published my inputs. I won't reproduce
the whole benchmark program since it's pretty similar to Judson's but
here's the important part. The rest should be pretty obvious.

CONST N& = 100000

CLS
PRINT "Code"; TAB(25); "Time"; TAB(35); "Comments"
PRINT "----"; TAB(25); "----"; TAB(35); "--------"
LET X$ = "E"

LET Q$ = "An empty loop": C$ = "Calibrate to cut FOR/NEXT from timing"
LET Q0 = TIMER 'Reset the loop timer
FOR J& = 1& TO N&
LET A$ = "HELLO THERE. HOW ARE YOU, MY GOOD FRIEND"
NEXT
LET Q1 = TIMER - Q0 ' Calculate Loop base time
GOSUB Message

LET Q$ = "Ed's Solution": C$ = "The original"
FOR J& = 1& TO N&
LET A$ = "HELLO THERE. HOW ARE YOU, MY GOOD FRIEND"
GOSUB 110
NEXT
GOSUB Message

LET Q$ = "Buck's 1st solution": C$ = "Fails when X$ does not appear in
A$"
LET Q0 = TIMER 'Reset the loop timer
FOR J& = 1& TO N&
LET A$ = "HELLO THERE. HOW ARE YOU, MY GOOD FRIEND"
GOSUB 210
NEXT
GOSUB Message

... etc., etc...

SYSTEM

Message:
PRINT Q$; TAB(25); TIMER - Q0 - Q1; TAB(35); C$
RETURN

110 A = INSTR(A$, X$)
IF A = 0 THEN RETURN


A$ = LEFT$(A$, A - 1) + MID$(A$, A + 1)

GOTO 110

210 A = INSTR(A$, X$)


DO
A$ = LEFT$(A$, A - 1) + MID$(A$, A + 1)
A = INSTR(A$, X$)
LOOP UNTIL A = 0

RETURN

...followed by all the other routines and procedures that needed
testing.


I'm impressed by TrueBASIC's performance though. I always agreed with K
& K's opinion that we shouldn't have to specify Numeric types and it's
nice to see that their compiler is clever enough to do that without
sacrificing too much performance.

Cheers

Derek

Michael Mattias

unread,
Nov 11, 2005, 3:08:16 PM11/11/05
to
"Tom Lake" <tl...@twcny.rr.com> wrote in message
news:5B6df.8$uC...@twister.nyroc.rr.com...
> although the QBasic help says SADD is not supported, it actually is

Not, it's NOT "supported." RTFM.

It simply happens to work.

MCM

Michael Mattias

unread,
Nov 11, 2005, 3:08:16 PM11/11/05
to
Wait a minute, wait a minute...

What's the object here?

1. Execution speed?
2. Ease of maintenance (i.e., understandable to the next poor schmoe who has
to work on the program?)
3. Reduce size of compiled program?
4. Reduce the size of the source code? (I know that's a non-reason, but hey,
someone brought it up)
5. Reduce resources used by the program at run time?

Hey, you can't have it all... you have to pick your priorities and program
accordingly.. all programming is a tradeoff of resources and performance.

Ooops......I forgot one teensy-weensy miniscule tiny small miniature
possibility for the point of the exercise...

" To help the guy who posted the problem to begin with."

MCM


Stephen Howe

unread,
Nov 11, 2005, 3:10:42 PM11/11/05
to
> The program below works fine in QBasic and Quick BASIC. Although
> the QBasic help says SADD is not supported, it actually is. This way,
> you don't have to worry about segments.

Thanks Tom. Very useful to know.
I might do a STRINGS of QBASIC.EXE and see what else is there.
Is this QBASIC 1.0 or 1.1 ?

Stephen


Derek

unread,
Nov 11, 2005, 3:25:11 PM11/11/05
to
I've already posted enough of it in answer to Tom that you should be
able to recreate the missing bits. They are just modified copies of
what's already posted. If people specified RETURN, I used a GOSUB in
their loop and if they specified a SUB routine, I called that instead.

It would be interesting to do an in depth test using the cases that
you've described above. I probably won't do it as I have other
work-related code to deal with over the weekend -- but feel free! I'm
sure that we'd all love to see the results!

Cheers

Derek

Judson McClendon

unread,
Nov 11, 2005, 3:26:28 PM11/11/05
to
"Tom Lake" <tl...@twcny.rr.com> wrote:


Removing '5' from '12345678901234567890' for 10,000,000 loops
Empty 1.4 seconds
Ed 11.2 seconds
Buck 11.1 seconds
Judson1 11.6 seconds
Judson2 73.3 seconds
Judson3 10.9 seconds
Tom 11.1 seconds
Ethan 11.2 seconds
Stephen1 10.4 seconds
Stephen2 6.5 seconds
Michael 11.2 seconds
Derek 11.1 seconds


--
Judson McClendon ju...@sunvaley0.com (remove zero)
Sun Valley Systems http://sunvaley.com
"For God so loved the world that He gave His only begotten Son, that
whoever believes in Him should not perish but have everlasting life."

'


' **************************************************
' * *
' * BENCH.BAS *
' * *
' * Judson D. McClendon *
' * Sun Valley Systems *
' * 4522 Shadow Ridge Pkwy *
' * Pinson, AL 35126-2192 *
' * 205-680-0460 *
' * *
' **************************************************
'
DEFINT A-Z

DECLARE SUB BenchEmpty ()
DECLARE SUB BenchEd ()
DECLARE SUB BenchBuck ()
DECLARE SUB BenchJudson1 ()
DECLARE SUB BenchJudson2 ()
DECLARE SUB BenchJudson3 ()
DECLARE SUB BenchTom ()
DECLARE SUB BenchEthan ()

DECLARE SUB BenchStephen1 ()
DECLARE SUB BenchStephen2 ()


DECLARE SUB BenchMichael ()
DECLARE SUB BenchDerek ()

DECLARE SUB BombOut ()

DIM SHARED NbrLoops AS LONG, AValue AS STRING, XValue AS STRING
DIM SHARED TimeBeg AS SINGLE, TimeEnd AS SINGLE, Loops AS LONG

DIM TimeEmpty AS SINGLE
DIM TimeEd AS SINGLE
DIM TimeBuck AS SINGLE
DIM TimeJudson1 AS SINGLE
DIM TimeJudson2 AS SINGLE
DIM TimeJudson3 AS SINGLE
DIM TimeTom AS SINGLE
DIM TimeEthan AS SINGLE

DIM TimeStephen1 AS SINGLE
DIM TimeStephen2 AS SINGLE


DIM TimeMichael AS SINGLE
DIM TimeDerek AS SINGLE


T$ = COMMAND$
T = INSTR(T$, " ")
IF (T > 0) THEN
CALL BombOut
END IF
T = INSTR(T$, ",")
IF (T = 0) THEN
CALL BombOut
END IF
NbrLoops = VAL(LEFT$(T$, T - 1))
T$ = MID$(T$, T + 1)
T = INSTR(T$, ",")
IF (T = 0) THEN
CALL BombOut
END IF
AValue = LEFT$(T$, T - 1)
XValue = MID$(T$, T + 1)


CLS
PRINT USING "Removing '&' from '&' for ###,###,### loops"; XValue; AValue;
NbrLoops

CALL BenchEmpty
TimeEmpty = TimeEnd - TimeBeg

PRINT USING "Empty #,###.# seconds"; TimeEmpty

CALL BenchEd
TimeEd = TimeEnd - TimeBeg - TimeEmpty

PRINT USING "Ed #,###.# seconds"; TimeEd

CALL BenchBuck
TimeBuck = TimeEnd - TimeBeg - TimeEmpty

PRINT USING "Buck #,###.# seconds"; TimeBuck

CALL BenchJudson1
TimeJudson1 = TimeEnd - TimeBeg - TimeEmpty

PRINT USING "Judson1 #,###.# seconds"; TimeJudson1

CALL BenchJudson2
TimeJudson2 = TimeEnd - TimeBeg - TimeEmpty

PRINT USING "Judson2 #,###.# seconds"; TimeJudson2

CALL BenchJudson3
TimeJudson3 = TimeEnd - TimeBeg - TimeEmpty

PRINT USING "Judson3 #,###.# seconds"; TimeJudson3

CALL BenchTom
TimeTom = TimeEnd - TimeBeg - TimeEmpty

PRINT USING "Tom #,###.# seconds"; TimeTom

CALL BenchEthan
TimeEthan = TimeEnd - TimeBeg - TimeEmpty

PRINT USING "Ethan #,###.# seconds"; TimeEthan

CALL BenchStephen1
TimeStephen1 = TimeEnd - TimeBeg - TimeEmpty
PRINT USING "Stephen1 #,###.# seconds"; TimeStephen1

CALL BenchStephen2
TimeStephen2 = TimeEnd - TimeBeg - TimeEmpty
PRINT USING "Stephen2 #,###.# seconds"; TimeStephen2

CALL BenchMichael
TimeMichael = TimeEnd - TimeBeg - TimeEmpty

PRINT USING "Michael #,###.# seconds"; TimeMichael

CALL BenchDerek
TimeDerek = TimeEnd - TimeBeg - TimeEmpty

PRINT USING "Derek #,###.# seconds"; TimeDerek

END

SUB BenchBuck ' Buck


DEFINT A-Z
TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue

A = INSTR(A$, X$)


DO
A$ = LEFT$(A$, A - 1) + MID$(A$, A + 1)
A = INSTR(A$, X$)
LOOP UNTIL A = 0

NEXT Loops


TimeEnd = TIMER
END SUB

SUB BenchDerek ' Derek


TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue

GOTO D310
D300:
LET A$ = LEFT$(A$, A% - 1) + MID$(A$, A% + 1)
D310:
LET A% = INSTR(A$, X$): IF A% THEN GOTO D300

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchEd ' Ed


TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue

E310:
A = INSTR(A$, X$)
IF A = 0 THEN GOTO E999


A$ = LEFT$(A$, A - 1) + MID$(A$, A + 1)

GOTO E310
E999:

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchEmpty ' Empty


TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue
NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchEthan ' Ethan


TimeBeg = TIMER
Char$ = XValue
FOR Loops = 1 TO NbrLoops
Text$ = AValue

DO
Found = INSTR(Text$, Char$)
IF Found THEN Text$ = LEFT$(Text$, Found - 1) + MID$(Text$, Found +
1)
LOOP WHILE Found

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchJudson1 ' Judson 1


TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue

L = LEN(X$)
DO
A = INSTR(A$, X$)


IF (A > 0) THEN
A$ = LEFT$(A$, A - 1) + MID$(A$, A + L)
ELSE
EXIT DO
END IF
LOOP

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchJudson2 ' Judson 2


TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue

T$ = SPACE$(LEN(A$)): B = 0
FOR A = 1 TO LEN(A$)
IF (MID$(A$, A, 1) <> X$) THEN
B = B + 1
MID$(T$, B, 1) = MID$(A$, A, 1)
END IF
NEXT
A$ = LEFT$(T$, B)

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchJudson3 ' Judson 3


TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue

A = 1
L = LEN(X$)
DO
A = INSTR(A, A$, X$)
IF (A > 0) THEN

A$ = LEFT$(A$, A - 1) + MID$(A$, A + L)
ELSE
EXIT DO
END IF
LOOP

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchMichael ' Michael


TimeBeg = TIMER
Char$ = XValue
FOR Loops = 1 TO NbrLoops
Text$ = AValue

Found = INSTR(Text$, Char$)
WHILE Found
Text$ = LEFT$(Text$, Found - 1) + MID$(Text$, Found + 1)
Found = INSTR(Text$, Char$)
WEND

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchStephen1 ' Stephen 1


TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue

A% = 1
DO
A% = INSTR(A%, A$, X$)
IF A% = 0 THEN EXIT DO
A$ = LEFT$(A$, A% - 1) + MID$(A$, A% + 1)
LOOP

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BenchStephen2 ' Stephen 2


TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue

I% = INSTR(a$, x$)


IF I% = 0 THEN RETURN
x% = ASC(x$)
ALenOld% = LEN(a$)
ALenNew% = I% - 1
'read string descriptor address
APtr& = SADD(a$)
FOR I% = I% + 1 TO ALenOld%
Y% = PEEK(APtr& + I% - 1)
IF Y% <> x% THEN
POKE APtr& + ALenNew%, Y%
ALenNew% = ALenNew% + 1
END IF
NEXT I%
a$ = LEFT$(a$, ALenNew%)

NEXT Loops


TimeEnd = TIMER
END SUB

SUB BenchTom ' Tom


TimeBeg = TIMER
X$ = XValue
FOR Loops = 1 TO NbrLoops
A$ = AValue

DO
A = INSTR(A$, X$)
IF A = 0 THEN EXIT DO


A$ = LEFT$(A$, A - 1) + MID$(A$, A + 1)

LOOP

NEXT Loops
TimeEnd = TIMER
END SUB

SUB BombOut
PRINT "Use as: BENCH <nbr of loops>,<A$ string>,<X$ character>"
END
END SUB


Derek

unread,
Nov 11, 2005, 3:29:35 PM11/11/05
to
I added the code to my test prog and the result was...

Stephen's 2nd solution 4.488281

...when run under QBASIC to remove the "E"s from the test string "HELLO


THERE. HOW ARE YOU, MY GOOD FRIEND"

So your first offering was about 4 times faster. However the latest
version may fare better on long strings or under other conditions.

Cheers

Derek

Derek

unread,
Nov 11, 2005, 3:47:00 PM11/11/05
to
Yes, it's to help the guy but I'm pretty sure that we've done that by
now. So object 6 accomplished. But I think that you can also add an
object 7 to the list -- "have fun coding". Sure, I attempt to get the
most appropriate tradeoff from your list for the job in hand but I also
try to get job satisfaction. I'll bet you do too. If we didn't enjoy
coding we'd go into some other line of business and we probably
wouldn't contribute to the MS-DOS BASIC newsgroups.

Cheers

Derek

Derek

unread,
Nov 11, 2005, 3:57:23 PM11/11/05
to
Sorry about the lack of DEFINT A-Z. I should guessed that you were
assuming INTEGER. At least Judson has rectified my error there. As for
the +- 1/18 of a sec, I wasn't too worried since I was running in a
Windows XP Command Prompt session anyway and I knew that that would put
the results off a bit. I originally reduced the relative timing error a
little by running 1,000,000 iterations but it was getting quite late by
the time I'd got the test program working so I cut that to 100,000 for
my posting. The relative timings still came out the same. I don't find
absolute timings so interesting since we all have different computers
and different versions of Windows and thus absolute timings aren't
really reproducible by others.

Cheers

Derek

Stephen Howe

unread,
Nov 11, 2005, 4:04:45 PM11/11/05
to
> The program below works fine in QBasic and Quick BASIC. Although
> the QBasic help says SADD is not supported, it actually is. This way,
> you don't have to worry about segments.

It is not the only thing. I found SETMEM also "works".

Stephen


ararghmai...@now.at.arargh.com

unread,
Nov 11, 2005, 7:13:23 PM11/11/05
to

Strings won't tell you a whole lot.

This is the begining of the keyword table in qbasic 1.1.

006340
01 12 0E FF 0F 13 FF FF 09 08 0A FF 19 00 23 42 ..............#B
53 40 6F 07 51 43 43 45 53 53 00 41 4C 49 41 53 S...@o.QCCESS.ALIAS
00 22 4E 44 80 06 21 4E 59 00 51 50 50 45 4E 44 ."ND..!NY.QPPEND
00 11 53 00 23 53 43 40 76 07 23 54 4E 40 7D 07 ..S.#SC@v.#TN@}.
00 22 00 31 41 53 45 00 33 45 45 50 01 00 00 51 .".1ASE.3EEP...Q

The entries shown are for : ABS, ACCESS, ALIAS, AND, ANY, APPEND, AS,
ASC, ATN, BASE, BEEP, and the last '51' is the begining of BINARY. :-)

Have fun. :-)

Todd Vargo

unread,
Nov 11, 2005, 7:06:25 PM11/11/05
to

"Stephen Howe" <stephenPOINThoweATtns-globalPOINTcom> wrote in message
news:OhzF1fs...@TK2MSFTNGP10.phx.gbl...

> > For the code above,
> > A% = INSTR(A%, A$, X$)
> > should be
> > A% = INSTR(A% + 1, A$, X$)
> > otherwise if A% is zero (as is usually the case in a SUB), the INSTR
> > function will throw an 'illegal function call'.
>
> Correct observation but wrong code. What I had was correct.
> The code you presented above does not handle repeated characters X$ that
are
> moved into the same position after the string is altered. You are moving 1
> beyond each time.

Ah, good catch. Well, the point was, A% needed to be preset before going
into the loop.

>
> > Also, as previously mentioned, the code above does not consider that X$
> can
> > be greater than a single character.
>
> Well yes that is true. But if you look at the original posters code
sample,
> it is clear from the LEFT$ and MID$, it is clear that the code is a
function
> for removing a _single_ character from a string, not a string from a
string.
> The code I presented reflects that.

Rather than posting separately, I was commenting about the OP's limitation,
rather for the OP's benefit, then for others. The OP's REM statement
suggests (to me anyway) the single character limitation may have been an
oversight. I would have expected the REM statement to read something to the
effect of 'Deletes all character X$ from A$'. YMMV.

--
Todd Vargo (double "L" to reply by email)

Tom Lake

unread,
Nov 11, 2005, 9:21:47 PM11/11/05
to
>> although the QBasic help says SADD is not supported, it actually is
>
> Not, it's NOT "supported." RTFM.
>
> It simply happens to work.

It's as supported as the rest of the MS-DOS BASIC languages are
now! As far as RTFM, WHAT manual? I didn't get a manual
with it. Did you? All I have is the help file which I already said
lists SADD as not implemented (semantics aside).

Tom Lake


Tom Lake

unread,
Nov 11, 2005, 9:24:34 PM11/11/05
to

I have 1.1 but I remember that it works in both versions. Of course
since I write BASIC code for a living using 50-60 different dialects
(there are over 150 PC versions of BASIC if you count DOS and Windows
separately) I could remember wrong.

Tom Lake


Ethan Winer

unread,
Nov 12, 2005, 9:12:25 AM11/12/05
to
Derek,

> running 1,000,000 iterations <

BASIC doesn't do this, but some compilers are so "smart" that when they see
a loop with no reference to the index inside the loop, they compile only one
iteration. I don't want a compiler to second-guess me like that! If I write
this:

FOR X% = 1 TO 1000
Y% = 3
NEXT

I don't want the compiler to change that to this:

Y% = 3
X% = 1001

:->)

--Ethan


Tom Lake

unread,
Nov 12, 2005, 9:24:48 AM11/12/05
to
> BASIC doesn't do this, but some compilers are so "smart" that when they
> see
> a loop with no reference to the index inside the loop, they compile only
> one
> iteration. I don't want a compiler to second-guess me like that! If I
> write
> this:
>
> FOR X% = 1 TO 1000
> Y% = 3
> NEXT
>
> I don't want the compiler to change that to this:
>
> Y% = 3
> X% = 1001

What, you don't want an optimizing compiler? 8^)

This is a great thread! I love it when all sorts (bubble, insertion,
shell?)
of people get together and contribute in a positive way to the topic.
Even though I've been programming in BASIC since 1967 or so, and
still make my living programming in BASIC, I can still learn things from
this newsgroup.

Tom Lake


Judson McClendon

unread,
Nov 12, 2005, 9:57:38 AM11/12/05
to
"Derek" <dere...@yahoo.ca> wrote:
> It would be interesting to do an in depth test using the cases that
> you've described above. I probably won't do it as I have other
> work-related code to deal with over the weekend -- but feel free! I'm
> sure that we'd all love to see the results!

This is why I posted the second version of my benchmark. It takes the number
of loops, the search string and target character from the command line, so
it could be run multiple times overnight from a BAT file. You can use
redirection to place the output into a disk file, or files.

Michael Mattias

unread,
Nov 12, 2005, 11:31:19 AM11/12/05
to
"Tom Lake" <tl...@twcny.rr.com> wrote in message
news:%kcdf.179$uC3...@twister.nyroc.rr.com...


Semantics aside? Aside?

Semantics is what documentation - be it a printed manual, web page or help
file - is all about!

If the documentation explicity states "not implemented" then any reliance on
that function is some serious crapshooting.

That said, I wonder if SADD still "works" in Qbasic if a DEF SEG to other
than the default data segment is in effect?

MCM

Michael Mattias

unread,
Nov 12, 2005, 11:31:20 AM11/12/05
to
"Tom Lake" <tl...@twcny.rr.com> wrote in message
news:QWmdf.223$JQ....@twister.nyroc.rr.com...
> Even though I've been programming in BASIC since 1967 or so...

For the history buffs....

History of BASIC...
http://www.fys.ruu.nl/~bergmann/history.html

History of Microsoft BASIC...
http://www.emsps.com/oldtools/msbasv.htm


MCM


Michael Mattias

unread,
Nov 12, 2005, 11:31:20 AM11/12/05
to
"Ethan Winer" <ethanw at ethanwiner dot com> wrote in message
news:ee5PfM55...@TK2MSFTNGP14.phx.gbl...

> BASIC doesn't do this, but some compilers are so "smart" that when they
see
> a loop with no reference to the index inside the loop, they compile only
one
> iteration. I don't want a compiler to second-guess me like that! If I
write
> this:
>
> FOR X% = 1 TO 1000
> Y% = 3
> NEXT

With all due respect to your many years of writing BASIC code, I think your
boss should handle the second guessing if you wrote code like that.

At the very least this code is missing some kind of comment why you are
executing 999 useless assignments.

FWIW, there are several code analyzer products for IBM mainframes which will
give you a report on your code to tell you when you do something like this.
You can pick one up for only, say, $50,000-$100,000 (plus maintenance),
hardware not included.

Also FWIW: the optimizer built in to the IBM Mainframe COBOL compiler
*would* change your code as suggested. It just wouldn't bother to tell you
it did.


MCM

Tom Lake

unread,
Nov 12, 2005, 12:33:12 PM11/12/05
to

"Michael Mattias" <michael...@gte.net> wrote in message
news:sNodf.6909$8W....@newssvr30.news.prodigy.com...

> "Tom Lake" <tl...@twcny.rr.com> wrote in message
> news:QWmdf.223$JQ....@twister.nyroc.rr.com...
>> Even though I've been programming in BASIC since 1967 or so...
>
> For the history buffs....
>
> History of BASIC...
> http://www.fys.ruu.nl/~bergmann/history.html

Yup. I've been programming in BASIC almost as long as it's been around
I was used to quite a few implementations before Gates and Allen
wrote Altair BASIC so I learned that one fairly quickly. It had some
nice features that mainframe and minicomputer BASICs lacked but was
sorely lacking in some areas. All in all, fitting an interpreter is such a
small
space was quite an achievement.

Tom Lake


Tom Lake

unread,
Nov 12, 2005, 12:44:15 PM11/12/05
to
> Semantics aside? Aside?

Yes. By "supported" I mean that the language supports it, not that
Microsoft
supports it.

> If the documentation explicity states "not implemented" then any reliance
> on
> that function is some serious crapshooting.

Not when the language is frozen in time. If there will be further
development
of the language, then yes, it would be foolish to rely on an "unsupported"
feature which could disappear in a future revision. When the language is
static
(as QBasic is now) and no more revisions of it will be forthcoming, then you
can rely on the feature set and it would be foolish NOT to take advantage of
all the features present.

>
> That said, I wonder if SADD still "works" in Qbasic if a DEF SEG to other
> than the default data segment is in effect?

Try this and see:

a$ = "T"
DEF SEG = &HF000
b$ = CHR$(PEEK(SADD(a$)))
PRINT "SADD does";
IF b$ <> "T" THEN PRINT "n't";
PRINT " work when an alternate DEF SEG is in effect."
DEF SEG

Tom Lake


Stephen Howe

unread,
Nov 12, 2005, 2:40:06 PM11/12/05
to
> If the documentation explicity states "not implemented" then any reliance
> on
> that function is some serious crapshooting.

Who says the documentation is the necessarily the _final_ authority?

1. I can think of some commerical compiler software I have been involved in
for the best part of 18 years and any time there was a new release, I would
inspect the libraries & header files to see what had changed for the new
version. Over 99% was the same. But for the 1% invariably I found

- the documentation was newer in parts that the libraries & header files
- the header files declared functions that did not exist in the libraries
- the libraries contained functions that were effectively undocumented (but
the fact they existed in other vendors offering made it very clear that they
were competing with this, the headers and documentation had not been
updated)
- occasionally 3 out of 3 were coincident :-)

2. I can think of Microsoft's ADO for accessing databases, which I have used
at work for the past 5 years.
There have been at least 14 occasions when I have emailed the Microsoft ADO
documentation team pointing out some aspect which appeared wrong. And the
feedback from Microsoft indicated that I was right. There is one
semi-documented feature of ADO (semi-documented as 1 Microsoft writer
mentions it)

3. For langauges like C & C++ & FORTRAN, there are ISO standards, so you can
tell, to some extent if the compiler is right, the documentation is right,
the libraries are right - i.e. it is independent of compiler vendor since
most of them promise 100% standard compliance. So with an outside yardstick,
you have some means of knowing which part is right.

In the case of SADD & SETMEM, you don't know if the documentation is wrong.
It could be that the documentation is incorrect, they were not supposed to
be removed and they should be documented.
It could be that the documentation is correct, and the progammers who should
have removed those from QBASIC did not.
But in any case, the chances are near 100% that they work the same way they
are documentated to work in other Microsoft BASIC products.

> That said, I wonder if SADD still "works" in Qbasic if a DEF SEG to other
> than the default data segment is in effect?

I don't think it would work. But it does not matter.
What QBASIC documentation indicates is that the strings are "near strings",
all part of DGROUP.
And tests on checking variable segments seem to confirm this observation.

Stephen Howe


Michael Mattias

unread,
Nov 12, 2005, 3:36:15 PM11/12/05
to
"Stephen Howe" <sjhoweATdialDOTpipexDOTcom> wrote in message
news:OhWdqD85...@TK2MSFTNGP10.phx.gbl...

> In the case of SADD & SETMEM, you don't know if the documentation is
wrong.
> It could be that the documentation is incorrect, they were not supposed to
> be removed and they should be documented.
> It could be that the documentation is correct, and the progammers who
should
> have removed those from QBASIC did not.

This is the exact point I have repeatedly tried to make to a certain
BASIC-language compiler developer currently located in Venice FL:

When the documented behavior does not match the observed behavior, there is
a bug. It may be a bug in the compiler, or it may be a bug in the
documentation; but THERE IS A BUG HERE.

Unfortunately, I guess I just do not speak Floridian, since this seemingly
obvious statement has never been understood there.

MCM

ararghmai...@now.at.arargh.com

unread,
Nov 12, 2005, 4:54:32 PM11/12/05
to
On Sat, 12 Nov 2005 16:31:19 GMT, "Michael Mattias"
<michael...@gte.net> wrote:

<snip>


>That said, I wonder if SADD still "works" in Qbasic if a DEF SEG to other
>than the default data segment is in effect?

The runtime code for both exists in Qbasic 1.1
<snip>

Bill Plenge

unread,
Nov 12, 2005, 6:51:57 PM11/12/05
to

This link isn't a complete history, it starts with the IBM PC. Prior to
that MS wrote several other BASIC interpeters. The TRS-80's Level II and
Disk BASIC for example.

>
>
> MCM


Bill Plenge

unread,
Nov 12, 2005, 7:02:20 PM11/12/05
to
That's what I get for only looking at the links at the top of the page,
ignore my previous post.


Michael Mattias

unread,
Nov 13, 2005, 8:56:43 AM11/13/05
to
"Bill Plenge" <B_Pl...@NoSpam.com> wrote in message
news:govdf.118$vq1...@tornado.rdc-kc.rr.com...

> That's what I get for only looking at the links at the top of the page,
> ignore my previous post.

Don't feel bad.

When I was in college (1968-1972), we MSOE students had access to a Venture
computer which one programmed in a language which was called an "Interactive
FORTRAN." Looking at some of my old paper listings, I'd swear it was a BASIC
dialect.

Even today I note the strong similarity between well-written COBOL and
well-written BASIC. (Poorly written COBOL looks like.. well, poorly-written
COBOL).

So who is to say what came first (other than the no-brainer that the first 3
GL was COBOL)?

Then again, "they" say ALL programming comes down to but four (4)
instructions: Store, Add, Compare and Jump. So it's not too surpising all
languages look as related to each other at least as much as do all West
Virginians.

MCM


Judson McClendon

unread,
Nov 13, 2005, 9:30:20 AM11/13/05
to
"Michael Mattias" <michael...@gte.net> wrote:
>
> ... So it's not too surpising all

> languages look as related to each other at least as much as do all West
> Virginians.


Even APL? :-)

Ethan Winer

unread,
Nov 13, 2005, 11:13:23 AM11/13/05
to
Michael,

> With all due respect to your many years of writing BASIC code, I think
your boss should handle the second guessing if you wrote code like that. <

ROF,L.

Hey, that was ONLY an example of how using a FOR loop to time code can give
bogus results with some optimizing compilers.

--Ethan


Ethan Winer

unread,
Nov 13, 2005, 11:16:51 AM11/13/05
to
Michael,

> I wonder if SADD still "works" in Qbasic if a DEF SEG to other than the
default data segment is in effect? <

DEF SEG affects only certain commands like BLOAD and PEEK and POKE. With
near strings, SADD should give the address of the string in DGROUP
regardless of the current DEF SEG value. And SADD with far strings should
give the address of the string in its own segment, also independent of DEF
SEG.

--Ethan


Tom Lake

unread,
Nov 13, 2005, 12:11:05 PM11/13/05
to

I see you didn't run my example or try one of your own.

In QBasic, SADD does NOT work if the default DEF SEG
is changed. Here's the proof again:

Michael Mattias

unread,
Nov 13, 2005, 1:12:57 PM11/13/05
to
"Ethan Winer" <ethanw at ethanwiner dot com> wrote in message
news:uHyQs2G6...@TK2MSFTNGP09.phx.gbl...

I guess my real point was, "If the help file says something is 'not
supported' or 'not implemented', DO NOT USE IT! "

At the very least, do not be surprised if it "stops working" under some
data circumstances.

MCM

Stephen Howe

unread,
Nov 13, 2005, 2:49:00 PM11/13/05
to
> I see you didn't run my example or try one of your own.
>
> In QBasic, SADD does NOT work if the default DEF SEG
> is changed. Here's the proof again:

Your proof is completely and utterly WRONG!!!
All you have proved is that PEEK is affected by DEF SEG changes (which is
known, that is what the documentation says)

This line here

b$ = CHR$(PEEK(SADD(a$)))

does not conclude that SADD does not work if DEF SEG is changed.
The value that SADD returns is unchanged by DEF SEG values, but the
character value at the segment:offset in question is what PEEK returns and
that depends entirely on the DEF SEG value.

Here is an example which shows this SADD returns unchanged values:

S$ = "Hello"
PRINT SADD(S$)
DEF SEG = &HF000
PRINT SADD(S$)
DEF SEG = &HE000
PRINT SADD(S$)
DEF SEG = &HD000
PRINT SADD(S$)
DEF SEG
PRINT SADD(S$)

Do you get the same value printed multiple times? I bet you do. And that is
to be expected. All SADD is returning is the offset portion of the first
chracter in the string, why should it depend on what the current value of
DEF SEG is?

Lets, say for the sake of example, that this value is &H7D3E in hex (32062
in decimal)

Using PEEK would mean that you are comparing the characters at
segment:offset positions

F000:7D3E
E000:7D3E
D000:7D3E
default:7D3E (DEF SEG on its own restores to DGROUP, this is the
real character of the string)

and why should they be the same character? They are different places in
memory.

So your code has not concluded anything about SADD.
All it has shown is that PEEK works as documented.

Stephen Howe


Tom Lake

unread,
Nov 13, 2005, 3:09:50 PM11/13/05
to
> Your proof is completely and utterly WRONG!!!
> All you have proved is that PEEK is affected by DEF SEG changes (which is
> known, that is what the documentation says)
>
> This line here
>
> b$ = CHR$(PEEK(SADD(a$)))
>
> does not conclude that SADD does not work if DEF SEG is changed.
> The value that SADD returns is unchanged by DEF SEG values, but the
> character value at the segment:offset in question is what PEEK returns and
> that depends entirely on the DEF SEG value.

You're right. Still, SADD isn't of much use when the segment has changed,
is it?

Tom Lake


Michael Mattias

unread,
Nov 13, 2005, 4:48:26 PM11/13/05
to
"Tom Lake" <tl...@twcny.rr.com> wrote in message
news:JsKdf.1348$JQ....@twister.nyroc.rr.com...

> >> I wonder if SADD still "works" in Qbasic if a DEF SEG to other than
the
> > default data segment is in effect? <
>
> In QBasic, SADD does NOT work if the default DEF SEG
> is changed. Here's the proof again:..


Whoa, you mean my WAG turned out to be right? Really?

I was just positing something 'odd, but possibly related'.....

I think I'm starting to scare myself!

MCM

Tom Lake

unread,
Nov 13, 2005, 6:57:47 PM11/13/05
to
>> In QBasic, SADD does NOT work if the default DEF SEG
>> is changed. Here's the proof again:..
>
>
> Whoa, you mean my WAG turned out to be right? Really?
>
> I was just positing something 'odd, but possibly related'.....
>
> I think I'm starting to scare myself!

Actually, I was wrong. Mark this day on your calendar!
It's PEEK and POKE that are affected by the DEF SEG
but SADD still returns the string address.

Tom Lake


Stephen Howe

unread,
Nov 13, 2005, 7:14:30 PM11/13/05
to
> You're right. Still, SADD isn't of much use when the segment has changed,
> is it?

SADD is basically for immediate use.
If you do

a$ = "Hello"
b$ = a$
aaddr& = SADD(a$)
b$ = b$ + " World"
'Can aaddr& be used here???

all bets are off as to aaddr& being the address of a$.
It was. And it may still be.
But that b$ addition inbetween could have a rearrangement of strings in
memory if memory was tight. BASIC does do that, same as FRE("").

If DEF SEG points to something other than DGROUP, it will need to reset back
for results of SADD to be used.

Stephen Howe

a...@spamaway.com

unread,
Nov 25, 2005, 1:53:51 PM11/25/05
to
> > FOR X% = 1 TO 1000
> > Y% = 3
> > NEXT

Yes, is it just me, or is the above code absolute nonsense and does not do
anything? (I'm no expert).

Tom Lake

unread,
Nov 25, 2005, 2:34:10 PM11/25/05
to
<a...@spamaway.com> wrote in message news:43875DBF...@spamaway.com...

Yes, the code is absolute nonsense as written, but it was intended to be a
simplistic
example of how some compilers optimize. Ethan Winer, the author of the
above, is actually
an excellent programmer who wouldn't do such a thing in his own real-world
code. In fact
he wrote the book on BASIC techniques that is still used today as the Bible
of BASIC
programming. He has generously made it available for download on his
Website for free!

http://www.ethanwiner.com/

Tom Lake


Ethan Winer

unread,
Nov 26, 2005, 2:21:04 PM11/26/05
to
Tom,

Hey thanks!

Just today I had to modify a program I wrote a few months ago, and I used a
GOTO to skip over something or other. The block of code I wrote - and that
was before this thread started - was exactly as follows:

IF Exist%(Dest$ + Path$ + LEFT$(LogFile$(X), 8) + ".DAT") THEN
GOTO DoNext 'IF you don't like my GOTO THEN call 1-800-BYTE-ME
END IF

Note that nobody will ever see this code, and that comment was just a joke
to myself.

--Ethan


Norman L. DeForest

unread,
Nov 26, 2005, 2:34:16 PM11/26/05
to

On Sat, 26 Nov 2005, it was written:

Spotted on the back of a power wheelchair:
"If you don't like the way I drive, call 1-800-GET-LOST"

Essentially *all* constructs for changing program flow, regardless of the
programming language used, get compiled into jumps, calls or returns for
most[1] processors anyway.

[1] Wasn't it the 1802 that could switch program counters to let you
flip back and forth between two or more different processes and
continue in each process where you left off in it? SEP was the
mnemonic if my memory is correct.
--
Norman De Forest http://www.chebucto.ns.ca/~af380/Profile.html
"> Is there anything Spamazon DOESN'T sell?
Clues. The market's too small to justify the effort."
-- Stuart Lamble in the scary devil monastery, Fri, 13 May 2005

ararghmai...@now.at.arargh.com

unread,
Nov 26, 2005, 4:27:41 PM11/26/05
to

I have no problem with the comment, as I do similar.

BUT, you do realize that that code will generate an extra
(unnecessary) JMP, don't you? (in PDS & BCET, anyway)

Coded as:

REM IF you don't like my GOTO THEN call 1-800-BYTE-ME
IF Exist%(Dest$ + Path$ + LEFT$(LogFile$(X), 8) + ".DAT") GOTO DoNext

gets rid of the extra instruction.

Not that it really matters as the IF <conditional> code is 20
instructions in BCET and 26 or so in PDS. :-)

L...@goforit.net

unread,
Nov 26, 2005, 8:23:55 PM11/26/05
to
"Norman L. DeForest" wrote:

As I recall the very ancient IBM 360 had 4 sets of registers for that kind of
thing.
A programmer used "privileged" instructions to get the registers to switch.

Lou


ararghmai...@now.at.arargh.com

unread,
Nov 26, 2005, 8:42:45 PM11/26/05
to
On Sun, 27 Nov 2005 01:23:55 GMT, L...@GoForIt.net wrote:

<snip>


>
>As I recall the very ancient IBM 360 had 4 sets of registers for that kind of
>thing.
>A programmer used "privileged" instructions to get the registers to switch.

Nope.

No IBM/360 or IBM/370 that I know of had anything like that.

16 32-bit registers, that was all they had. Plus 1 or 2 that were not
accessible to the programmer directly, such as the IP. Some
instructions were privileged - mainly I/O and memory protection and
such like.

It is loading more messages.
0 new messages