Re: A little Rolodex [revised]



[major revision - a little bigger, but a lot better :]

Description:

These programs provide a fairly efficient "Rolodex" card system,
including alpha sort and searching for any embedded string,
using minimal programming, for the following calculators:

49 series: HP49G, HP49G+, HP48Gii, and ???
48 series: HP48G, HP48GX, HP48G+

Where other similar applications tend to use
a large single "list of strings" for their database,
we instead use a directory of small ordinary variables
(each a string), which makes our programming
easy and efficient (or at least different :)

The original version was smaller (under 600 bytes),
but left storing values to the user, and left browsing
of results to the Interactive Stack application.

We now have a complete record browsing interface
and new Sto/Rcl/Purge functions that use the record itself
as the argument, bringing the basic package to about 1K bytes.

The larger directory below includes extras like Import/Export,
case-insensitive Find, and alternative SysRPL source programs,
but its core is still about 1K -- possibly still the smallest
efficient Rolodex app, based upon a unique storage method.

Data storage concept:

In this application, a database is a directory,
and each individual database record is stored
in its own variable, as a 'key' and "data" pair, i.e.
the first NN bytes of each record are used as an index key
(this leading part must be unique within one database),
and the remainder of the string is stored in that name.

This keeps overhead low, reduces individual record
processing to simple, small and fast operations,
and facilitates sorting and browsing search results
without having to sort the database itself.

Each separate database should be a subdirectory
*under* this program directory (example included).

User manual:

The following functions are conveniently presented
in a CUSTOM menu (displayed by pressing CUSTOM or CST
on the keyboard):

Search:

Find ( "string" -> all records containing the string )
All ( -> all items in database )

Store/recall/purge:

Sto ( "record" -> store new item [key must be unique] )
Rcl ( "record" -> recall [matching key] from database )
Purg ( "record" -> purge [its key] from database )

Optional and extended functions:

Nn ( Key length: built-in default is 20 )
Split ( "record" <-> "data" 'key' [for store/recall] )
Impo ( Import { list of strings } from other databases )
Expo ( Export all as { list of strings } )
Svars ( List all string variables, i.e. 2 TVARS )

The 'Browse' method of reviewing records uses CHOOSE to list and
select matching record keys, and SCROLL to view whole records;
upon exit, it leaves the last viewed record on the stack,
ready for editing or purging if desired.

If the original 'Dump' method is used instead of the 'Browse'
method, then matched records are simply returned to the stack,
where you can review them using the Interactive Stack
application (press "up arrow" and observe the menu),
thus obviating the need for any additional programming
to view and navigate thru the list of matched records.

The normal argument for our own Sto/Rcl/Purge commands
is an entire record (string), *not* a variable name
(although actually these will act much like the built-in
commands if you happen to supply a variable name instead)

To use a database:

o Navigate into the directory containing the records
(e.g. into the 'Sample' directory already provided)
o Press the CUSTOM function (or CST key) on the keyboard.

To search and review records:
o Enter "search string" on stack, then press [Find].
o Alternatively, press [All] to retrieve all records.
o To jump to a given first letter or letters,
press the given letter(s) after the Alpha key.

To add a new record:
o Create a new string, or edit an initially empty string.
o With edited record (string) on level 1, press [Sto]
o An "Object in use" error indicates a duplicate key;
press [Rcl] to retrieve existing record
or [Purg] to delete existing record,
so that a new record can be stored in its place.

To purge an existing record:
o With actual record (string) on level 1, press [Purg]
(note that the record contents remain on level 1)

To edit an existing record:
o With actual record (string) on level 1, press [Purg]
(note that the record contents remain on level 1)
o Then edit the record, finally press [Sto] to store it.
o An "Object in use" error indicates a duplicate key;
press [Rcl] to retrieve existing record
or [Purg] to delete existing record,
so that the edited record can be stored in its place.

You can create multiple versions of records
(without purging) if you make the keys unique,
or if you store each version with a different key length
(many functions will accept an optional numeric key length
as an additional argument on level 1; the value will be
stored and will remain in effect until changed again;
the [Nn] menu key also recalls or stores the key length).

To create a new database:

o Go to the Rolodex program directory
(under which the 'Sample' database is now stored)
o Type a database 'name' and then CRDIR
o Press the VAR menu key just created
(the VAR menu will at first be empty).
o Start entering new records.

Use PGDIR to purge an existing database.

