performance and style advice requested

A

Alex Martelli

I'm trying to learn some Ruby, so I want to write some Ruby code, starting
with my usual favourite fun field -- combinatorial arithmetic related to
the game of contract bridge. As a warm-up, I begin with the "probability
of pointcount". Problem definition: a hand is a set of 13 cards out of 52.
16 of those cards (Aces, Kings, Queens, Jacks) are known as "honors" and
are assigned a "pointcount" (4 for each Ace, 3 for each King, 2 for each
Queen, 1 for each Jack). Consider all possible hands and determine the
exact probability of each possible point-count.

Basically, this boils down to considering each of the 2**16 possible
sets of honors -- each has an easily computed total pointcount -- and
for each of them computing in how many different ways it can occur --
which is C(36, 13-numberofhonors) -- the ways of choosing out of the
36 non-honors in the deck the 13-numberofhonors non-honors in this
hand. (That's with C(x,y) extended to be 0 for y<0, of course).

So I sit down and code it in the simplest way -- in Python first for
practice, since that's what I'm familiar with, then in Ruby. Fine
both ways, except that (on my laptop, which is all I have at hand as
I'm on a business trip -- with Python 2.3 and Ruby 1.8 and Win/XP)
the Python version runs in just above 4 seconds while the Ruby one
takes over 9. Hmmm, probably my lack of Ruby instincts, of course,
but anyway -- just as of course -- this needs to be optimized by
quite a bit (not for its own sake, but because later I'll extend it
to compute much more interesting stuff, such as correlations between
points, controls and shape, etc, etc) so who cares about performance
for the "coded in the simplest way" versions (oh, I should clarify:
simplest _except_ that I have of course memoized C(), computing
combinations aka binomial coefficients, and the factorial it calls).

So I start optimizing and squeezing every cycle I can -- and I get
down to about 0.65 seconds for Python, 2.3 seconds for Ruby (best
case, for each of them). Eep -- the ratio keeps increasing, so my
lack of sound Ruby instincts must be really hurting. Oh well, say
I, let's try "ruby -rprofile". EEK! After a couple of times where
I was convinced it had just seized up I decided to let it run all
the way -- and it took almost 1000 seconds. Is a slowdown of over
400 times normal for Ruby profiling, or is something weird in my
installation...? (It's the one-exe native Ruby 1.8 install for Win
which comes with a lot of goodies such as SciTE, Programming Ruby
in HTML-Help format, etc).

Anyway, the profiler's output isn't illuminating to me at all, so
that's when I decide to turn to the famous Ruby community -- I'm
sure you'll find lots to critique in my Ruby program, particularly
with an eye to performance but not necessarily just that (I _am_
quite aware that I may be guilty of "coding Python in Ruby" -- and
this may produce suboptimal performance _and_ other issues).

So, first, for reference, here's the optimized Python program:


#
# Memoized implementation of factorial
#
def fact(x, _fact_memo = {0:1, 1:1, 2:2, 3:6, 4:24, 5:120, 6:720, 7:5040} ):
" factorial of x (0 if x<0) "
try: return _fact_memo[x]
except KeyError:
if x < 0: return 0
return _fact_memo.setdefault(x, x * fact(x-1))

#
# Memoized implementation of C(x, y) [binomial coefficient, aka comb]
#
def comb(x, y, _comb_memo = {}):
" combinations of x things y at a time (0 if x<0, y<0, or y>x) "
try: return _comb_memo[x, y]
except KeyError:
if x<0 or y<0 or y>x: return 0
return _comb_memo.setdefault((x, y), fact(x) / (fact(y)*fact(x-y)))

#
# we represent a "honor" by a its points-worth
#
honors = [ p for p in range(1,5) for s in range(4) ]

#
# we represent a "honors set" by a pair (points-worth, total-#-of-cards)
#
def honor_subsets(lis, fromindex=0):
""" recursive generator of all sets from a list and starting point;
each set is represented by a honor-set as above, while the list
must hold points-worths representing "honors" again as above.
"""
ph = lis[fromindex]
if fromindex == len(lis)-1:
yield 0, 0
yield ph, 1
return
for pw, le in honor_subsets(lis, fromindex+1):
yield pw, le
yield ph+pw, 1+le

def main():
" main computation "
# hist: histogram points -> number of occurrences
hist = {}
# totcom: total number of occurrences
totcom = 0L
# range over all possible honorsets
for hs in honor_subsets(honors):
# compute number of honors and pointcount for this honorset
pt, nh = hs
# compute number of possible occurrences of this honorset
nc = comb(36, 13-nh)
# update histogram and total
hist[pt] = hist.get(pt, 0) + nc
totcom += nc
# print total and eyeball-check it
print totcom, comb(52, 13)

# sort histogram by decreasing frequency
aux = [ (nc, pt) for pt, nc in hist.iteritems() ]
aux.sort()
aux.reverse()

# compute frequencies as relative percentages
divis = 100.0 / totcom

# give top 10 possibilities
for nc, pt in aux[:10]:
print "%2d %d (%.2f)" % (pt, nc, nc * divis)


# run the computation
import time
start = time.time()
main()
stend = time.time()
print stend-start


and here is the corresponding optimized Ruby program:


#
# factorial, memoized via an Array
#
$_fact_memo=[1,1,2,6,24,120,720]
def fact(x)
# factorial of x (0 if x<0)
result = $_fact_memo[x]
if !result
return 0 if x<0
result = $_fact_memo[x] = x*fact(x-1)
end
return result
end

#
# binomial factor (aka comb), memoized via a Hash
#
$_comb_memo={}
def comb(x, y)
# combinations of x things y at a time (0 if x<0, y<0, or y>x)
result = $_comb_memo[[x, y]]
if !result
return 0 if x<0 or y<0 or y>x
result = $_comb_memo[[x, y]] = fact(x)/(fact(y)*fact(x-y))
end
return result
end

#
# add to Array a recursive iterator over honor-subsets
#
# a honor is represented by its point-value (1..4); a honor-subset is
# a pair [total-pointcount, number-of-honors]
#
class Array
def each_subset(from=0)
if from==size-1:
#
# end of array, so, return 2 only possible subsets remaining:
# empty set, or singleton set with the last element in it
#
yield [0, 0]
yield [self[from], 1]
else
#
# 2+ elements remaining, so, loop iteratively, extracting
# the current element and getting all possible subsets of
# all later ones -- for each of those, return 2 possible
# subsets, i.e., either without or with the current element
#
thispoints = self[from]
each_subset(from+1) do |subpoints, sublength|
yield subpoints, sublength
yield thispoints + subpoints, 1 + sublength
end
end
end
end


def main()
# main computation
# hist: histogram (Hash) points -> number of occurrences
hist = Hash.new(0)
# totcon: total number of occurrences
totcom = 0
# range over all possible honor-sets
honors = Array(1..4) * 4
honors.each_subset do |pt, nh|
# receive number of honors and pointcount for this honorset,
# then compute number of possible occurrences of this honorset
nc = comb(36, 13-nh)
# update histogram and total
hist[pt] += nc
totcom += nc
end
# print total and eyeball-check it
print totcom, ' ', comb(52, 13), "\n"

# sort histogram by decreasing frequency
aux = hist.sort {|a,b| b[1]<=>a[1]}

# compute frequencies as relative percentages
divis = totcom / 100.0

# give top 10 possibilities
for pt, nc in aux[0,10]
printf "%2d %d (%.2f)\n" , pt, nc, nc/divis
end
end

# run the computation
start = Time.now
main
stend = Time.now
puts stend-start


Finally, FWIW, here's the start of the profiler's output...:


C:\Python23>ruby -rprofile alco2.rb
635013559600 635013559600
10 59723754816 (9.41)
9 59413313872 (9.36)
11 56799933520 (8.94)
8 56466608128 (8.89)
7 50979441968 (8.03)
12 50971682080 (8.03)
13 43906944752 (6.91)
6 41619399184 (6.55)
14 36153374224 (5.69)
5 32933031040 (5.19)
948.294
% cumulative self self total
time seconds seconds calls ms/call ms/call name
34.90 317.57 317.57 16 19848.44 909790.50 Array#each_subset
13.12 437.00 119.43 65537 1.82 6.56 Object#comb
10.56 533.11 96.11 131073 0.73 2.54 Hash#[]
8.20 607.70 74.59 65385 1.14 1.82 Array#eql?
8.11 681.49 73.79 65552 1.13 1.79 Array#hash
6.17 737.65 56.16 161735 0.35 0.35 Fixnum#+
4.87 781.99 44.34 130770 0.34 0.34 Numeric#eql?
4.80 825.63 43.65 131104 0.33 0.33 Kernel.hash
4.14 863.31 37.68 100426 0.38 0.38 Bignum#+
2.59 886.84 23.53 65551 0.36 0.36 Hash#[]=
2.48 909.38 22.54 65613 0.34 0.34 Fixnum#-
0.02 909.57 0.19 91 2.09 38.57 Object#fact
0.02 909.75 0.18 1 180.00 180.00
Profiler__.start_profile
0.02 909.92 0.17 1 170.00 190.00 Hash#sort
0.01 910.02 0.10 350 0.29 0.29 Fixnum#<
0.01 910.10 0.08 193 0.41 0.41 Hash#default
0.01 910.16 0.06 59 1.02 1.19 Fixnum#*

I've truncated starting from where the "%time" goes to 0.00.


Thanks for any feedback!


Alex
 
S

Simon Strandgaard

On Sun, 14 Sep 2003 19:09:54 +0200, Alex Martelli wrote:
[snip]
Anyway, the profiler's output isn't illuminating to me at all, so
that's when I decide to turn to the famous Ruby community -- I'm
sure you'll find lots to critique in my Ruby program, particularly
with an eye to performance but not necessarily just that (I _am_
quite aware that I may be guilty of "coding Python in Ruby" -- and
this may produce suboptimal performance _and_ other issues). [snip]
#
# binomial factor (aka comb), memoized via a Hash
#
$_comb_memo={}
def comb(x, y)
# combinations of x things y at a time (0 if x<0, y<0, or y>x)
result = $_comb_memo[[x, y]]

^^^^^^
^^^^^^

very slow!!

Ruby converts it the array into a string.. this takes time!

if !result
return 0 if x<0 or y<0 or y>x
result = $_comb_memo[[x, y]] = fact(x)/(fact(y)*fact(x-y))
end
return result
end



Try this instead, and tell me if it works ?


def comb(x, y)
# combinations of x things y at a time (0 if x<0, y<0, or y>x)
result = $_comb_memo[x << 16 + y]
if !result
return 0 if x<0 or y<0 or y>x
result = $_comb_memo[x << 16 + y] = fact(x)/(fact(y)*fact(x-y))
end
return result
end
 
R

Robert Feldt

Alex Martelli said:
So I start optimizing and squeezing every cycle I can -- and I get
down to about 0.65 seconds for Python, 2.3 seconds for Ruby (best
case, for each of them). Eep -- the ratio keeps increasing, so my
lack of sound Ruby instincts must be really hurting. Oh well, say
I, let's try "ruby -rprofile". EEK! After a couple of times where
I was convinced it had just seized up I decided to let it run all
the way -- and it took almost 1000 seconds. Is a slowdown of over
400 times normal for Ruby profiling, or is something weird in my
installation...? (It's the one-exe native Ruby 1.8 install for Win
which comes with a lot of goodies such as SciTE, Programming Ruby
in HTML-Help format, etc).
No, slowdowns in the range 20-500 times (depending on the code) is
to be expected. For speedier profiling you can try my rbprof profiler
which is part of AspectR. I haven't updated it in 1.5 years or so
though so it might not work out-of-the-box. Its faster and gives more
information.
Anyway, the profiler's output isn't illuminating to me at all, so
that's when I decide to turn to the famous Ruby community -- I'm
sure you'll find lots to critique in my Ruby program, particularly
with an eye to performance but not necessarily just that (I _am_
quite aware that I may be guilty of "coding Python in Ruby" -- and
this may produce suboptimal performance _and_ other issues).
While I doubt you should compare these languages on performance merits
and sincerely hope this post is not a subtle troll post (I assume it
isn't so: Welcome to the Ruby community, Alex!) let's give this a
try.

When it comes to performance I would first look at the algorithm. what
strikes me is that you can also parameterize on the point count
for each honor. This way you would not need to traverse all 2**16
combinations.

Second observation is that you should use Process.times.utime to time
this since you only want the time spent by the Ruby process.

Third observation is that I don't like globals so I wrap the
fact and comb methods in a module.

Fourth is some minor changes to use common Ruby idioms.

So my version is:

module Comb
# memoize results for speed
FactMemo, CombMemo = [1, 1, 2, 6, 24, 120, 720], {}

# factorial
def Comb.fact(n)
return 0 if n < 0
FactMemo[n] ||= (n * fact(n-1))
end

# binomial factor (aka comb), memoized via a Hash
def Comb.comb(x, y)
return 0 if x<0 or y<0 or y>x
CombMemo[[x,y]] ||= fact(x)/(fact(y) * fact(x-y))
end
end

#
# add to Array a recursive iterator over honor-value-subsets
#
# a honor is represented by its point-value (1..4); a honor-value-subset is
# [total-pointcount, number-of-honors, number-of-combs]
#
class Array
def each_subset(from=0)
v = self[from]
if from == length
yield 0, 0, 1
else
each_subset(from+1) do |pointcount, num_honors, num_combs|
(0..4).each do |m| yield pointcount + m*v, num_honors + m, num_combs * Comb.comb(4,m)
end
end
end
end
end
def main
# hist: histogram (Hash) points -> number of occurrences
histogram = Hash.new(0)
# totcon: total number of occurrences
total_count = 0
# range over all possible honor-value-subsets
honor_values = (1..4).to_a
honor_values.each_subset do |pt, nh, count|
# receive number of honors and pointcount and combinations
# for this honor-value-set, then compute number of possible occurrences # of this honor-value-set
num_combs = count * Comb.comb(36, 13-nh)

# update histogram and total
histogram[pt] += num_combs
total_count += num_combs
end

# print total and eyeball-check it
print total_count, ' ', Comb.comb(52, 13), "\n"
# sort histogram by decreasing frequency
aux = histogram.sort {|a,b| b[1]<=>a[1]}
# compute frequencies as relative percentages
divisor = total_count / 100.0
# give top 10 possibilities
aux[0,10].each do |point, count|
printf "%2d %d (%.2f)\n" , point, count, count / divisor
end
end

def time
start = Process.times.utime
yield
stend = Process.times.utime
stend-start
end

elapsed = time {main}
puts "#{elapsed} seconds"

and comparing that to your version (only changed to use Process.times.utime)
I get:

$ time ruby am_better_timing.rb
635013559600 635013559600
10 59723754816 (9.41)
9 59413313872 (9.36)
11 56799933520 (8.94)
8 56466608128 (8.89)
7 50979441968 (8.03)
12 50971682080 (8.03)
13 43906944752 (6.91)
6 41619399184 (6.55)
14 36153374224 (5.69)
5 32933031040 (5.19)
0.531

real 0m0.553s
user 0m0.561s
sys 0m0.000s

feldt@novomundo1 /tmp/alex_martelli
$ time ruby feldt.rb
635013559600 635013559600
10 59723754816 (9.41)
9 59413313872 (9.36)
11 56799933520 (8.94)
8 56466608128 (8.89)
7 50979441968 (8.03)
12 50971682080 (8.03)
13 43906944752 (6.91)
6 41619399184 (6.55)
14 36153374224 (5.69)
5 32933031040 (5.19)
0.0 seconds

real 0m0.033s
user 0m0.062s
sys 0m0.000s

and I'm ok with that so I'll stop there for now. But the change
I did in the algorithm is also available for you in python so I guess
the ratio between the languages might not change much...

Best regards,

Robert Feldt
 
B

Ben Giddings

Robert said:
Fourth is some minor changes to use common Ruby idioms.

So my version is:

module Comb
# memoize results for speed
FactMemo, CombMemo = [1, 1, 2, 6, 24, 120, 720], {}

# factorial
def Comb.fact(n)
return 0 if n < 0
FactMemo[n] ||= (n * fact(n-1))
end

I like the fact you wrapped the functionality in a module, but if we're
using Ruby idioms, the FactMemo and CombMemo variables should really not
be named with leading uppercase letters. They look like classes (or
constants) and may be treated as such by Ruby. I'd recommend instead
fact_memo and comb_memo.

Ben
 
R

Robert Feldt

Lyle Johnson said:
I think Alex's reputation in the Python community more than speaks for the fact that he's *not* a troll. This is a very reasonable question about Python versus Ruby performance.
Ok. I hope it was clear that I wasn't implying that he is.
My remark has nothing to do with Alex really; recent threads here on the list just makes "comparisons" between Ruby and Python a bit "infected" right now I guess. ;)

Sorry if it came out the wrong way.

Then I guess we should be delighted that a python "hot-shot"
is willing to check Ruby out. Again: Welcome here, Alex.

Regards,

Robert
 
R

Robert Feldt

Ben Giddings said:
So my version is:

module Comb
# memoize results for speed
FactMemo, CombMemo = [1, 1, 2, 6, 24, 120, 720], {}

# factorial
def Comb.fact(n)
return 0 if n < 0
FactMemo[n] ||= (n * fact(n-1))
end

I like the fact you wrapped the functionality in a module, but if we're using Ruby idioms, the FactMemo and CombMemo variables should really not be named with leading uppercase letters. They look like classes (or constants) and may be treated as such by Ruby. I'd recommend instead fact_memo and comb_memo.
Yeah, I guess you're right in a way. On the other hand they *are* constants since the array and the hash are the same objects
even if their contents change... ;)

To me its more a question of what feels right than its about
what is more of a Ruby idiom. I could be wrong though...

The more logical to me following your thinking would be @fact_memo
and @comb_memo. Or to use the memoize extension. :)

