optimized compare_absolute method

master
Justin Balthrop 2009-08-07 14:43:40 -07:00
parent f83703c6af
commit 9e2b2e2abb
2 changed files with 35 additions and 23 deletions

View File

@ -68,37 +68,49 @@ class Bdb::Simple
@db = nil
end
CLASS_ORDER = {}
[FalseClass, TrueClass, Fixnum, Numeric, Float, Symbol, String, Array].each_with_index {|c, i| CLASS_ORDER[c] = i}
def self.compare_absolute(left, right)
if left.class == right.class
if left.is_a?(Array) and right.is_a?(Array)
if left.is_a?(right.class)
case left
when Array
# Arrays: compare one element at a time.
left.zip(right) do |l,r|
comp = compare_absolute(l, r)
return comp unless comp == 0
n = [left.size, right.size].min
n.times do |i|
comp = compare_absolute(left[i], right[i])
return comp if comp != 0
end
left.size == right.size ? 0 : -1
elsif left.is_a?(Hash) and right.is_a?(Hash)
# Hashes: sort the keys and compare as an array of arrays.
left.size <=> right.size
when Hash
# Hashes: sort the keys and compare as an array of arrays. This may be slow.
left = left.to_a.sort {|a,b| compare_absolute(a[0],b[0])}
right = right.to_a.sort {|a,b| compare_absolute(a[0],b[0])}
compare_absolute(left, right)
when NilClass, TrueClass, FalseClass
0
when Symbol
left.to_s <=> right.to_s
else
begin
# Try to use the spaceship operator.
left <=> right
rescue NoMethodError => e
left.hash <=> right.hash
end
end
# Use the spaceship operator.
left <=> right
end
elsif left.kind_of?(Numeric) and right.kind_of?(Numeric)
# Numerics are always comparable.
left <=> right
else
# Nil is the smallest. Hash is the largest. All other objects are sorted by class name.
return -1 if left.is_a?(NilClass)
return 1 if right.is_a?(NilClass)
return 1 if left.is_a?(Hash)
return -1 if right.is_a?(Hash)
# Nil is the smallest. Hash is the largest.
return -1 if left.is_a?(NilClass) or right.is_a?(Hash)
return 1 if left.is_a?(Hash) or right.is_a?(NilClass)
# Compare class names and hashes as a last resort if that fails.
right.class.name <=> left.class.name
# Try to use the class sort order so we don't have to do a string comparison.
left_order = CLASS_ORDER[left.class]
right_order = CLASS_ORDER[right.class]
if left_order.nil? and right_order.nil?
left.class.name <=> right.class.name
else
(left_order || 9999) <=> (right_order || 9999)
end
end
end
end

View File

@ -44,7 +44,7 @@ class SimpleTest < Test::Unit::TestCase
def test_compare_absolute
list = [5, 6, "foo", :bar, "bar", :foo, [1,2,4], true, [1,2,3], false, [1], [2], nil, {}, {:b => 1, :a => 1}, {:b => 2, :a => 1}]
expected = [nil, true, :bar, :foo, "bar", "foo", 5, 6, false, [1], [1, 2, 3], [1, 2, 4], [2], {}, {:a=>1, :b=>1}, {:a=>1, :b=>2}]
expected = [nil, false, true, 5, 6, :bar, :foo, "bar", "foo", [1], [1, 2, 3], [1, 2, 4], [2], {}, {:a=>1, :b=>1}, {:a=>1, :b=>2}]
assert_equal expected, list.sort {|a,b| Bdb::Simple.compare_absolute(a,b)}
100.times do
assert_equal expected, list.shuffle.sort {|a,b| Bdb::Simple.compare_absolute(a,b)}