Import and Export to other similar databases:

Since various similar databases
often take the form of a single list of strings,
we provide an optional "Import" function,
to assimilate any such list of strings from the stack
into the current Rolodex "directory" database,
and an "Export" function to do the reverse.

That's all there is to it!
(source program comments may go into further detail)

About the VAR menu:

o Do not use the VAR menu at all!
o Records can not be directly stored or recalled!

The "Review" key (right-shifted down arrow)
shows approximately what each variable contains;
if you can't resist wanting to know exactly
what some stored record actually contains, then:

Right-shift [menukey] (recall the "data" portion), *then*
Ans[LastARG] (recall the corresponding 'key' portion), *then*
[Split] (or SWAP +) to concatenate 'key' and "data"

HP48G[X][+] considerations:

The "Small" Edit (and Stack) display modes on HP49 series
enable editing and viewing larger records without extra apps;
for HP48, you may wish to use add-on applications such as
JYA's StringWriter, MiniWriter, or [T]ED and VV (Jazz,EDVV):

http://www.hpcalc.org/search.php?query=string+writer (+Mini)
http://www.hpcalc.org/search.php?query=ed+vv
http://www.hpcalc.org/search.php?query=goodies+10 (EDVV,VV33)
http://www.hpcalc.org/search.php?query=goodies+11 (TED)

A full-screen CHOOSE program enables HP48G series users
to review more data at a time when selecting records
(setting flag -90 may suffice on 49 series calculators);
you can get full-screen CHOOSE programs from
http://groups.google.com/group/comp.sys.hp48/msg/8888908f27145901?dmode=source
which can be invoked by changing the word CHOOSE
in the 'Brow' program to either CHOOSF or CHOOSM.

Sample timings: Searching 100 randomly generated
100-byte records (total database size slightly under 11K)
and "dumping" 16 found records in sorted order to the stack
took 6-8 seconds on HP48GX or HP49G with UserRPL 'Find'
or 3-4 seconds with SysRPL 'Find' --
on HP49G+ it should be so fast as to make little difference
(FWIW, timings are on Emu48 "authentic speed," not real calcs).

BTW, directories containing only strings can be transferred
between HP48 and HP49 series (use OBJFIX or FIXIT/FIXOB after
*binary* transfer -- note that ascii transfer will *not* work).

The UserRPL and SysRPL source below is compatible
with all HP48G/49G series calculators
(the SysRPL 'Match' should even work on HP48S[X], FWIW :)

This version permits case-insensitive searches,
using the [included] UCASE program from Goodies Disk #7:
http://www.hpcalc.org/search.php?query=goodies+7
(translates [a-z] only, not international accented chars);
you may optionally provide any alternate UCASE program,
such as from "Donnelly's Tool Kit" (if you have it).

Programs:

The source directory contains the basic UserRPL version,
plus additional variables containing alternate
SysRPL source; case-insensitive searching requires
replacing the original dummy UCASE
with the appropriate compiled binary.

To trim down the supplied directory to the 1K or so
that is actually necessary, the sample database
and extra source files should eventually be discarded,
along with any unused Dump, Expo and Impo programs.

The sample database was contrived to be representable
in ascii form, but user databases *must* be backed up
in *binary* form, because the variable names will be
arbitrary strings, and can not be re-compiled from ascii
source as 'any old string' (invalid syntax would result).


%%HP: T(3)F(.); @ Header for file transfer only

DIR @ Directory starts

@ Sample database (contains newline characters: 010)
@ Search "00" vs. "25" vs. "50" vs. "0" Etc.
Sample DIR
Brown ", Anna\010 909-555-5001"
Jones ", Kate\010 877-555-2550"
Smith ", Thom\010 212-555-0025"
Nn 5. @ Key length (impractically short, for demo only)
END

\173\173\173 TEXT @ '---' cosmetic separator (or hider)

@ Display all items containing the arg string
Find \<< Match SORT Brow \>> @ Use either 'Brow' or 'Dump'
@ We always sort, assuming a reasonable number of matches;
@ therefore you don't need to keep the database itself sorted.

@ Display all items in directory
All \<< Svars Brow \>> @ Use either 'Brow' or 'Dump'
@ Here we don't sort -- in case the database is very large, it
@ might be more productive to occasionally VARS ORDER instead.

