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

Perl vs Python vs Ruby.... vs Tcl?

211 views
Skip to first unread message

fro...@gmail.com

unread,
Sep 11, 2007, 9:51:44 AM9/11/07
to

Hello. I found this page interesting

http://mjtsai.com/blog/2002/11/25/perl_vs_python_vs_ruby/

Would someone care to show me how this would be done in Tcl please?
Thanks.

sch...@uni-oldenburg.de

unread,
Sep 11, 2007, 10:31:21 AM9/11/07
to

Probably by using the tcllib csv module and adding some lsearch/lsort -
index trickery for Tcl 8.5 or simple iteration with foreach.

Michael

Arjen Markus

unread,
Sep 11, 2007, 10:39:57 AM9/11/07
to

I do not think the CSV module is needed: from the description
it does not look like there will be embedded tabs or quotation
marks. So:

set fields [split [string map {\" " "} $line] \t]

ought to take care of the parsing bit. The rest is
easy as well, I think.

Regards,

Arjen

Neil Madden

unread,
Sep 11, 2007, 10:59:54 AM9/11/07
to

It's pretty straight-forward. Here's a version that is roughly
equivalent to the other languages:

set records [list]
while {[gets stdin line] >= 0} {
lappend records [map {trim \"} [split $line \t]]
}

set EMAIL 17
set CONTACTME 27
set SKUTITLE 34

set contactRecords [list]
foreach r $records {
lappend contactRecords [lselect $r $SKUTITLE $CONTACTME $EMAIL]
}

set contactRecords [lsort -index 0 $contactRecords]

foreach r $contactRecords {
if {[lindex $r 1] == 1} {
puts [join $r \t]
}
}

In order to make this work, you will need the following helper
procedures. Tcl has a more minimal "standard" library than many other
languages (keeps it nice and small), but you can find equivalents to
these commands in the libraries shipped with most distributions (e.g.,
tcllib contains "map"):

# Just reverse the arguments to [string trim]
proc trim {chars str} { string trim $str $chars }
# Classic map function
proc map {f xs} {
set r [list]
foreach x $xs { lappend r [invoke $f $x] }
return $r
}
proc invoke {f x} { uplevel #0 $f $x }
# Get multiple indices from a list at once
proc lselect {xs args} {
set ret [list]
foreach idx $args { lappend ret [lindex $xs $idx] }
return $ret
}

Really, though, I'd say put all this data into a relational database. A
single SQL query will be much cleaner and more efficient than any of
these solutions. If you don't want a full blown RDBMS then there are
lighter-weight solutions, such as SQLite or Metakit, that work extremely
well.

-- Neil

Cameron Laird

unread,
Sep 11, 2007, 10:41:01 AM9/11/07
to
In article <1189518704....@50g2000hsm.googlegroups.com>,

Yes.

'Might be a bit of delay, though ...

Incidentally, not all his Python, Ruby, and Perl are current;
modern Pythoneers would be likelier to use (another) list
comprehension than a filter()ed lambda. Also, I'm uneasy with
his occasional conflation of list and array ...

Andrew Mangogna

unread,
Sep 11, 2007, 11:54:00 AM9/11/07
to
Neil Madden wrote:

> fro...@gmail.com wrote:
>> Hello. I found this page interesting
>>
>> http://mjtsai.com/blog/2002/11/25/perl_vs_python_vs_ruby/
>>
>> Would someone care to show me how this would be done in Tcl please?
>> Thanks.
>
> It's pretty straight-forward. Here's a version that is roughly
> equivalent to the other languages:

[snip - nice example in straight Tcl]

>
> Really, though, I'd say put all this data into a relational database. A
> single SQL query will be much cleaner and more efficient than any of
> these solutions. If you don't want a full blown RDBMS then there are
> lighter-weight solutions, such as SQLite or Metakit, that work extremely
> well.
>
> -- Neil

In the realm of killing gnats with a sledgehammer and if you don't mind
bringing in a binary extension, TclRAL (http://tclral.sourceforge.net) can
make quick work of these tabular sorts of problems.

=========================================================================

package require ral
namespace import ::ral::*

# A variable to hold the relation of input fields that we are interested in.
# We set EMAIL as the identifier so each record will be checked for
# unique values of the EMAIL attribute.
relvar create records {
Relation {
EMAIL string
CONTACTME boolean
SKUTITLE string
} {
EMAIL
}
}

# Gather up the input.
# Here we just strip off all the quote marks and split by tabs.
# Hard to believe this would work on "real world" input, but this
# is just an example.


while {[gets stdin line] >= 0} {

set fields [split [string map {\" {}} $line] \t]

# Insert the interesting fields into the relvar
relvar insert records [list\
EMAIL [lindex $fields 17]\
CONTACTME [lindex $fields 27]\
SKUTITLE [lindex $fields 34]\
]
}

# Uncomment this to see all the input in a nice tabular form.
#puts [relformat $records]

# Now for the "relational" part.
# Find those records where we are to contact them.
set contactrecords [relation restrictwith $records {$CONTACTME == 1}]

# Output the fields in ascending SKUTITLE order.
relation foreach cr $contactrecords -ascending SKUTITLE {
relation assign $cr
puts "$SKUTITLE\t$CONTACTME\t$EMAIL"
}

===================================================================
--
Andrew Mangogna

Donal K. Fellows

unread,
Sep 11, 2007, 5:16:25 PM9/11/07
to
sch...@uni-oldenburg.de wrote:

> fro...@gmail.com wrote:
>> Would someone care to show me how this would be done in Tcl please?
>> Thanks.
>
> Probably by using the tcllib csv module and adding some lsearch/lsort -
> index trickery for Tcl 8.5 or simple iteration with foreach.

Once I stopped trying to write it like Python or other such foolishness,
I got this:

set EMAIL 17; set CONTACTME 27; set SKUTITLE 34

foreach line [split [read stdin] \n] {
set r [split [string map {\" {}} $line] \t]
lappend contactRecords [list \
[lindex $r $SKUTITLE] [lindex $r $CONTACTME] [lindex $r $EMAIL]]
}
foreach r [lsort -index 0 [lsearch -all -inline -index 1
$contactRecords 1]] {
puts [join $r \t]
}

Anyone asking why not use a DB instead, you're right! It's simpler and
(probably) clearer...

package require sqlite3
sqlite3 db contacts.db
set tab \t
puts [join [db eval {
SELECT SKUTITLE || $tab || CONTACTME || $tab || EMAIL
FROM myTable
WHERE CONTACTME = 1
ORDER BY SKUTITLE
}] \n]

Donal.

tom.rmadilo

unread,
Sep 11, 2007, 8:19:06 PM9/11/07
to
On Sep 11, 2:16 pm, "Donal K. Fellows"

<donal.k.fell...@manchester.ac.uk> wrote:
> Anyone asking why not use a DB instead, you're right! It's simpler and
> (probably) clearer...

We could just provide an example. Hey, there is a specific list of 8
steps, nothing about csv, specific assumptions, etc. I'll post my
script here, followed by a set of reference data (I've changed the
indexes since they are not specified in the 8 steps.):

This is tsv.tcl:

#!/web/nsd45/bin/tclsh8.4
# Do comparison Algorithm

# 1. Read stdin, split by \t
# 2. String trim each field of '"' (quotation marks)
# 3. Sore these fields in list called record
# 4. lappend 'record' to another list called records
# 5. create new record list named contactRecords with only three
fields:
# SKUTITLE, CONTACTME, EMAIL
# 6. Sort contactRecords by SKUTITLE
# 7. Delete from contactRecords any records where CONTACTME != 1
# 8. Print contactRecords with tabs between fields and newline between
records

set EMAIL 2
set CONTACTME 0
set SKUTITLE 1

set records [list]
set contactRecords [list]

while {![eof stdin]} {
# 1.
set line [split [gets stdin] "\t"]
# 2-3
set record [string map {"\"" ""} $line]
# 4.
lappend records $record
# 5.
lappend contactRecords [list \
[lindex $record $SKUTITLE]\
[lindex $record $CONTACTME]\
[lindex $record $EMAIL]];
}

# 6.
set contactRecords [lsort $contactRecords]
# 7.
set contactRecords [lsearch -all -inline -not $contactRecords {* 1 *}]
# 8.
foreach contactRecord $contactRecords {
puts stdout [join $contactRecord "\t"]
}

# Following is the data.tsv file (tab separated values):

$cat data.tsv
"1" "rz" "t@b"
"0" "uq" "r@y"
"1" "rn" "e@u"
"0" "mn" "q@a"
"0" "tt" "o@y"
"1" "ew" "i@x"
"0" "gh" "v@z"

Then do this:

$ cat data.tsv | ./tsv.tcl

Results:
gh 0 v@z
mn 0 q@a
tt 0 o@y
uq 0 r@y

Although you could claim to do this faster or with less code, you have
to create the variables specified in the steps, replace the value of
certain variables, etc. These are significant to comparing languages.
Tcl can do each step in the order asked, creating only the variables
needed. Very nice is the removal of quotes from list elements.

tom.rmadilo

unread,
Sep 11, 2007, 10:33:44 PM9/11/07
to
On Sep 11, 5:19 pm, "tom.rmadilo" <tom.rmad...@gmail.com> wrote:
> We could just provide an example.

The comments on the reference page are very interesting. Most
scripting languages support nearly impossible to understand code.

Also, several are incorrect, as is the Donal example above. Here is
the incorrect script in shell language (using the above data file):

$ cat data.tsv | awk '$1 ~ /1/ { print $2 " " $1 " " $3; }' | sed
's#"##g' | sort
ew 1 i@x
rn 1 e@u
rz 1 t@b

correct version:

$ cat data.tsv | awk '$1 !~ /1/ { print $2 " " $1 " " $3; }' | sed
's#"##g' | sort


gh 0 v@z
mn 0 q@a
tt 0 o@y
uq 0 r@y

Close, but no cigar.

sleb...@gmail.com

unread,
Sep 11, 2007, 10:36:16 PM9/11/07
to
On Sep 12, 8:19 am, "tom.rmadilo" <tom.rmad...@gmail.com> wrote:
> On Sep 11, 2:16 pm, "Donal K. Fellows"
>
> <donal.k.fell...@manchester.ac.uk> wrote:
> > Anyone asking why not use a DB instead, you're right! It's simpler and
> > (probably) clearer...
>
> <snip>

>
> $cat data.tsv
> "1" "rz" "t@b"
> "0" "uq" "r@y"
> "1" "rn" "e@u"
> "0" "mn" "q@a"
> "0" "tt" "o@y"
> "1" "ew" "i@x"
> "0" "gh" "v@z"
>
> Then do this:
>
> $ cat data.tsv | ./tsv.tcl
>
> Results:
> gh 0 v@z
> mn 0 q@a
> tt 0 o@y
> uq 0 r@y
>
> Although you could claim to do this faster or with less code, you have
> to create the variables specified in the steps, replace the value of
> certain variables, etc. These are significant to comparing languages.
> Tcl can do each step in the order asked, creating only the variables
> needed. Very nice is the removal of quotes from list elements.

I just realised something when I saw your sample data file. If I read
the spec correctly it states that the data is tab separated and
surrounded by quotes. Isn't this simply a bunch of lists separated by
newlines? Let the parser do the work:

set EMAIL 2
set CONTACTME 0
set SKUTITLE 1

while {[gets stdin line] >= 0} {
if {[lindex $line $CONTACTME] == 1} {
lappend contactRecords [list \
[lindex $line $SKUTITLE] \
[lindex $line $CONTACTME] \
[lindex $line $EMAIL]
]
}
}

foreach x [lsort contactRecords] {
puts [join $x "\t"]
}

Ok, yeah I know, never treat unknown strings as lits. But the spec
basically defines the input data to be proper tcl lists (if I
understand it correctly).

tom.rmadilo

unread,
Sep 12, 2007, 1:14:07 AM9/12/07
to
On Sep 11, 7:36 pm, "slebet...@yahoo.com" <slebet...@gmail.com> wrote:
> Ok, yeah I know, never treat unknown strings as lits. But the spec
> basically defines the input data to be proper tcl lists (if I
> understand it correctly).

First, I admit to a mistake. Let me correct that:

Donal's script is correct, not mine. removing -not from step 7
corrects the error. I've spent too much time writing SQL select
statements, where an = is much easier to select than a negative, which
pretty much supports Donal's argument that you should do this with a
DB. Easy to get confused!

Now...A string isn't a list until you turn it into one.

If you read a line into a variable. That variable is a string, no
matter how much it looks like a Tcl list. If you do a 'to list'
operation, then you can treat the string as a list, minus the chars
which joined the string. So the input line contains tabs, these have
to be removed.


Bryan Oakley

unread,
Sep 12, 2007, 6:37:41 AM9/12/07
to
tom.rmadilo wrote:

> Now...A string isn't a list until you turn it into one.
>

... or it is turned into one implicitly by the interpreter.

> If you read a line into a variable. That variable is a string, no
> matter how much it looks like a Tcl list. If you do a 'to list'
> operation, then you can treat the string as a list, minus the chars
> which joined the string. So the input line contains tabs, these have
> to be removed.

That is not quite correct if I understand what you have written. It is
certainly possible to read a string in from a file and use list commands
on it without first explicitly converting it to a list (ill-advised
though that may be). In this case, the tabs will be removed by the
implicit conversion of the string to a list the first time a list
command is used.

Tcl has always had the ability to automatically convert strings to
lists, provided the strings are in a suitable format.

tom.rmadilo

unread,
Sep 12, 2007, 11:17:01 AM9/12/07
to

Wow, it works! In addition, your version handles tabs embedded in a
field, and any number of whitespace chars between fields. My script
fails under these conditions.

sleb...@gmail.com

unread,
Sep 12, 2007, 11:52:19 AM9/12/07
to
On Sep 12, 11:17 pm, "tom.rmadilo" <tom.rmad...@gmail.com> wrote:
> On Sep 12, 3:37 am, Bryan Oakley <oak...@bardo.clearlight.com> wrote:
>
> > tom.rmadilo wrote:
> > > Now...A string isn't a list until you turn it into one.
>
> > ... or it is turned into one implicitly by the interpreter.
>
> > > If you read a line into a variable. That variable is a string, no
> > > matter how much it looks like a Tcl list.
> <snip>

> > Tcl has always had the ability to automatically convert strings to
> > lists, provided the strings are in a suitable format.
>
> Wow, it works! In addition, your version handles tabs embedded in a
> field, and any number of whitespace chars between fields. My script
> fails under these conditions.

Remember that in Tcl, everything is a string (tm). Of course
implementation wise, for the sake of speed, lists, dicts and strings
are implemented as different object types. But the language itself, by
definition, sees lists merely as specially formatted strings.

Funny thing is, strictly speaking, Tcl the language (as opposed to Tcl
the collection of C code implementing the language) does not actually
*have* lists. If you look carefully at the definition of the language
you will not find the definition of a list. By definition, a list in
tcl is simply a string that the [lindex] command finds acceptable.

0 new messages