2009-05-19 7 views
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 

यह दोनों कठिन और त्रुटि-प्रवण है, खासकर कई क्षेत्रों के साथ। यह त्रुटि-प्रवण पर्याप्त है कि मुझे अक्सर लगता है कि मुझे उस कार्य को यूनिट परीक्षण करना चाहिए, जो केवल थकाऊपन और क्रियाशक्ति में जोड़ता है।

हास्केल ऐसा करने का एक विशेष रूप से अच्छा तरीका प्रदान करता है:

 
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 एस, Ordering का उत्पादन करने के लिए।)

compareD का निर्धारण है इतना आसान है कि यह स्पष्ट रूप से सही है, और मुझे स्थिर प्रकार की जांच के बिना भी यूनिट परीक्षण की आवश्यकता महसूस नहीं होगी।

वास्तव में, सवाल यह भी थोड़ा और अधिक दिलचस्प है, क्योंकि मैं सिर्फ मानक <=> ऑपरेटर का उपयोग करने के लिए नहीं चाहते हो सकता है, लेकिन हो सकता तरह अलग अलग समय पर अलग अलग तरीकों से, जैसे:

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

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

तो, प्रश्न:

  1. रूबी में इस तरह की चीज को लागू करने का सामान्य तरीका क्या है?
  2. मानक पुस्तकालयों में जो परिभाषित किया गया है उसका उपयोग करके इसे करने का सबसे अच्छा तरीका क्या है?
  3. उपरोक्त हास्केल कोड में कितना नज़दीक हो सकता है, और तुलना में यह कितना विश्वसनीय है? यदि आवश्यक हो, तो कैसे एक यह सुनिश्चित कर सकता है कि फ़ील्ड्स को <=> या < और > ऑपरेटरों को उचित रूप से लागू किया गया हो?

संयोग से, जबकि यह एक रूबी सवाल यह है कि, मैं विषय पर हास्केल तकनीक की चर्चा पर विचार करना है, तो इस साइट के वृद्ध तो इस बात से सहमत खुश हूँ। कृपया इस पर टिप्पणी करने में संकोच न करें कि यह उचित है या नहीं, और यदि यह है, तो इस पोस्ट को 'हैकसेल' भी टैग करें।

उत्तर

7

यहां आपके विचार पर एक रिफ है। यह किसी भी अतिरिक्त स्थिरांक को परिभाषित नहीं करता है, आपको दो ऑब्जेक्ट्स की तुलना करने के लिए आवृत्ति चर और विधियों के किसी भी संयोजन का उपयोग करने की अनुमति देता है, इसके बराबर प्रारंभिक निकास होता है, और तुलनात्मक रूप से परिभाषित सभी विधियों को शामिल करता है।

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? '<=>' का प्रयोग करके) का जवाब तुलना करने के लिए प्रयोग किया जाता है बनाने के लिए कुछ अतिरिक्त त्रुटि में जाँच डाल सकता है, और करने की कोशिश के मामले में स्पष्ट त्रुटि संदेश देना वे कहीं नहीं करते हैं।

+0

ग्रेट सामान। यह मुझे एक ही आसान 'sortBy' चीज़ नहीं मिलता है जो हास्केल करता है, लेकिन यह निश्चित रूप से डिफ़ॉल्ट तुलना से निपटने का एक अच्छा काम करता है! –

+0

ध्यान रखें कि आप Enumerable # सॉर्ट का उपयोग बहुत अधिक तरीके से कर सकते हैं जैसे Haskell's List.sortBy (बस करीबी के बिना, क्षमा करें)। और संख्यात्मक # sort_by आपको क्रमबद्ध समय पर सॉर्टिंग कुंजी को परिभाषित करने देता है। – rampion

+0

+1, रूबी कोड के बेहतर बिट्स में से एक मैंने SO पर देखा है – Allyn

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 में तरीकों से लौटे के लिए लागू किया जाता है बनाने के लिए। लेकिन फिर रूबी होने के नाते, यह शायद ठीक है, है ना?

लघु टिप्पणियां इस पर टिप्पणी कर सकती हैं, लेकिन मुझे अन्य उत्तरों में विस्तृत चर्चा और एक्सटेंशन देखने में रुचि होगी।

+2

इस संस्करण का मतलब है कि सभी वस्तुओं respond_to होगा? (: <=>), लेकिन सबसे एक NoMethodError बढ़ा देंगे। यह एक अच्छा विचार नहीं है। आप <=> की परिभाषा को स्थानांतरित करने का प्रयास कर सकते हैं: spaceship_uses कॉल, जो समस्या को ठीक करेगा। बस define_method का उपयोग करें (: <=>) करें ... अंत –

+0

भी, तुलनात्मक को शामिल करना न भूलें, इसलिए आपके अन्य तुलना ऑपरेटर आपके लिए परिभाषित किए गए हैं। – rampion

+0

यहां एक संशोधन है जिसमें मेरे और गायस के परिवर्तन शामिल हैं: http://gist.github.com/114798 – rampion

8

यहाँ मैं कस्टम अधिक प्रबंधनीय नियमों छँटाई बनाने के लिए कर दिया गया है: मेरी सभी वर्गों मैंने कभी सॉर्ट करने के लिए की जरूरत है पर, मैं "to_sort" तरीकों कि सरणियों वापसी को परिभाषित है, और फिर ओवरराइड < => to_sort उपयोग करने के लिए:

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

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

इस प्रकार व्हाटवर्स की किसी भी सरणी को सॉर्ट करना (व्हाट्सवर्स और व्हाट्सथोर और व्हाथवेयर्स के विषम सरणी समेत, जिनमें से सभी प्रकार-विशिष्ट to_sort फ़ंक्शंस को लागू करते हैं और यह < => ओवरराइड) केवल सरणी की सरणी को सॉर्ट करने के लिए आंतरिक रूप से devolves।

+0

नोट, यदि आपके किसी भी आवृत्ति चर 'शून्य' हैं तो यह उड़ाएगा। –

+0

हां, मैं वास्तव में इस और कुछ अन्य चीजों के लिए NilClass पैच करता हूं, साथ ही ... –

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 
संबंधित मुद्दे