6

经常有人想要<=>在产品数据类型上实现(比较,或“宇宙飞船”)运算符,即具有多个字段的类(所有这些(我们希望!)已经<=>实现),比较某个字段中的字段命令。

def <=>(o)
    f1 < o.f1 && (return -1)
    f1 > o.f1 && (return  1)
    f2 < o.f2 && (return -1)
    f2 > o.f2 && (return  1)
    return 0
end

这既乏味又容易出错,尤其是对于很多领域。它很容易出错,以至于我经常觉得我应该对该函数进行单元测试,这只会增加乏味和冗长。

Haskell 提供了一种特别好的方法:

导入 Data.Monoid (mappend)
导入 Data.Ord(比较)

-- 来自标准库:
-- 数据排序 = LT | 情商 | 燃气轮机

数据 D = D { f3 :: Int, f2 :: Double, f1 :: Char } 推导 Show

compareD :: D -> D -> 排序
compareD = foldl1 mappend [比较 f1,比较 f2,比较 f3]

(对于那些不熟悉的人fold,以上扩展为

comparing f1 `mappend` comparing f2 `mappend` comparing f3

它产生一个可应用于两个Ds 的函数,以产生一个Ordering.)

的定义compareD非常简单,显然是正确的,即使没有静态类型检查,我也不觉得需要对其进行单元测试。

实际上,这个问题可能比这更有趣,因为我可能不想只使用标准<=>运算符,而是在不同的时间以不同的方式排序,例如:

sortByOrderings :: [a -> a -> 排序] -> [a] -> [a]
sortByOrderings = sortBy 。foldl1 映射

sortByF3F1 = sortByOrderings [比较 f3,比较 f1]
sortByF2F3 = sortByOrderings [比较 f2,比较 f3]

所以,问题:

  1. 在 Ruby 中实现这类事情的典型方法是什么?
  2. 仅使用标准库中定义的内容最好的方法是什么?
  3. 相比之下,与上面的 Haskell 代码有多接近,它的可靠性如何?如有必要,如何确保字段具有正确实施的<=>or<>运算符?

顺便说一句,虽然这是一个 Ruby 问题,但如果该站点的长辈们同意,我很高兴考虑讨论有关 Haskell 技术的主题。请随时评论这是否合适,如果合适,请将此帖子标记为“haskell”。

4

4 回答 4

8

以下是我为使自定义排序规则更易于管理所做的工作:在我需要排序的所有类中,我定义了返回数组的“to_sort”方法,然后覆盖 <=> 以使用 to_sort:

class Whatever
  def to_sort
    [@mainkey,@subkey,@subsubkey]
  end

  def <=>(o)
    self.to_sort <=> o.to_sort
  end
end

因此,对任意数组的排序(包括任意数组、任意其他数组和 Whathaveyours 的异构数组,所有这些数组都实现了特定于类型的 to_sort 函数和相同的 <=> 覆盖)只是在内部转移到对数组数组进行排序。

于 2009-05-19T14:08:03.843 回答
7

这是您的想法的即兴表演。它不定义任何额外的常量,允许您使用实例变量和方法的任意组合来比较两个对象,在不相等时提前退出,并包括 Comparable 定义的所有方法。

class Object
    def self.compare_by(*symbols)
        include Comparable
        dispatchers = symbols.map do |symbol|
          if symbol.to_s =~ /^@/
            lambda { |o| o.instance_variable_get(symbol) }
          else
            lambda { |o| o.__send__(symbol) }
          end
        end
        define_method('<=>') do |other|
          dispatchers.inject(0) do |_,dispatcher|
            comp = dispatcher[self] <=> dispatcher[other]
            break comp if comp != 0
            comp
          end
        end
    end
end

class T
    def initialize(name,f1,f2,f3)
      @name,@f1, @f2, @f3 = name,f1, f2, f3;
    end

    def f1
      puts "checking #@name's f1"
      @f1
    end
    def f3
      puts "checking #@name's f3"
      @f3
    end

    compare_by :f1, :@f2, :f3
end

w = T.new('x',1,1,2)
x = T.new('x',1,2,3)
y = T.new('y',2,3,4)
z = T.new('z',2,3,5)

p w < x   #=> checking x's f1
          #   checking x's f1
          #   true
p x == y  #=> checking x's f1
          #   checking y's f1
          #   false
p y <= z  #=> checking y's f1
          #   checking z's f1
          #   checking y's f3
          #   checking z's f3
          #   true

如果您愿意,您可以在其中插入一些额外的错误检查,以确保用于比较的值实际响应<=>(使用respond_to? '<=>'),并尝试在它们不响应的情况下给出更清晰的错误消息。

于 2009-05-20T15:00:22.497 回答
2

我采用了与 rampion 类似的方法,但想处理属性可能是nil.

module ComparableBy
  def comparable_by(*attributes)
    include Comparable

    define_method(:<=>) do |other|
      return if other.nil?
      attributes.each do |attribute|
        left  = self.__send__(attribute)
        right = other.__send__(attribute)
        return -1 if left.nil?
        return 1 if right.nil?
        comparison = left <=> right
        return comparison unless comparison == 0
      end
      return 0
    end
  end
end

示例用法:

SomeObject = Struct.new(:a, :b, :c) do
  extend ComparableBy
  comparable_by :a, :b, :c
end
于 2012-03-09T19:28:13.253 回答
0

好吧,这里有一个扩展的快速破解,以一种Object似乎相当不错的方式实现这一点。

class Object

    def self.spaceship_uses(*methods)
        self.const_set(:SPACESHIP_USES, methods)
    end

    def <=>(o)
        raise(NoMethodError, "undefined method `<=>' for #{self.inspect}") \
            unless self.class.const_defined?(:SPACESHIP_USES)
        self.class.const_get(:SPACESHIP_USES).each { |sym|
            self.send(sym) < o.send(sym) && (return -1)
            self.send(sym) > o.send(sym) && (return  1)
        }
        return 0
    end

end

class T

    def initialize(f1, f2) @f1, @f2 = f1, f2; end

    attr_reader    :f1, :f2
    spaceship_uses :f1, :f2

end

<这当然不处理任何类型问题,以确保>正确实现SPACESHIP_USES. 但是,作为 Ruby,这可能很好,不是吗?

简短的评论可以对此发表评论,但我有兴趣在其他答案中看到详细的讨论和扩展。

于 2009-05-19T12:59:01.407 回答