Frage

Nicht selten will man die <=> (Vergleich oder „Raumschiff“) Operator auf einem Produktdatentyp, dh eine Klasse mit mehreren Feldern (von denen alle (hoffentlich!) Bereits umgesetzt hat <=>) zu implementieren, zu vergleichen die Felder in einer bestimmten Reihenfolge.

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

Dies ist sowohl langwierig und fehleranfällig, vor allem mit vielen Feldern. Es ist fehleranfällig genug, dass ich oft das Gefühl, ich soll Unit-Test, dass die Funktion, die die Langweiligkeit und Ausführlichkeit kommt noch hinzu,.

Haskell bietet eine besonders schöne Art und Weise, dies zu tun:

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]

(Für diejenigen, die nicht vertraut mit fold, die oben expandiert nach

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

, die eine Funktion erzeugt, die zu zwei Ds angewendet werden kann, wird eine Ordering zu erzeugen.)

Die Definition von compareD ist so einfach, dass es offensichtlich richtig, und ich würde die Notwendigkeit, Unit-Test nicht das Gefühl, es auch ohne statische Typprüfung.

Eigentlich kann die Frage sein, auch etwas interessanter als diese, da ich nicht nur den Standard <=> Operator verwenden möge, aber Art auf unterschiedliche Weise zu unterschiedlichen Zeiten, z.

sortByOrderings :: [a -> a -> Ordering] -> [a] -> [a]
sortByOrderings = sortBy . foldl1 mappend

sortByF3F1 = sortByOrderings [comparing f3, comparing f1]
sortByF2F3 = sortByOrderings [comparing f2, comparing f3]

Also, die Fragen:

  1. Was ist der typische Weg, um diese Art der Sache in Ruby der Umsetzung?
  2. Was ist die schönste Art, es zu tun mit genau dem, was in den Standardbibliotheken definiert wird?
  3. Wie nah kann man oben auf den Haskell-Code erhalten, und wie zuverlässig ist es, im Vergleich? Falls erforderlich, wie kann man sicherstellen, dass die Felder eine ordnungsgemäß umgesetzt <=> oder < und > Betreiber?

übrigens, während dies eine Ruby Frage ist, bin ich glücklich Diskussion der Haskell Techniken beim Thema zu prüfen, ob die Ältesten dieser Website damit einverstanden ist. Bitte fühlen Sie sich frei zu äußern, ob das angemessen ist oder nicht, und wenn es Tag ist, dieser Beitrag ‚Haskell‘ auch.

War es hilfreich?

Lösung

Hier ist ein Riff auf Ihrer Idee. Es definiert keine zusätzlichen Konstanten, können Sie eine beliebige Kombination von Instanzvariablen und Methoden verwenden, um zwei Objekte zu vergleichen, ein vorzeitiges Ausscheiden auf nicht-gleich ist, und umfasst alle durch Vergleichbar definierten Methoden.

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

Wenn Sie wollten, könnten Sie etwas mehr Fehler drin Überprüfung einfügen, um sicherzustellen, dass die Werte tatsächlich verwendet vergleichen reagieren auf <=> (respond_to? '<=>' verwenden), und versuchen, geben klarere Fehlermeldungen im Fall wwhere sie es nicht tun.

Andere Tipps

Hier ist, was ich tun Gewohnheit machen Regeln Sortierung handlicher: auf allen meinen Klassen muss ich jemals sortieren, I „to_sort“ Methoden definieren, die Arrays zurückgeben, und dann außer Kraft setzen <=> verwenden to_sort:

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

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

Sortier somit jede Anordnung von Was immer (einschließlich heterogenen Arrays von Was immer und Whateverothers und Whathaveyours, von denen all typspezifischen to_sort Funktionen und derselben <=> Übersteuerung implementieren) nur intern zufällt ein Array von Arrays zu sortieren.

Ich habe einen ähnlichen Ansatz wie rampion, aber wollte, den Fall behandeln, in denen Attribute nil werden könnten.

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

Beispiel Verbrauch:

SomeObject = Struct.new(:a, :b, :c) do
  extend ComparableBy
  comparable_by :a, :b, :c
end

Nun, hier ist eine schnelle Hack bei einer Erweiterung Object dazu in geschehen, was scheint ein recht netter Weg zu sein.

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

Das ist natürlich nicht mit irgendwelchen Tipps Fragen befassen, um sicherzustellen, dass < und > sind für die Objekte korrekt umgesetzt durch die in SPACESHIP_USES zurückgegeben. Aber dann zu gewinnen, ist Rubin, dies wahrscheinlich in Ordnung ist, ist es nicht?

Kurze Kommentare zu diesem Thema äußern können, aber ich würde zu sehen, ausführliche Diskussion und Erweiterungen in anderen Antworten interessiert.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top