오늘은 지난번 글에 이어서 아래 그림처럼 행 번호 추가와 테이블 형태로 출력되도록 프로그램 개선해 보자.
우선 행 번호를 추가하도록 수정된 아래 코드를 보자.
each_with_index 메서드를 사용하여 쉽게 처리할 수 있는데 배열의 인덱스는 0부터이고 첫 번째 요소는 헤더를 담은 배열이므로 i 가 0이면 헤더명 '#NO' 를 젤 앞에 추가해 주고 i 가 0이 아니면 즉, 1이상이면 데이터를 담은 배열이므로 해당 정수값을 문자열로 변환하여 젤 앞에 추가해 주면 된다.
여기까지 작성한 후 실행해 보면 행 번호가 잘 출력되는 게 보일 것이다.
data = [["종목명", "구매단가", "현재가"],
["삼성전자", "77,500", "74,300"],
["현대차", "245,000", "246,000"]]
data = data.map do |row|
row.map do |col|
if col.is_a?(String)
col.encode("CP949")
else
col.to_s
end
end
end
data.each_with_index do |row, i|
if i == 0
row.unshift("#NO")
else
row.unshift(i.to_s)
end
end
col_widths = data.transpose.map { |cols| cols.map { |col| col.bytesize }.max }
data.each do |row|
row.each_with_index do |col, i|
print col.ljust(col_widths[i] + (col.size - col.bytesize) + 2)
end
puts
end
이제 헤더와 헤더 사이 그리고 값과 값 사이에 '|' 을 추가해 보자.
아래 코드를 보면 기존 코드와 달리 헤더와 값을 각각 print 메서드로 출력하지 않고 (내부 배열에 대해) map 을 통해 먼저 해당 열의 최대 너비에 맞춰 정렬된 문자열의 배열로 변환하고 이것을 join 메서드를 사용하여 문자열 하나로 결합시켰다. 즉 2차원 배열을 문자열을 담은 1차원 배열로 만들었다.
여기서 그냥 map 메서드에 블록을 전달하지 않고 map 메서드의 결괏값인 Enumerator 객체의 with_index 메서드를 호출하면서 블록을 전달했는데 이것은 인덱스가 담긴 블록 파라미터 i 를 사용해 col_widths 배열에서 해당하는 열의 최대 너비값을 가져오기 위해서이다.
그리고 join 메서드에 전달한 인수(" | ")에 이미 공백을 앞뒤로 추가하였기 때문에 ljust 메서드 호출 시 여백을 위해 너비에 더해줬던 '2' 는 제거하였다.
이제 프로그램을 실행해 보면 원하는 대로 헤더와 값들이 ' | ' 으로 구분되어 출력되는 게 보인다.
생략...
col_widths = data.transpose.map { |cols| cols.map { |col| col.bytesize }.max }
data = data.map do |row|
row.map.with_index { |col, i| col.ljust(col_widths[i] + (col.size - col.bytesize)) }.join(" | ")
end
puts data
이제 처음 보았던 대로 데이터가 실제 테이블 형태로 출력되도록 프로그램을 마저 수정해 보자.
아래 전체 소스 코드를 그대로 옮겨 놓았다.
우선 c_del, l_del, r_del 세 개의 변수를 선언했는데 변수 c_del 은 바로 직전 코드에서 사용했던 문자열 ' | ' 을 할당해 주었고 l_del 은 테이블 모양을 만들기 위해 가장 좌측에 보여줄 '| ' 문자열을, 그리고 r_del 은 가장 우측에 표시할 ' |' 문자열을 할당해 주었다.
그리고 라인 표시를 위해 전체 너비를 계산해야 하는데 먼저 각 열의 최대 너비 값을 모두 더하고(col_widths.sum) 거기에 c_del 구분자의 너비(length)에 필요한 개수(col_widths.size - 1)를 곱해서 더해준 후 마지막으로 l_del 과 r_del 의 너비(length)를 더해주면 된다. 이렇게 구한 전체 너비(tot_width) 값을 사용하여 테이블의 행과 행을 구분하기 위한 라인(line)을 만들었다.
이때 문자열의 '*' 연산자 메서드를 사용했는데 문자열의 '*' 연사자 메서드는 대상 문자열을 원하는 만큼 반복해서 이어붙인 새 문자열을 반환해 준다. 즉, "abc" * 3 을 실행하면 "abc" 문자열이 세 번 반복된 "abcabcabc" 문자열을 돌려준다.
실행해 보면 이 글의 시작 부분에 보았던 실행 화면과 동일하게 출력되는 걸 볼 수 있다.
data = [["종목명", "구매단가", "현재가"],
["삼성전자", "77,500", "74,300"],
["현대차", "245,000", "246,000"]]
data = data.map do |row|
row.map do |col|
if col.is_a?(String)
col.encode("CP949")
else
col.to_s
end
end
end
data.each_with_index do |row, i|
if i == 0
row.unshift("#NO")
else
row.unshift(i.to_s)
end
end
col_widths = data.transpose.map { |cols| cols.map { |col| col.bytesize }.max }
c_del = " | "
l_del = "| "
r_del = " |"
data = data.map do |row|
row.map.with_index { |col, i| col.ljust(col_widths[i] + (col.size - col.bytesize)) }.join(c_del)
end
data = data.map { |str| "#{l_del}#{str}#{r_del}" }
tot_width = col_widths.sum + (c_del.length * (col_widths.size - 1)) + l_del.length + r_del.length
line = "-" * tot_width
puts line
data.each_with_index do |str, i|
puts str
puts line if i == 0
end
puts line
다음에는 오늘 만든 프로그램을 common_lib 에 넣어서 필요한 프로그램에서 바로 가져다 사용할 수 있도록 만들어 보겠다.
그리고 추가로 원하는 위치의 값에 색깔을 입힐 수 있는 옵션 기능도 추가해 보겠다.
See you again~~