/Robert
 
A

Alex Martelli

Simon Strandgaard wrote:
...
def comb(x, y)
# combinations of x things y at a time (0 if x<0, y<0, or y>x)
result = $_comb_memo[[x, y]]

^^^^^^
^^^^^^

very slow!!

Ruby converts it the array into a string.. this takes time!

Oh my! I had no idea Ruby suffered under such a handicap wrt to Python
(that arrays had to be converted to strings to index into hashes, while
Python can use "tuples", its equivalent of frozen arrays, for that) --
then I must definitely be very sparing in using hashes with multi-dim
indexes in Ruby, at least in any bottleneck. Thanks! Just the kind of
things I'm trying to learn (I don't remember noticing any such caveat
in Thomas and Hunt's excellent book).
Try this instead, and tell me if it works ?


def comb(x, y)
# combinations of x things y at a time (0 if x<0, y<0, or y>x)
result = $_comb_memo[x << 16 + y]
if !result
return 0 if x<0 or y<0 or y>x
result = $_comb_memo[x << 16 + y] = fact(x)/(fact(y)*fact(x-y))
end
return result
end

I had already reworked comb (as per other advice from c.l.ruby( to a
slightly different and slightly faster form than my original one:

def comb(x, y)
# combinations of x things y at a time (0 if x<0, y<0, or y>x)
$_comb_memo[[x, y]] ||=
if x<0 or y<0 or y>x then 0
else fact(x)/(fact(y)*fact(x-y)) end
end

this gave a best-case performance (on Linux, and w. Ruby 1.6.8 -- a
far faster combo than Ruby 1.8.0 on Win/XP, apparently, see my latest
post) of 1.38 reported, 1.44 total.

The tiny change you suggest for indexing into the hash, i.e. changing
the only occurrence of indexing in this form of the method to

$_comb_memo[x<<16 + y] ||=

does make things even better -- we're now at 1.20 reported, 1.25 total,
best run of many (against 0.694 reported, 0.788 total for Python).
"Rubier and rubier", said Alice!-)

With Ruby now taking less than twice as long as Python, I guess I could
be satisfied here and move on to richer and more complicated cases, but
I can't help wondering if there might not be some crucial optimization
waiting to happen in the recursive iterator, which is (according to the
profiler, with its 400-times slowdown;-) by far the bottleneck...
any suggestions are welcome!


And thanks again for the suggestions so far...!


Alex
 
A

Alex Martelli

*blink* why not, pray? While clarity, productivity, simplicity, and so on,
are surely all crucial attributes in a language, what's wrong with wanting
to ascertain how it performs (when used in the best/fastest way, at least)
in typical problems in one's domains of interest...? A 10% or 20%
difference is hardly ever going to matter, but factors of 2 or 3 might
well make the difference, in practice, between two languages of otherwise
comparable merits -- and that's what I am observing here, so far (at least
in the toy/starting problem which I'm using as a warm-up exercise -- we'll
see what happens when I move to bigger and more interesting problems, e.g.
I cannot exclude that Ruby's performance might "scale up" better, or
whatever).

