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| EQ | GT

data D = D {f3 :: Int、f2 :: Double、f1::Char}派生ショー

compareD ::D->D->注文
compareD = foldl1 mappend [f1の比較、f2の比較、f3の比較]

(に精通していない人のためfoldに、上記はに拡張されます

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

Dこれは、2つのsに適用できる関数を生成し、を生成しOrderingます。)

の定義compareDは非常に単純なので明らかに正しいので、静的な型チェックがなくても単体テストの必要性を感じることはありません。

実際、この質問はこれよりも少し興味深いかもしれません。標準の<=>演算子だけを使用するのではなく、さまざまな時間にさまざまな方法で並べ替えるからです。たとえば、次のようになります。

sortByOrderings ::[a->a->注文]->[a]->[a]
sortByOrderings=sortBy。foldl1 mappend

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

したがって、Whateversの任意の配列(WhateversとWhateverothersおよびWhathaveyoursの異種配列を含み、これらはすべてタイプ固有のto_sort関数とこれと同じ<=>オーバーライドを実装します)の並べ替えは、配列の配列の並べ替えに内部的に委ねられます。

于 2009-05-19T14:08:03.843 に答える
7

これがあなたのアイデアのリフです。余分な定数を定義せず、インスタンス変数とメソッドの任意の組み合わせを使用して2つのオブジェクトを比較でき、等しくない場合は早期に終了し、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

私は大暴れと同様のアプローチを取りましたが、属性がである可能性がある場合を処理したかったの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

もちろん、これは入力の問題を処理しません。これは、のメソッドによって返されるオブジェクトに対して、<およびが適切に実装されていることを確認するためです。しかし、Rubyであるため、これはおそらく問題ありませんね。>SPACESHIP_USES

短いコメントでこれについてコメントすることができますが、他の回答で詳細な議論と拡張を見ることに興味があります。

于 2009-05-19T12:59:01.407 に答える