代码之家  ›  专栏  ›  技术社区  ›  cjs

Ruby<=>组合器的实现

  •  6
  • cjs  · 技术社区  · 16 年前

    通常,人们希望实现 <=> 产品数据类型上的(比较或“宇宙飞船”)运算符,即具有多个字段的类(所有这些字段(我们希望!)已经拥有 <= & gt; 实现),按一定顺序比较字段。

    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提供了一种特别好的方法:

    import Data.Monoid (mappend)
    import Data.Ord (comparing)
    
    -- From the standard library:
    -- data Ordering = LT | EQ | GT
    
    data D = D { f3 :: Int, f2 :: Double, f1 :: Char } deriving Show
    
    compareD :: D -> D -> Ordering
    compareD = foldl1 mappend [comparing f1, comparing f2, comparing f3]
    

    (对于不熟悉 fold ,以上扩展到

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

    它产生一个可以应用于两个 D s,产生一个 Ordering )

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

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

    sortByOrderings :: [a -> a -> Ordering] -> [a] -> [a]
    sortByOrderings = sortBy . foldl1 mappend
    
    sortByF3F1 = sortByOrderings [comparing f3, comparing f1]
    sortByF2F3 = sortByOrderings [comparing f2, comparing f3]
    

    所以,问题是:

    1. 在Ruby中实现这类事情的典型方式是什么?
    2. 仅使用标准库中定义的内容,最好的方法是什么?
    3. 与上面的haskell代码相比,它的可靠性有多高?如有必要,如何确保字段 <= & gt; < > 操作员?

    顺便说一句,虽然这是一个Ruby问题,但是如果本网站的长辈们同意的话,我很乐意考虑讨论haskell技术。请随时评论这是否合适,如果合适的话,也请将这篇文章标记为“haskell”。

    4 回复  |  直到 13 年前
        1
  •  7
  •   rampion    16 年前

    这是你的想法。它不定义任何额外的常量,允许您使用实例变量和方法的任何组合来比较两个对象,早期退出时不相等,并包含由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? '<=>' )并尝试 在这种情况下,给出更清晰的错误信息。

        2
  •  8
  •   glenn mcdonald    16 年前

    我要做的是使自定义排序规则更易于管理:在我需要排序的所有类上,我定义了返回数组的“to-sort”方法,然后重写<=>以用于排序:

    class Whatever
      def to_sort
        [@mainkey,@subkey,@subsubkey]
      end
    
      def <=>(o)
        self.to_sort <=> o.to_sort
      end
    end
    

    因此,排序任何whatever数组(包括whatever和whateverothers的异类数组以及whatehavours的异类数组,所有这些数组都实现了特定于排序函数的类型,以及相同的<=>覆盖)只会在内部转为对数组进行排序。

        3
  •  2
  •   Ryan McGeary    13 年前

    我采取了和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
    
        4
  •  0
  •   cjs    16 年前

    好吧,这里有一个快速黑客在一个扩展 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 . 但是,作为红宝石,这可能很好,不是吗?

    简短的评论可以对此进行评论,但我希望在其他答案中看到详细的讨论和扩展。

    推荐文章