Ok. I hope it was clear that I wasn't implying that he is.
My remark has nothing to do with Alex really; recent threads here on the
list just makes "comparisons" between Ruby and Python a bit "infected"
right now I guess. ;)

Were there highly contentious threads on Ruby vs Python here recently?
Sorry, I did look around briefly for such recent things (mostly to avoid
re-asking what might have recently been answered) but didn't find them
(maybe my ISP has expired them already, or maybe I made some mistake).

Sorry if it came out the wrong way.

No problem!
Then I guess we should be delighted that a python "hot-shot"
is willing to check Ruby out. Again: Welcome here, Alex.

Thanks! I'm always willing to check out new things (or else I'd hardly
have found Python 3+ years ago, being somewhat of a C++ "hot-shot" then;-),
and was interested in Python since Linux Magazine (without telling me in
advance) ran my Python article and Thomas and Hunt's Ruby article smack
one against the other in the same issue. So I was quite glad to have the
opportunity to take Thomas's Ruby tutorial at OSCON, get and study the
pickaxe book, and starting to try things out for real -- the only way to
really learn a language (or other technology or tool), IMHO, particularly
with good support from an online community such as this one.


Alex
 
T

ts

A> Oh my! I had no idea Ruby suffered under such a handicap wrt to Python
A> (that arrays had to be converted to strings to index into hashes, while
A> Python can use "tuples", its equivalent of frozen arrays, for that) --

