Mailing List Archive: 49091 messages
  • Home
  • Script library
  • AltME Archive
  • Mailing list
  • Articles Index
  • Site search
 

[REBOL] Re: Switch with refinements/hmmm!

From: joel:neely:fedex at: 2-Nov-2001 15:58

Hi, all, ... Joel Neely wrote:
> Incidentally, I'm working up a set of benchmarks on all of the > variations discussed in this thread; I'll publish results later > today. >
As promised, here are some comparisons. I took a bunch of different variations as discussed in this thread, made a sub- version of each one for 2, 3, and 4 refinements, and did some timings. The text table below gives some key tradeoff points and reports times. The test harness called each version of each variation with all possible combinations of refinements, and did so enough times to get vaguely stable net times. Since I didn't have time to run the overall test multiple times and average the results, all numbers should be viewed as accurate +/- two seconds (or worse). times vs. refinements ------------ 0? >1? code run mod flex 2 3 4 rs-either y n lin lin m hi m hi 12 26 54 rs-ifelse y n lin lin m hi m hi 13 27 55 rs-anyall1 y n lin lin m hi med 13 27 55 rs-if1 n n lin lin m hi m hi 17 38 80 rs-true n n lin lin v hi v hi 19 41 92 rs-pif n n lin lin v hi v hi 51 109 226 rs-ifelsetree y y exp lin m lo v hi 14 29 63 rs-eithertree y y exp lin m lo v hi 13 29 61 rs-alln n y lin lin v hi med 13 30 72 rs-anyalln y y exp exp m lo med 13 30 72 rs-anyparn y y exp exp med med 14 31 74 rs-anyifn y y exp exp med med 15 35 92 rs-ifn y y lin lin m hi v hi 22 49 109 rs-selwds y y exp exp m lo v lo 24 51 118 rs-combin y y lin lin med m lo 33 75 167 rs-selstr y y exp exp m lo v lo 35 79 178 rs-macro y y lin lin v hi m hi 35 80 180 rs-combo y y lin lin med v lo 66 152 337 rs-selblk y y exp exp m lo v lo 37 134 494 The "0?" column tells whether an approach explicitly handles the case where no refinements are set. The ">1?" column tells whether an approach handles more than one refinement set per call. The "code" column tells whether the source code for an approach increases linearly or exponentially with the number of refinements. The "run" column tells whether the execution time for an approach increases linearly or exponentially with the number of refinements. The "mod" column is my subjective impression of how easy the code was to modify as the number of refinements increased. The "flex" column is my subjective impression of how flexible the code would be if different actions needed to be taken for the various tested cases/combinations. The timings are in seconds. Note however that increasing the number of refinements doubles the number of combinations possible. The benchmark put every routine through all possible combinations; the following table shows timings normalized to remove that factor. 2 3 4 rs-either 3.00 3.25 3.38 rs-ifelse 3.25 3.38 3.44 rs-anyall1 3.25 3.38 3.44 rs-if1 4.25 4.75 5.00 rs-true 4.75 5.13 5.75 rs-pif 12.75 13.63 14.13 rs-eithertree 3.25 3.63 3.81 rs-ifelsetree 3.50 3.63 3.94 rs-alln 3.25 3.75 4.50 rs-anyalln 3.25 3.75 4.50 rs-anyparn 3.50 3.88 4.63 rs-anyifn 3.75 4.38 5.75 rs-ifn 5.50 6.13 6.81 rs-selwds 6.00 6.38 7.38 rs-combin 8.25 9.38 10.44 rs-selstr 8.75 9.88 11.13 rs-macro 8.75 10.00 11.25 rs-combo 16.50 19.00 21.06 rs-selblk 9.25 16.75 30.88 Descriptions/code for each of the tested alternatives appears below. The functions were all written to append a string to a global variable instead of printing (to avoid making the whole benchmark I/O bound). Only the version with three refinements is shown for each algorithm. RE-EITHER and RS-IFELSE are conventional if-elseif-elsif-... "ladders" that take the first successful test, and have an else clause for no matches. They only differ in the use of EITHER versus IF/ELSE. rs-either-3: func [/refa /refb /refc][ either refa [ append rs-result "ref A" ][either refb [ append rs-result "ref B" ][either refc [ append rs-result "ref C" ][ append rs-result "NO ref" ]]] ] rs-ifelse-3: func [/refa /refb /refc][ if/else refa [ append rs-result "ref A" ][if/else refb [ append rs-result "ref B" ][if/else refc [ append rs-result "ref C" ][ append rs-result "NO ref" ]]] ] RS-ANYALL1 uses the ANY/ALL combination, bailing out after the first true branch is found, with a default for no true option. rs-anyall1-3: func [/refa /refb /refc] [ any[ all [refa append rs-result "ref A"] all [refb append rs-result "ref B"] all [refc append rs-result "ref C"] append rs-result "NO ref" ] ] RS-IF1 uses successive IF tests, exiting as soon as one is selected. This version doesn't have a default case, but it would be easy to add. rs-if1-3: func [/refa /refb /refc][ if refa [append rs-result "ref A" exit] if refb [append rs-result "ref B" exit] if refc [append rs-result "ref C" exit] ] RS-TRUE uses the "inside-out-SWITCH" trick to select the first branch where the associated word has a specified constant value. rs-true-3: func[/refa /refb /refc][ switch true reduce [ ; needs a value refa [append rs-result "ref A"] refb [append rs-result "ref B"] refc [append rs-result "ref C"] ] ] RS-PIF uses Ladislav's polymorphic IF construction, with no default. rs-pif-3: func[/refa /refb /refc][ pif [ refa [append rs-result "ref A"] refb [append rs-result "ref B"] refc [append rs-result "ref C"] ] ] RS-EITHERTREE and RS-IFELSETREE use the most obvious structure -- a fully-populated binary decision tree. rs-eithertree-3: func [/refa /refb /refc] [ either refa [either refb [either refc [append rs-result "all refs are set!"] [append rs-result "ref A & B"]] [either refc [append rs-result "ref A & C"] [append rs-result "ref A"]]] [either refb [either refc [append rs-result "ref B & C"] [append rs-result "ref B"]] [either refc [append rs-result "ref C"] [append rs-result "NO refs"]]] ] rs-ifelsetree-3: func [/refa /refb /refc] [ if/else refa [if/else refb [if/else refc [append rs-result "all refs are set!"] [append rs-result "ref A & B"]] [if/else refc [append rs-result "ref A & C"] [append rs-result "ref A"]]] [if/else refb [if/else refc [append rs-result "ref B & C"] [append rs-result "ref B"]] [if/else refc [append rs-result "ref C"] [append rs-result "NO refs"]]] ] RS-ALLN uses a series of ALL clauses to test each refinement in order, thus handling multiple true branches (but no default). rs-alln-3: func [/refa /refb /refc][ all [refa append rs-result "ref A"] all [refb append rs-result "ref B"] all [refc append rs-result "ref C"] ] RS-ANYALLN yses the full-blown ANY/ALL to test every possible combination of refinement values. Note that it is order-sensitive. rs-anyalln-3: func [/refa /refb /refc] [ any[ all [refa refb refc append rs-result "all refs are set !"] all [refa refb append rs-result "ref A & B"] all [refa refc append rs-result "ref A & C"] all [refb refc append rs-result "ref B & C"] all [refa append rs-result "ref A"] all [refb append rs-result "ref B"] all [refc append rs-result "ref C"] append rs-result "NO ref" ] ] RS-ANYPARN uses the "parens-in-an-all" trick to avoid short-circuit exit of a selected branch. rs-anyparn-3: func [/refa /refb /refc] [ any[ all [refa refb refc (append rs-result "all refs are set !")] all [refa refb (append rs-result "ref A & B")] all [refa refc (append rs-result "ref A & C")] all [refb refc (append rs-result "ref B & C")] all [refa (append rs-result "ref A")] all [refb (append rs-result "ref B")] all [refc (append rs-result "ref C")] append rs-result "NO ref" ] ] RS-ANYIFN uses an ANY to bail out after a branch has been chosen, but uses IF for the individual branches to avoid the short-circuit exit issue with ALL. This is a more generic version of RS-ANYPARN. rs-anyifn-3: func [/refa /refb /refc] [ any[ if all [refa refb refc] [append rs-result "all refs are set !"] if all [refa refb] [append rs-result "ref A & B"] if all [refa refc] [append rs-result "ref A & C"] if all [refb refc] [append rs-result "ref B & C"] if all [refa] [append rs-result "ref A"] if all [refb] [append rs-result "ref B"] if all [refc] [append rs-result "ref C"] append rs-result "NO ref" ] ] RS-IFN uses a sequence of IF tests, wrapped in an overall test to check for no true branches. rs-ifn-3: func [/refa /refb /refc][ either any [refa refb refc] [ if refa [append rs-result "ref A"] if refb [append rs-result "ref B"] if refc [append rs-result "ref C"] ][ append rs-result "NO ref" ] exit ] RS-SELWDS creates a "key" block with the values of all refinements, and uses that to SELECT/ONLY a value to be printed. The last non- trivial line uses LOAD MOLD/ONLY REDUCE to get words instead of NONE! or LOGIC! values in the key block. rs-selwds-3: func [/refa /refb /refc] [ append rs-result select/only [ [none none none] "no refs" [true none none] "ref A" [none true none] "ref B" [true true none] "ref A & B" [none none true] "ref C" [true none true] "ref A & C" [none true true] "ref B & C" [true true true] "all refs" ] load mold/only reduce [refa refb refc] ] RS-COMBIN is a hand-optimized version of RS-COMBO. See that version for a description. rs-combin-3: func [/refa /refb /refc /local r n limit] [ r: make string! 6 n: 0 limit: 3 if refa [append r " A" n: n + 1] if refb [append r " B" n: n + 1] if refc [append r " C" n: n + 1] append rs-result switch/default n reduce [ 0 ["NO refs"] limit ["all refs are set !"] ][join "ref" r] ] RS-SELSTR creates a key value (as with RS-SELWDS) but the key is a string instead of a block. rs-selstr-3: func [/refa /refb /refc] [ append rs-result select [ "" "no refs" "a" "ref A" "b" "ref B" "ab" "ref A & B" "c" "ref C" "ac" "ref A & C" "bc" "ref B & C" "abc" "all refs" ] to-string replace/all reduce [ all [refa "a"] all [refb "b"] all [refc "c"] ] none "" ] RS-MACRO uses a special-purpose version of SWITCH to locate and evaluate the appropriate block of expressions. .rsmacro: func [block /default if-nothing /local todo] [ todo: clear [] foreach [word then-do] block [ if get word [append todo then-do] ] if empty? todo [append todo if-nothing] copy todo ] rs-macro-3: func [/refa /refb /refc /local todo] [ todo: .rsmacro/default [ refa [append rs-result "is A"] refb [append rs-result "is B"] refc [append rs-result "is C"] ] [append rs-result "nothing to do"] do todo ] RS-COMBO uses a special-case function to test each refinement in order, constructing a string with labels for all true cases. It prints the result, but with special handling for all and none true. rs-combo-3: func [/refa /refb /refc /local r n limit check] [ r: copy [] n: 0 limit: 3 check: func [opt label] [ if opt [repend r [pick ["&" ""] (n: n + 1) > 1 label]] ] check refa "A" check refb "B" check refc "C" append rs-result switch/default n reduce [ 0 ["NO refs"] limit ["all refs are set !"] ][join "ref " form r] ] RS-SELBLK uses multi-layer REDUCEtion to ensure that the key block and the selector blocks are all constructed with NONE! or LOGIC! values. rs-selblk-3: func [/refa /refb /refc] [ append rs-result select/only reduce [ reduce [none none none] "no refs" reduce [true none none] "ref A" reduce [none true none] "ref B" reduce [true true none] "ref A & B" reduce [none none true] "ref C" reduce [true none true] "ref A & C" reduce [none true true] "ref B & C" reduce [true true true] "all refs" ] reduce [refa refb refc] ] My thanks to all who contributed ideas. My apologies for not having enough time to fully credit each idea to the appropriate contributors, and for any errors I may have made in rendering your ideas. -jn- -- This sentence contradicts itself -- no actually it doesn't. -- Doug Hofstadter joel<dot>neely<at>fedex<dot>com