Ruby를 사용하여 로마 숫자를 정수로 변환

사진 제공: Karl Callwood on Unsplash

스포일러 경고: 이 게시물은 일반적인 코딩 문제에 대한 해결책을 보여줍니다. 다음은 leetcode의 문제에 대한 링크이며 계속 읽기 전에 먼저 자신의 방식으로 문제를 해결하려는 경우 지침이 포함되어 있습니다. 그렇지 않으면 아래에서 나와 함께하십시오!

소개



최근에는 leetcode 및 codewars 솔루션을 정기적으로 제출하여 코딩 작업을 유지하려고 노력하고 있습니다. 워밍업을 위해 먼저 '쉬운' 것부터 시작해야겠다고 생각했습니다. 이것은 쉬운 것으로 분류되었지만 합격률은 58%에 불과합니다.

문제 정의



기본적으로 목표는 로마 숫자 문자열이 입력으로 주어지면 동등한 정수를 출력하는 함수를 작성하는 것입니다. 예를 들어 "MCMXCIV" => 1994 .

데이터 구조에 대한 생각.



내 첫 번째 생각은 기본 로마 숫자를 다음과 같이 해시 맵을 사용하여 해당 값에 매핑해야 한다는 것입니다.

def numerals
  {
    'I' => 1,
    'V' => 5,
    'X' => 10,
    'L' => 50,
    'C' => 100,
    'D' => 500,
    'M' => 1000
  }
end


나는 또한 문자열과 아마도 배열을 다루고 있다고 언급했습니다.

문자열 및 배열 조작



문자열의 문자도 평가해야 한다는 것을 깨달았습니다. Ruby에는 문자열의 개별 문자를 반복하는 편리한 string#each_char 메서드가 있으므로 'VIII'['V', 'I', 'I', 'I' ] 와 같은 문자 배열로 분할됩니다. 이제 배열로 작업하고 있으므로 각 숫자 문자를 해당 값에 매핑하는 데 사용할 수 있는 array#map 메서드에 액세스할 수 있습니다.


['V', 'I', 'I', 'I'].map { |n| numerals[n] }
# => [5, 1, 1, 1]

# using built-in array#sum method
['V', 'I', 'I', 'I'].map { |n| numerals[n] }.sum

# => [5, 1, 1, 1].sum
# => 8



이제 끝난거 맞죠? 우리는 이미 머릿속에서 하는 과정을 기본적으로 다루었습니다(5 + 1 + 1 + 1 = 8).

잡았다



그러나 까다로운 부분은 로마 숫자가 5 또는 10의 배수보다 하나의 인수보다 작은 숫자를 의미할 때 발생합니다. IV는 V보다 하나 적고 5에서 1을 빼고 4를 반환한다는 의미입니다. 위에 링크된 운동 지침에서:
  • V(5)와 X(10) 앞에 I를 배치하여 4와 9를 만들 수 있습니다.
  • X를 L(50)과 C(100) 앞에 배치하여 40과 90을 만들 수 있습니다.
  • D(500) 및 M(1000) 앞에 C를 배치하여 400 및 900을 만들 수 있습니다.

  • 위의 방법은 어쨌든 숫자를 "그룹화"하지 않기 때문에 문자열의 매핑된 문자를 단순히 합산하려는 계획에 렌치를 던집니다. 아마도 이러한 특별한 경우를 추출하는 방법이 있을 것입니다. IX 및 CM.

    먼저 위와 같은 해시로 정의해 보겠습니다.

    def special_numerals
      {
        'IV' => 4,
        'IX' => 9,
        'XL' => 40,
        'XC' => 90,
        'CD' => 400,
        'CM' => 900
      }
    end
    


    스캔 방법



    이제 문자열을 스캔하고 이러한 특수 숫자를 확인하는 방법이 필요합니다. 고맙게도 Ruby는 문자열에서 찾은 일치 항목의 배열을 반환하는 string#scan 메서드를 사용하여 쉽게 만들 수 있습니다.

      s = "MCMXCIV" # 1994
      special_matches = s.scan(/IV|IX|XL|XC|CD|CM/)
      # => ["CM", "XC", "IV"]
    


    이를 통해 위에서 사용한 것과 동일한 "매핑 및 합계"논리를 사용할 수 있습니다. 그러나 나머지 "비특수"문자는 어떻게 처리해야 할까요? 이것은 잠재적으로 일부 숫자를 두 번 세는 결과를 낳습니다. 간단한 점검으로 예방할 수 있습니다.

    먼저 문자열에 사용된 특수 숫자가 있는지 확인합니다. 그렇다면 해당 값에 매핑하고 합산하십시오. 이 합계를 변수에 기록합니다.

    gsub 방법



    둘째, 나머지 숫자를 정확하게 합산할 수 있도록 문자열에서 "특수"숫자를 제거합니다. 이것은 편리한 string#gsub 방법이 들어오는 곳입니다.

      s = "MCMXCIV" # 1994
      special_matches = s.scan(/IV|IX|XL|XC|CD|CM/)
      # => ["CM", "XC", "IV"]
      special_sum = special_matches.map { |m| special_numerals[m]}.sum
      # => 900 + 90 + 4 == 994
      special_matches.each { |m| s.gsub!(m, '') }
      # s is modified in place with its special numerals removed.
      # => s == "M"
      # we still need to add 1000 to our result
    


    따라서 입력 문자열은 이제 "M"이며 동일한 논리를 사용하여 합산할 수 있습니다. 이 경우에는 하나의 문자만 있지만 논리는 여러 문자에 대해 동일합니다.

      normal_sum = s.each_char.map { |c| numerals[c] }.sum  
      # => numerals['M'] == 1000
      # => [1000].sum
      # => 1000
    


    해결책



    모두 합치면 문자열에 "특수"숫자가 있는 경우 "특수"숫자 합계와 "정상"숫자 합계를 반환합니다. 그렇지 않으면 "정상"합계를 반환합니다. 코드에서 이 논리를 다음 솔루션으로 변환했습니다.

    def roman_to_int(s)
      special_matches = s.scan(/IV|IX|XL|XC|CD|CM/)
      if special_matches
        special_sum = special_matches.map { |m| special_numerals[m]}.sum
        special_matches.each { |m| s.gsub!(m, '') }
      end
      normal_sum = s.each_char.map { |c| numerals[c] }.sum  
      return (special_sum + normal_sum) if special_sum
    
      normal_sum
    end
    
    def numerals
      {
        'I' => 1,
        'V' => 5,
        'X' => 10,
        'L' => 50,
        'C' => 100,
        'D' => 500,
        'M' => 1000
      }
    end
    
    def special_numerals
      {
        'IV' => 4,
        'IX' => 9,
        'XL' => 40,
        'XC' => 90,
        'CD' => 400,
        'CM' => 900
      }
    end
    
    roman_to_s("MCMXCIV")
    # => 1994
    


    결론



    나는 이것이 leetcode에서 가장 빠른 솔루션이라고 생각하지 않지만 개인적으로 읽을 수 있다고 생각합니다. 이것은 숫자를 정수 값에 매핑하는 것을 간단하게 만든 해시 데이터 구조의 힘에 대한 증거입니다.

    이 문제를 어떻게 해결했습니까? 아니면 무엇을 다르게 할 수 있습니까? 아래 댓글로 알려주세요!

    좋은 웹페이지 즐겨찾기