svg% ruby -e 'a = {[1, 2] => 12}; p a.keys[0].class'
Array
svg%

A> then I must definitely be very sparing in using hashes with multi-dim
A> indexes in Ruby, at least in any bottleneck. Thanks! Just the kind of
A> things I'm trying to learn

then forget it :)


Guy Decoux
 
H

Hal Fulton

Alex said:
Simon Strandgaard wrote:
...
def comb(x, y)
# combinations of x things y at a time (0 if x<0, y<0, or y>x)
result = $_comb_memo[[x, y]]

^^^^^^
^^^^^^

very slow!!

Ruby converts it the array into a string.. this takes time!


Oh my! I had no idea Ruby suffered under such a handicap wrt to Python
(that arrays had to be converted to strings to index into hashes, while
Python can use "tuples", its equivalent of frozen arrays, for that) --
then I must definitely be very sparing in using hashes with multi-dim
indexes in Ruby, at least in any bottleneck. Thanks! Just the kind of
things I'm trying to learn (I don't remember noticing any such caveat
in Thomas and Hunt's excellent book).

Simon is mistaken here. There may be some kind of
speed hit when storing an array as a hash key, but
the key is definitely stored as an array.

Hal
 
R

Robert Feldt

Alex Martelli said:
*blink* why not, pray? While clarity, productivity, simplicity, and so on,
are surely all crucial attributes in a language, what's wrong with wanting
to ascertain how it performs (when used in the best/fastest way, at least)
in typical problems in one's domains of interest...? A 10% or 20% difference is hardly ever going to matter, but factors of 2 or 3 might
well make the difference, in practice, between two languages of otherwise
comparable merits -- and that's what I am observing here, so far (at least
in the toy/starting problem which I'm using as a warm-up exercise -- we'll
see what happens when I move to bigger and more interesting problems, e.g.
I cannot exclude that Ruby's performance might "scale up" better, or whatever).
Nothing wrong with the comparison but:

