NORU검색
카테고리Programming

백준 15663 : N과 M (9) - Ruby

Ruby로 백준 15663번 N과 M (9)을 숏코딩으로 풀이하며 입력 처리, 순열·중복 제거, 코드 골프 문법을 소개합니다.

태그
이 글의 목차

최근 친구들이 백준 문제를 풀기 시작했길래 저도 몇 문제를 풀어보고 있습니다. 그런데 그냥 푸는 것보단 목표를 세워놓고 하고 싶어서 몇 년 만에 Ruby를 쓰고, 가능할 것 같은 문제는 숏코딩(코드 골프)를 시도해보고 있습니다. 이번에 포스팅할 문제 역시 Ruby로 작성했으며, 작성일 기준 숏코딩 1위를 기록하고 있습니다. 풀이 자체는 간단하지만 루비에서 숏코딩에 사용하는 표현 정도를 간단히 소개할 수 있을 것 같습니다.

문제 정보

문제

N개의 자연수와 자연수 M이 주어졌을 때, 아래 조건을 만족하는 길이가 M인 수열을 모두 구하는 프로그램을 작성하시오.

  • N개의 자연수 중에서 M개를 고른 수열

입력

첫째 줄에 N과 M이 주어진다. (1<=M<=N<=8) 둘째 줄에 N개의 수가 주어진다. 입력으로 주어지는 수는 10000보다 작거나 같은 자연수이다.

출력

한 줄에 하나씩 문제의 조건을 만족하는 수열을 출력한다. 중복되는 수열을 여러 번 출력하면 안되며, 각 수열은 공백으로 구분해서 출력해야 한다. 수열은 사전 순으로 증가하는 순서로 출력해야 한다.

링크

백준 15663번 - N과 M (9)

코드

_,m,*a=$<.read.split.map &:to_i
a.sort.permutation(m).uniq{|c| [*c]}.each{|c|puts c*' '}

설명

아마 루비라는 언어를 잘 모르시거나, 루비의 여러 의미로 신기한 기능들을 접해보지 못 하신 분들께는 많이 당황스러우실 것 같습니다. 괜찮아요. 전 요즘 골프스크립트로 작성된 코드들을 보고 그런 걸 느끼고 있습니다. 무슨 문법일까요 대체

입력 한 번에 받기

이 문제에서 입력받는 것은 두 줄입니다.

N M
# # # #

N과 M, 그리고 나머지 숫자들을 배열로 받아야 합니다. 루비로 정직하게 코드를 작성한다면 이렇게 됩니다.

N,M = gets.split.map &:to_i

얘도 평범하지만은 않네요. gets는 한 줄을 입력받습니다. 이후 split을 통해 공백을 기준으로 나누고 map을 통해 각각을 to_i를 돌려서 숫자로 변환합니다. 조금 더 풀어서 쓰면 이렇게 됩니다.

N,M = gets.split.map{|c| c.to_i}

루비는 전개연산자를 지원하기 때문에

a,b,*c=[1,2,3,4,5] # a=1 b=2 c=[3,4,5]

위와 같은 방식으로 배열의 요소를 각각의 변수에 나눠 담을 수 있습니다. *을 함께 사용하면 나머지 요소를 배열로 묶어서 담게 됩니다.

gets를 통해서 한 줄씩 입력을 받는 것조차 숏코딩에서는 절약해야 합니다. 여기서 필요한 것은 ARGF라는 객체입니다. 커맨드라인 인수나 STDIN으로 입력받은 것을 스트림으로 다룰 수 있게 만들어진 객체인데, 루비에서는 놀랍게도 $<라는 별칭까지 제공해줍니다.

$<.read

이렇게 하면 모든 줄을 한 번에 받아오게 되는 것이죠. 이후에는 split과 map, to_i를 통해 숫자로 가공하면 입력받기는 끝입니다.

연산하기

입력받은 N개의 자연수(배열 a라고 하겠습니다)가 있는데, 이 배열 a를 기반으로 순열들을 구하는 문제입니다. Ruby에서 조합과 순열은 아래와 같은 메소드로 제공합니다.

# a = 배열
# m = 수열의 길이

# 순열
a.permutation(m)

# 조합
a.combination(m)

# 중복 순열
a.repeated_permutation(m)

# 중복 조합
a.repeated_combination(m)

그런데 문제에서 요구하는 건 정렬된 순열이죠. sort로 정렬하고, permutation으로 순열을 구하고, 중복되는 수열을 제거하기 위해 uniq를 사용합니다. 이후 그 순열들을 대상으로 each 메소드를 사용해 공백으로 구분해 출력합니다.

a=[1,2,3]
puts a*' '

배열에 문자를 곱하면 그 문자로 join을 수행합니다. 추가로 사용된 문법을 정리하면 아래와 같습니다.

# 루비는 함수를 호출할 때 매개변수가 없다면 괄호도 생략할 수 있습니다.
a.sort
a.sort()

# 매개변수가 있어도 괄호를 생략할 수 있습니다.
a.push(1)
a.push 1
# 괄호 한 쌍을 쓰는 것보다 띄어쓰기 하나가 더 짧죠.

# 루비에서 함수에 코드 블럭을 전달할 때 두가지 방법이 있습니다.
a.each do |c|
    puts c
end
a.each { |c|
    puts c
}
# do ... end보다는 { }를 쓰는 게 더 짧네요

후기

사실 백준 숏코딩 순위 페이지에도 적혀있지만 숏코딩(코드 골프)는 재미를 목적으로 생각하고 즐겨야하는 게 맞는 것 같습니다. 실무에서는 이렇게 짧게 짜는 것보다는 가독성이나 성능을 더 신경쓰는 게 중요한 상황이 대부분이었습니다. 하지만 기억이 가물가물한 언어를 다시 만져볼 겸, 평소라면 신경쓰지 않았을 생소한 기능을 사용해보기엔 숏코딩만큼 쉽고 재밌는 방법이 없는 것 같네요! 인터넷 검색을 해보시면 아시겠지만 루비에는 아직 수 많은 비밀들이 숨겨져 있습니다. 혹시 알고리즘 문제를 풀 때 새로운 경험을 해보고 싶으시다면 루비를 시작해보시는 건 어떨까요?