루비 초보입니다. 간단한 문제를 풀려고 하는데 쉽게 되질 않네요.
http://programming-challenges.com/pg.php?page=downloadproblem&probid=110102&format=html
이 문제를 풀려고 다음과 같이 코드를 짰습니다만
gets=~/(\d+)\s(\d+)/
m, n=$1.to_i, $2.to_i
field, num=[m][n]
0.upto(m) {|i| gets=~/([*.])*\s/; 0.upto(n) {|j| field[i][j]=$j}}
def adj(minefield)
id=0
for k in (i==0)? (i):(i-1)..(i==m)? (i):(i+1)
for l in (j==0)? (j):(j-1)..(j==n)? (j):(j+1)
id+=1 if (minefield[k][l]=='*')
end
end
return id
end
0.upto(m) {|i| 0.upto(j) {|j| print num[i][j]=adj(field[i][j])}; puts ""}
Problem 002 Inbae$ ruby Problem002.rb
3 5
*.***
Problem002.rb:4: undefined method `[]' for nil:NilClass (NoMethodError)
from Problem002.rb:4:in `upto'
from Problem002.rb:4
from Problem002.rb:4:in `upto'
from Problem002.rb:4
라네요. 뭐가 문제인지 잘 모르겠습니다... 어떻게 하면 개선할 수 있는지, 그리고 어떻게 하면 코드를 좀 단축할 수 있을지 알고 싶습니다. 일차원 배열을 사용하면 더 쉽겠지만 일부러 이차원 배열을 써 봤습니다.
아, 그리고 곡괭이 책을 보면서 공부하고 있는데, 책 구성이 여태 보던 언어 책이랑 좀 달라서 지식이 체계화가 잘 안 되네요. 참고할 만한 보조 자료 있을까요? 한국어면 더 좋구요.
ruby는 잘모르지만..
ruby는 잘모르지만.. field가 nil이라는 이야기네요..
이부분이 잘못된거 같은데... 원하시는게 m x n 2차 배열 만드는 거라면.. 관련 문법을 참조해 보시면 될듯합니다.
구글링 해보니
뭐 이렇게 하라는 거 같은데.. 더 좋은 코드는 다른 분들이 달아주시겠지요..
같은 내용인데.. 그냥 더 짧은겁니다.
히히 더 짧게
m,n=4,5
field = [[nil] * n] * m
이게 약간 함정이 있습니다.
Ruby의 Array의 * 연산자가 deep copy를 하지 않습니다. 그래서 fields array 내부의 n 길이의 array가 모두 같은 참조값을 가집니다.
이렇게 확인할수 있습니다.
아 그렇군요.
역시 안써봤던 repeat 연산자를 아무 생각없이 사용했더니 흐흐
field, num=[m][n] # =>
field, num=[m][n] # => nil
만약 2차원 배열을 동적으로 만들고 싶다면
[] << []
이 방법으로 생성가능하옵니다.
- Why don't you come in OpenSolaris? I hope you come together.
--
I think to myself...what a emerging world.
음...
다들 감사합니다.
gets=~/(\d+)\s(\d+)/
m, n=$1.to_i, $2.to_i
field, num=(1..m).map{|i| [nil]*n}
0.upto(m) {|i| gets=~/([*.])*\s/; 0.upto(n) {|j| field[i][j]=$j}}
def adj(minefield)
id=0
for k in (i==0)? (i):(i-1)..(i==m)? (i):(i+1)
for l in (j==0)? (j):(j-1)..(j==n)? (j):(j+1)
id+=1 if (minefield[k][l]=='*')
end
end
return id
end
0.upto(m) {|i| 0.upto(j) {|j| print num[i][j]=adj(field[i][j])}; puts ""}
이렇게 바꿔 봤는데 line 4에서 에러가 나는군요. 한 번에 선언과 처리를 동시에 할 수 있을 것 같기도 한데... 아직 잘 모르겠네요. 코드가 루비스럽지도 않은 것 같고 말이죠. 뭔가 우아하고 깔끔하면서도 짧게 끝나는 방법 없을까요?
field, num=(1..m).map{|i|
Enumerable#map은 &block으로 전달된 식을 평가한 값으로 배열을 되돌려 줍니다.
[nil] * n #=> [nil, nil, ... ] 이 되겠죠. 1..m 이라는 Range 클래스에의해 m번 반복되겠군요.
반복되었습니다. field, num = 이라는 부분에서 앞서 나온 map의 결과물인 배열이 '잘려서' 저장됩니다.
그리고 다음 부분에서 에러가 납니다.
문제는 이 부분
field[i][j]
으로 field가 2차원 배열로 선언되어있지 않기 때문입니다.여기까지면 에러에 대한 이유가 설명이 될 거라 생각합니다. 1.8.7에서 irb로 간단히 해본 것이니 문제가 있을 수도 있습니다만... -_-;;
----
그리고 잡설...
'루비'다운 건 없습니다. 우리의 무시무시한 Matz씨의 말처럼 루비는 프로그래밍을 즐길 수 있게 만들 언어이기 때문이죠.
'구조적' 으로 짤수도 있고 '객체지향적'으로 짤 수도 있습니다.
&block를 남발하여 자신과 타인을 괴롭힐수도 있고, brace를 쓰던 do end를 쓰던 자기 마음입니다. # design pattern까지 개떡같이 하자는 말은 아니구요 :)
루비다움에 너무 구속되지마세요. 자기가 짜는 게 바로 루비가 아닐까 생각합니다.
사실 짜는 사람마다 다 틀려요. 지금까지 제가 봐온 바대로라면요. :)
그리고 그걸로 토다는 사람은 아직 못 봤습니다. 그냥 자유롭게 짜세요.
루비로 짜면 반드시 타 언어에 비해 50% 코드가 줄어든다! 이런 게 정답은 아니잖아요. :)
잡설이 길었습니다. :)
요즘 Rails의 환상에 빠져 Ruby는 어때? 라고 묻는 사람이 주변에 좀 있다보니 '반' Rails적인 잡설이 되었습니다. 별로 못 느끼실지도 모르겠지만... :)
- Why don't you come in OpenSolaris? I hope you come together.
--
I think to myself...what a emerging world.
아... 그렇군요.
그럼 Array.new로 선언하고 각 원소에 대해 Array.new로 선언하는 것과 map으로 선언하는 것이 다른 건가요?
그리고
gets=~/(\d+)\s(\d+)/
m, n=$1.to_i, $2.to_i
field=Array.new(m); num=Array.new(m)
0.upto(m) {|i| field[i]=Array.new(n); num[i]=Array.new(n)}
0.upto(m) {|i| gets=~/([*.])*\s/; 0.upto(n) {|j| field[i][j]=$j}}
def adj(minefield)
id=0
for k in (i==0)? (i):(i-1)..(i==m)? (i):(i+1)
for l in (j==0)? (j):(j-1)..(j==n)? (j):(j+1)
id+=1 if (minefield[k][l]=='*')
end
end
return id
end
0.upto(m) {|i| 0.upto(n) {|j| print num[i][j]=adj(field[i][j])}; puts ""}
로 바꾸었는데, 에러가 나는군요... adj 함수의 문제인가요? 원래 adj(minefield[i][j])로 하려고 했는데 역시나 안 되더군요. 루비를 쓰면 뭐가 가능하고 뭐가 불가능한지 모르겠네요 -_-;;
여하튼 0.upto(m) {|i| 0.upto(n) {|j| print num[i][j]=adj(field[i][j])}; puts ""}를 유지시키는 범위에서 adj를 짜려면 어떻게 해야 하나요?
Quote: 그럼 Array.new로
결과적으로 다를 수도 있고 같을 수도 있습니다.
일 경우 map은 배열을 반환하고 field, num = 의 = 연산자 법칙에 따라 그 배열 자체가 field, num에 저장되지 않고 [0]와 [1]이 각각 저장됩니다.
field가 2차원 배열이 될 수 없었던 이유가 여기 있습니다. map에 의해 반환되는 배열이 2차원 배열이었기 때문에 결과적으로 2차원 배열의 index 0과 1의 값이 저장되었기 때문이죠.
map으로 반환되는 값이 3차원일 경우에는 2차원 배열이 각각 저장되겠네요. :)
Array.new로 선언할 경우도 마찬가지입니다. 위 코드에서 문제가 되었던 부분은 field, num = 방식의 = 연산자 부분과 map이 반환하는 값이 2차원 배열이었다는 점이네요. :)
그리고 [nil]*4의 경우 [nil] 이라는 배열 4개가 생성될 것 같지만 사실은 '4개의 요소를 가진 1차원 배열'이 생성됩니다. 이 부분도 주의를 하시면 되겠네요.
안되는 것 많습니다. :)
아래와 같은 소스는 parsing 자체가 불가능하죠.
()로 method call 부분을 명확하게 해주면 문제가 없어집니다.
이 부분외에도 많을 거라 생각합니다. 루비를 짜실 때 일단 생각해보면 좋은 것은
1. perl, c 언어에서 비슷한 기능이 있었는가 # => 루비가 보수적이기 때문입니다. 본래 다른 언어에 있었던 기능이 포함이 되어있다면 그 사용법을 참고하는 것도 괜찮은 방법입니다.
2. {} / do end 어느 쪽으로 통일할 것인가 # => 가독성의 문제군요. 전 do end를 선호하는 편입니다. {}의 경우보다 do end를 쓸 경우 parser에 걸리는 부분이 좀 더 있기 때문에 어쩔 수 없이 소스가 깔끔해지는 경향이 있는 것 같습니다.
3. () 를 쓸 것인가? # => ()를 쓸 때와 쓰지 않을 때 코드의 구성(parse 문제)이 조금 달라집니다. Rails의 Model을 예로 들면
()를 쓸 경우 parser가 판단할 수 있는 범위가 명확해지므로, "모든 것은 文이다" 라는 루비의 기본 개념을 충실히 보장하게 됩니다만 반대로 '허용되는 모든 방법'을 사용하게 되므로 반대로 소스가 지저분해지는 경우도 많습니다.
---
또 주저리 주저리 길었습니다. 문제를 스스로 해결하시는데 중점을 두고(링크해주신 문제가 뭐하는 문제인지 영어가 딸려 몰라서 그러는 겁니다^^) 힌트(=, map)만 드립니다.
소스는 아래에 다른 분들이 써주신 걸 참고하시면 되겠네요. :)
- Why don't you come in OpenSolaris? I hope you come together.
--
I think to myself...what a emerging world.
루비 느낌? 흠 뭘까요..
adj 부는 다시 작성하시면 되구요.
파싱만 조금 루비 메소드들 사용한다면 이 정도 하면 루비 느낌을 느낄수 있겠네요.
그런데 이건 다른 언어를 해도 비슷할 겁니다. ;;
깔끔하고 좋네요.
깔끔하고 좋네요. :)
- Why don't you come in OpenSolaris? I hope you come together.
--
I think to myself...what a emerging world.
adj 계산을 간편하게 하기 위해 테두리 경계를 포함하는 배열로 한번 시도
이 책을 추천합니다.
이 책을 추천합니다. 루비 1.9 까지 버커합니다.
The Ruby Programming Language
http://oreilly.com/catalog/9780596516178
By David Flanagan, Yukihiro Matsumoto
Publisher: O'Reilly Media
Released: January 2008
Pages: 448
아래 책은 얇지만 일목요연하게 설명되어 있습니다.
루비 1.8.x 까지 커버합니다.
Ruby Pocket Reference
http://oreilly.com/catalog/9780596514815
By Michael Fitzgerald
Publisher: O'Reilly Media
Released: July 2007
Pages: 180
댓글 달기