Forum OpenACS Q&A: Coding: db vs tcl: Fastest Random Loop

I have a tcl web page and want to present 7 items consisting of a sentence or two of text with a small picture that will be rendered in a column. I want to rotate and randomize the 7 items every time a user comes back or refreshes the page.

I know I could stuff the 7 pieces of html text into a database table and use
...order by random()
to have different items come to the top.

*** Is there an eqivelent way of doing this with just tcl code without going to the database and looping through the 7 items?

My reason for this is to reduce the amount of time to render the page as it already is dynamic and hits the db for other user stuff.

*** Is this hitting the db vs tcl code relevant or a non-issue?

-Bob

Collapse
Posted by Jerry Asher on
I think the db vs tcl code issue is very relevant. I'd probably keep the items in the db for persistence and let the server pull them out and cache them in an nsv every few hours or whatever is appropriate. I'd select them at random with ns_rand.
Collapse
Posted by MaineBob OConnor on

Hi Jerry,

My requirement is for the order to be different "instantly" so that if the user refreshes or comes back to this page in 5 minutes, she sees the differently ordered items. What I was hoping for is something like:

set final_text [random [list $txt1 $txt2 $txt3 $txt4 $txt5 $txt6 $txt7]]
with the txt1, txt2... sets in the tcl code.

Also, does it make a difference in speed and caching if the text is in the /tcl area vs the /www area? Do I understand correctly that stuff in /tcl is loaded into memory once at aolserver startup?

-Bob

Collapse
Posted by Jerry Asher on
It sounds as though you want a random draw from a deck without replacement, as opposed to just a random number.  That's a bit more difficult to describe while my girls eat their breakfast, so I'll let you implement that one on your own.

You might consider however, do you really require the displays to be randomly arranged on each presentation, or do you just want them to be different?  If the latter, you could have your server predetermine the display orders just once, and then use cookie technology or what have you to determine where in the sequence each visitor is.  That might be less cpu intensive (although neither is probably a big deal.)

And yes, procs in your tcl lib (usually /tcl) persist through each connection.  The tradeoff is that the cumulative total of all your /tcl procs makes aolserver a bit bigger and a bit slower to start each thread.

Collapse
Posted by MaineBob OConnor on

Ok for you lurkers, here is the code that I ended up using.

set rand_num [ns_rand 6]

if { $rand_num == 0 } {
  set the_text "$txt1 $txt2 $txt3 $txt4 $txt5 $txt6"
} elseif {
     $rand_num == 1 } {
  set the_text "$txt2 $txt3 $txt4 $txt5 $txt6 $txt1"
} elseif {
     $rand_num == 2 } {
  set the_text "$txt3 $txt4 $txt5 $txt6 $txt1 $txt2"
} elseif {
     $rand_num == 3 } {
  set the_text "$txt4 $txt5 $txt6 $txt1 $txt2 $txt3"
} elseif {
     $rand_num == 4 } {
  set the_text "$txt5 $txt6 $txt1 $txt2 $txt3 $txt4"
} else {
  set the_text "$txt6 $txt1 $txt2 $txt3 $txt4 $txt5"
}

This code just rotates the show order and is not truly random. But for now, it does the job, doesn't hit the database and seems fast.

-Bob

Collapse
Posted by Jonathan Ellis on
You can make it a lot cleaner if you read your seven (6?) items into an array txt(0..$n_txt), then doing
proc_doc rotated_index { i n max } {
    returns index $i rotated $n places out of $max
} {
    return [expr ($i + $n) % $max]
}

set offset [ns_rand $n_txt]
for { set i 0 } { $i < $n_txt } { incr i } {
    append the_text $txt([rotated_index $i $offset $n_txt])
}
Collapse
Posted by Michael A. Cleverly on
There are n! ways to order n-items. For six items then, 6! equals 720 unique combinations. Your if code above enumerates 6, excluding the other 714 possibilities.

The following Tcl proc will "randomize" a list of elements for you:

proc randomize_list {original} {
    set randomized [list]
    set size [expr {[llength $original] + 1}]
 
    while {[incr size -1]} {
        set i [expr {int(rand() * $size)}]
        lappend randomized [lindex $original $i]
        set original [lreplace $original $i $i]
    }
 
    return $randomized
}