문제

드물지 않게, 누군가는 다음을 구현하고 싶어합니다. <=> 제품 데이터 유형에 대한 (비교 또는 "우주선") 연산자, 즉 여러 필드가 있는 클래스(모두 (희망합니다!) 이미 <=> 구현) 특정 순서로 필드를 비교합니다.

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

이는 두 가지에 적용할 수 있는 함수를 생성합니다. Ds,를 생산하기 위해 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. Ruby에서 이런 종류의 것을 구현하는 일반적인 방법은 무엇입니까?
  2. 표준 라이브러리에 정의된 것을 사용하여 이를 수행하는 가장 좋은 방법은 무엇입니까?
  3. 위의 Haskell 코드에 얼마나 근접할 수 있으며, 이에 비해 얼마나 신뢰할 수 있습니까?필요한 경우 필드가 올바르게 구현되었는지 어떻게 확인할 수 있습니까? <=> 또는 < 그리고 > 운영자?

그건 그렇고, 이것은 Ruby 질문이지만, 이 사이트의 장로들이 동의한다면 주제에 관한 Haskell 기술에 대한 토론을 고려하게 되어 기쁩니다.그것이 적절한지 아닌지에 대해 자유롭게 의견을 말씀해 주시고, 그렇다면 이 게시물에 'haskell'을 태그해 주세요.

도움이 되었습니까?

해결책

여기에 당신의 아이디어에 대한 리프가 있습니다. 여분의 상수를 정의하지 않으며 인스턴스 변수와 메소드의 조합을 사용하여 두 객체를 비교하고 동등하지 않은 초기 종료 및 비교 가능한 모든 방법을 포함 할 수 있습니다.

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? '<=>'), 그렇지 않은 경우에 명확한 오류 메시지를 제공하십시오.

다른 팁

사용자 정의 정렬 규칙을 보다 쉽게 ​​관리할 수 있도록 하기 위해 수행하는 작업은 다음과 같습니다.정렬이 필요한 모든 클래스에서 배열을 반환하는 "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 함수와 이 동일한 <=> 재정의를 구현하는 Whathave yours 및 Whathaves 및 Whathaveyours의 이질적인 배열 포함)의 정렬은 내부적으로 배열 배열 정렬에 맡겨집니다.

나는 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

글쎄, 여기에 연장 된 빠른 해킹이 있습니다. 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. 그러나 루비가되면 이건 아마 괜찮을까요?

짧은 의견은 이것에 대해 언급 할 수 있지만 다른 답변에서 자세한 토론과 확장을보고 싶습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top