1, If performance is *REALLY* important to you, you wouldn't use Python or Ruby anyway, IMHO, so their relative performance
is less important than any of them compared to C.

2, Performance is hard to compare since algorithmic differences is often
so much more important than the gains you get with "tweak-like"
performance tuning (for example the 16-time speedup of your problem I presented earlier)

3, There has been a couple of troll-like "Ruby has nothing to offer
compared to Python" threads in the last couple of weeks

so please understand my response in that context.

Regards,

Robert
 
A

Alex Martelli

Hal said:
Alex said:
Simon Strandgaard wrote:
...
def comb(x, y)
# combinations of x things y at a time (0 if x<0, y<0, or y>x)
result = $_comb_memo[[x, y]]

^^^^^^
^^^^^^

very slow!!

Ruby converts it the array into a string.. this takes time!


Oh my! I had no idea Ruby suffered under such a handicap wrt to Python
(that arrays had to be converted to strings to index into hashes, while
...
Simon is mistaken here. There may be some kind of
speed hit when storing an array as a hash key, but
the key is definitely stored as an array.

Ah! Good to know, thanks -- I was (mistakenly, of course) taking all the
pronouncements of the c.l.ruby readership as if they came from Ruby
experts -- unless of course corrected by others at the time I got to read
them (I always read news and mail backwards in time to cover for that),
while Simon's remark had been left unchallenged until now. Still, the
move from [x,y] to x<<16+y did accelerate Ruby a bit (while the same
change slowed Python down a bit) so it does seem the "some kind of speed
hit" may in certain cases be (though not by much) noticeable.


Alex
 
A

Alex Martelli

ts said:
A> Oh my! I had no idea Ruby suffered under such a handicap wrt to Python
A> (that arrays had to be converted to strings to index into hashes, while
A> Python can use "tuples", its equivalent of frozen arrays, for that) --

svg% ruby -e 'a = {[1, 2] => 12}; p a.keys[0].class'
Array
svg%

A> then I must definitely be very sparing in using hashes with multi-dim
A> indexes in Ruby, at least in any bottleneck. Thanks! Just the kind of
A> things I'm trying to learn

then forget it :)

....while remembering that some who post here may NOT be as knowledgeable
as Ruby as they sound by the certainty of their assertions (just like on
most other Usenet groups, of course). OK, thanks!


Alex
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,575
Members
45,053
Latest member
billing-software

Latest Threads

Top