Die Durchführung des Ruby-<=> Combinator
-
22-08-2019 - |
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 D
s 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:
- Was ist der typische Weg, um diese Art der Sache in Ruby der Umsetzung?
- Was ist die schönste Art, es zu tun mit genau dem, was in den Standardbibliotheken definiert wird?
- 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.
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.