Rcl @ 'id' -> "record"
@ "new" -> "new" "old" [having same ID]
@ ... NN -> first store NN as key length
\<< Ff Gid DUP
IFERR RCL THEN DROP2 ERRN DOERR ELSE + END \>>

Sto @ "data" 'id' ->
@ "record" ->
@ ... NN -> first store NN as key length
@ Disallow duplicate key
\<< Nnck DUP TYPE 6. \=/
{ \->STR Split } IFT DUP @Here@ VTYPE -1. >
{ SWAP + 9. DOERR } IFT STO \>>

Purg @ 'id' -> [purged]
@ "record" -> "record" [record with same ID is purged]
@ ... NN -> first store NN as key length
\<< Gid PURGE \>>

Nn 20. @ Default key length (used when storing new records)
@ Must not exceed 127 (but ought to be 20 or less anyway)
@ (see also the 'Nnck' function)

Split @ "string" <-> "data" 'key' (ready to STOre)
@ ... NN -> first store NN as key length
@ Disallow empty record
\<< Nnck DUP TYPE 6. == { SWAP + } @ unsplit
{ \->STR DUP SIZE NOT { 9. DOERR } IFT
DUP Nn 1. + OVER SIZE SUB SWAP 1. Nn SUB
#5B15h SYSEVAL } IFTE \>> @ same address on 49/48
@ Caution - back up memory before using SYSEVAL!

Svars \<< 2. TVARS \>> @ List all vars containing strings
@ (a SysRPL alternative for HP48G[X][+] is supplied later)

@ A stored program named UCASE
@ must be available (via 'UCASE RCL') in the current path:
UCASE QUOTE @ "dummy" program (does nothing)
@ Compile and store an actual program of your choice
@ (it *must* start with a "CK1" type SysRPL command,
@ because it will be evaluated using EvalNoCK)

@ Custom menu (appears in all subdirectories)
CST 'Cstm' Cstm { Find All Rcl Sto Purg Nn
Split Impo Expo Svars UCASE }
@ Customize the above, according to calc model
@ and optional components.

@ "string" -> { matching record IDs }
@ Note that this version requires separate UCASE
@ (a faster SysRPL alternative is supplied later)
Match \<< \->STR UCASE \-> s \<< Svars
IF DUP SIZE THEN { } SWAP 1. \<< DUP DUP RCL +
UCASE s POS NOT { DROP } IFT \>> DOSUBS END
DUP SIZE { SWAP DROP } IFT \>> \>> @ 142.5 bytes

Brow @ Alternative to 'Dump'
@ { list } -> Browse, leave last viewed record on stack
@ SCROLL is only in 49 series; on 48GX series,
@ try VV DROP (Jazz/EDVV) or [T]ED
@ or #42D32h SYSEVAL DROP (built-in 48GX editor)
\<< DUP SIZE NOT { DROP "No matches" DOERR } IFT
@ You could *omit* the line above if you use your own
@ CHOOSE based on ROMPTR B3 0 (with zero arg);
@ this makes for a nicer, more uniform interface
@ (an empty CHOOSE list signifying no matches)
Ff 1. DO DUP2 "Select" ROT ROT CHOOSE CLLCD
{ SWAP DROP DUP2 DUP RCL + SCROLL SWAP POS 0. }
@ Uncomment stuff below as an alternative to above DOERR
{ @IFERR@ GET DUP RCL + @THEN DROP2 END@ 1. }
IFTE UNTIL END \>>

Gid @ 'id' -> 'id'
@ "record" -> "record" 'id'
@ ... NN -> first store NN as key length
\<< Nnck DUP TYPE 6. \=/
{ \->STR DUP Split SWAP DROP } IFT \>>

Nnck @ ... NN -> first store NN as key length
\<< QUOTE { 0. 28. } OVER TYPE POS NOT { ::Nn RCL }
IFT 5. MAX 5. SQ MIN 'Nn' STO \>>
@ 5 to 25, current value saved in 'Nn'
@ 127 is absolute max permitted by operating system.
@ Each database (subdirectory) can have its own value.

@ Enable Last Args (needed to predict IFERR results)
Ff \<< 4. EXP NEG CF \>> @ Flag -55

@ Optional features - may be purged if not in use:

Dump @ OMIT if 'Brow' is included and 'Expo' is omitted
@ { list } -> Dump all records (and original list) to stack
@ Use "Interactive Stack" application as viewer.
\<< DUP SIZE { 1. OVER SIZE FOR n
DUP n GET DUP RCL + SWAP NEXT } IFT \>>

@ Export (optional additional function):
@ All records in current directory -> { list of strings }
Expo \<< Svars Dump SIZE \->LIST \>>
@ You may optionally append any saved ~DUPS list (from Import).
@ If you need a sorted export, either first perform Svars ORDER
@ or perform SORT after creating the export list.

@ Import (optional additional function):
@ { list of strings } -> store all in current directory
@ Any records with duplicate keys
@ are appended to a list saved in '~DUPS'
@ ... NN -> first store NN as key length
Impo \<< Ff Nnck
{ } '~DUPS' IFERR @Here@ RCL THEN STO ELSE DROP2 END
LIST\-> IF DUP THEN 1. SWAP START IFERR Sto
THEN '~DUPS' STO+ END NEXT ELSE DROP END \>>

@ Faster SysRPL (source), for large databases
@ (compile, replace UserRPL functions, then discard source)

Matc.s @ 'Match' (using separate UCASE) for all HP48/49
":: CK1NOLASTWD CK&DISPATCH1 THREE
:: CONTEXT@ LastNonNull NOTcasedrop NULL{} SWAP'
ID UCASE XEQRCL DUPTYPECOL? NOTcase SETTYPEERR
SWAPOVER EvalNoCK ZEROZEROTWO DOBIND
ZEROSWAP BEGIN :: CK&DISPATCH0
THREE :: DUP RAM-WORDNAME DECOMP$ OVER &$
2GETLAM EvalNoCK 1GETLAM ONE POS$ #0=?SEMI
DUP RAM-WORDNAME UNROT SWAP#1+SWAP ; ZERO NOP ;
PrevNonNull NOT_UNTIL ABND {}N ; ;
" @ To be compiled (131 bytes)

Svar.s @ 'Svars' for HP48[S/G][X][+] (not needed on 49 series)
":: CK0NOLASTWD CONTEXT@ LastNonNull NOTcase NULL{}
ZEROSWAP BEGIN :: CK&DISPATCH0 THREE
:: DUP RAM-WORDNAME UNROT SWAP#1+SWAP
; ZERO NOP ; PrevNonNull NOT_UNTIL {}N ;
" @ To be compiled (60 bytes, replaces slow 2 TVARS).

@ Fast VARS and ORDER for HP48[S/G][X][+]:
@ http://www.hpcalc.org/details.php?id=2787
@ http://www.hpcalc.org/hp48/utils/misc/fastvars.zip
@ http://www.hpcalc.org/details.php?id=2453
@ http://www.hpcalc.org/hp48/utils/memory/ord.zip

@ For case-insensitive matching:

@ The versions below translate [a-z] only
@ (not international accented characters)

@ Credit for the UCASE versions below:
@ Copyright 1991 Brian Maguire, All Rights Reserved
@ http://www.hpcalc.org/hp48/compilations/horn/horn7.zip

UCA48.s @ Jazz version for HP48
"( UCASE with SASM syntax )
::
CK1NOLASTWD
CK&DISPATCH1
THREE
::
CKREF
CODE
GOSBVL =SAVPTR
A=DAT1 A
D1=A
D1=D1+ 5
A=DAT1 A
A=A-CON A,5
B=0 W
B=A A
BSRB
D1=D1+ 3
each D1=D1+ 2
B=B-1 A
GOC fin
A=DAT1 B
LC(2) #61
?C>A B
GOYES each
LC(2) #7A
?C<A B
GOYES each
LC(2) #20
A=A-C B
DAT1=A B
GOTO each
fin GOVLNG =GETPTRLOOP
ENDCODE
;
;
" @ To be compiled (68 bytes)

UCA49.s @ MASD version for HP49
"( UCASE with MASD syntax )
::
CK1NOLASTWD
CK&DISPATCH1
THREE
::
CKREF
CODE
GOSBVL =SAVPTR
A=DAT1 A
D1=A
D1+5
A=DAT1 A
A-5 A
B=0 W
B=A A
BSRB.W
D1+3
*each D1+2
B=B-1 A
GOC fin
A=DAT1 B
LC 61
?C>A B -> each
LC 7A
?C<A B -> each
LC 20
A=A-C B
DAT1=A B
GOTO each
*fin GOVLNG =GETPTRLOOP
ENDCODE
;
;
" @ To be compiled (68 bytes)

END @ Directory ends
.