diff --git a/lib/bdb/simple.rb b/lib/bdb/simple.rb index 4bfb4df..ba75ae8 100644 --- a/lib/bdb/simple.rb +++ b/lib/bdb/simple.rb @@ -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 diff --git a/test/simple_test.rb b/test/simple_test.rb index 67c5eb1..809283d 100644 --- a/test/simple_test.rb +++ b/test/simple_test.rb @@ -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)}