プログラミング漫遊記

思ったことや、勉強したことをつらつらと。

【Ruby】ミュータブルとイミュータブル

フィヨルドブートキャンプでチェリー本の第1版の輪読会をやっているんですが、その時出てきた「ミュータブル」と「イミュータブル」について誤解していたのでまとめたいと思います。

ミュータブル・イミュータブルとは

Rubyのオブジェクトは「ミュータブルなオブジェクト」か「イミュータブルなオブジェクト」の2種類に分けられる。

じゃあミュータブル・イミュータブルってなんやねん、という話。

  • ミュータブル:破壊的な変更が可能なオブジェクト

  • イミュータブル:破壊的な変更ができない(そもそも破壊的メソッドが定義されていない)オブジェクト

ミュータブルなオブジェクトの例

# 文字列(String)
str = 'hello'
str.upcase! #=> 'HELLO'
str #=> 'HELLO'

# 配列(Array)
ary = []
ary.push(1, 2, 3) # => [1, 2, 3]
ary # => [1, 2, 3]

# 配列(Hash)
hash = {a: 1, b: 2}
hash.merge!({c: 3}) #=> {a: 1, b: 2, c: 3}
hash#=> {a: 1, b: 2, c: 3}

ミュータブルなオブジェクトは生成するたびに異なるオブジェクトになる

ミュータブルなオブジェクトは、例え同じ文字列でも生成するたびに異なるオブジェクトとなります。 同一オブジェクトかどうかはオブジェクトIDを確かめるとわかります。

なおオブジェクトIDは実行環境で変わります。

# 同じ文字列でも生成するたびに異なるオブジェクト
'aaa'.object_id
=> 63180

'aaa'.object_id
=> 65580

'aaa'.object_id
=> 67980

異なるオブジェクトなのでそれぞれを別々の変数に代入し破壊的な変更を加えても、お互いに影響がありません。

str1 = 'aaa'
str2 = 'aaa'

str1.upcase! #=> 'AAA'
str2 #=> 'aaa'

str1.object_id == str2.object_id
#=> false

ミュータブルなオブジェクトの注意点

次のようにするとどちらの変数も同じオブジェクトを参照しているため破壊的変更を加えるとどちらも変更されてしまいます。

str1 = 'aaa'

str2 = str1
str2 # => 'aaa'

str2.upcase! #=> 'AAA'
str1 => 'AAA'

# str1もstr2も同じ'aaa'というオブジェクトを参照している
str1.oject_id == str2.object_id
#=> true

イミュータブルなオブジェクトの例

Integer, Floatなどの数値やSymbolなどがイミュータブルなオブジェクトに当たります。 一意であることを担保されているため、同じ値であればオブジェクトIDは常に同じです。

# 数値(Integer, Float)
1.object_id
=> 3
1.object_id
=> 3
1.object_id
=> 3

(1.4).object_id
=> -21617278211378382
(1.4).object_id
=> -21617278211378382
(1.4).object_id
=> -21617278211378382


# シンボル(Symbol)
:a.object_id
=> 808668
:a.object_id
=> 808668
:a.object_id
=> 808668

イミュータブルなオブジェクトはデフォルトでfreezeされている

ミュータブルなオブジェクトに対してfreezeメソッドを使うと破壊的変更ができなくなります。

一方、イミュータブルなオブジェクトはデフォルトでfreezeされています。(当たり前か、、、)

# ミュータブル
str = 'aaa'
str.frozen
#=> false

str.freeze
str.frozen?
#=> true

str.upcase!
#=> `upcase!': can't modify frozen String: "aaa" (FrozenError)

# イミュータブル
num = 1
num.frozen?
#=> true

ちなみに

「破壊的変更ができないこと」と、「変数に再代入できないこと」は別物です。ここを混同して勘違いしていました。 freezeしても「破壊的変更ができない」だけで再代入して参照するオブジェクトが変わってしまったら破壊的変更が加えられます。

僕は「再代入」を破壊的変更だと思い込んでいたので、そもそも再代入できないのでは?と誤解していました。

str = 'aaa'
str.freeze

# freezeしたので破壊的変更は加えられない
str.upcase! 
#=> `upcase!': can't modify frozen String: "aaa" (FrozenError)

str = 'aaa' # 異なるオブジェクトを再代入する
str.upcase!
#=> 'AAA'

参考

るりま:

2021年っていつだっけ?

2021年ももうすぐ終わるということで、そろそろ今年の振り返りブログを書いていきたいと思います!!!!

みたいな感じで2021年やり残した「今年の振り返りブログ」をあたかも現在が2021年の年の瀬であるかのように振る舞いながら振り返っていきます。

プログラミングのTipsみたいな話は一切出てこないし、全体的にプライベートな(プログラミング学習の)話しか書かないので悪しからず。

ではスタート!

1月

今年の1月はまだ学校で働いていました。

3年生担任ということもあり受験のことで頭がいっぱい。コロナ禍で受験対応が日に日に変わっていき右往左往していた気がする。

退職の話は去年から進めていたんですが、特殊な手続きを踏んで退職する都合上、膨大な書類の山を処理する羽目に。しんどかったな〜。

プライベートでは「めかぶ」にハマって晩御飯は一生めかぶ食べていた気がする。本当に食生活は最悪レベル。

2月

教室の雰囲気が混沌と化す時期。

スポーツ推薦や特色選抜で合格者が出始め、どこか気が抜けた生徒とピリピリする生徒が混ざり起きる現象。まぁ、毎年の風物詩。 幸い、生徒指導事案などはあまり起こらず、毎日誰かと教育相談をするくらいで乗り切れた。

部活動に関しては自分が主催する最後の大会がありました。2、3年前から中体連の専門委員になって土日も大体働いていたのでひと段落ついたことになる。

また、仕事が忙しくてストップしていたプログラミング学習は4月くらいになるだろうな〜と目星を付け出したのはこの頃。

3月

受験後の進路指導。引き継ぎ。卒業。退職。目の回る毎日。

離任式は中止だったにも関わらず、たくさんの生徒や保護者が駆けつけてくれたのはいい思い出。 8年間の教員生活は終わり、無職生活の突入。

果たしてなけなしの貯金で生活していけるのか!この頃は不安でいっぱいでした。今も不安だけど。

4月

あれ?仕事辞めたはずなのに、電話がじゃんじゃんかかってくる。

「あれってどうやっけ?」

「〇〇に書いときました!(怒)」

みたいなやりとりも多く、ちょっと怒ってた。

フィヨルドブートキャンプ再開。 前回は「Railsラクティス」まで進んでたけど、記憶喪失状態だったので大人しくHTMLのプラクティから始めることに。

最初は遠回りかなと思ったけど、結果的には一からやり直して知識が固まった気がするので大正解!!

久しぶりに再開したフィヨルドブートキャンプは結構変わっていて、受講者同士の交流が多くなってた。

この頃は散歩にハマっていて毎日神社の🐢を見るのが唯一の生きがいでした。

5月

ラクティスはSQL,DB設計,shinatraあたり。

DB設計では書籍『楽々ERDレッスン』を4回くらい通しで読んだことになるので、1回目と比べて理解度が上がったことに感動。

プライベートでは引越しの準備をしていたため、土日は家探しという感じ。 そして、引っ越すために断捨離を始めたのもこの頃から。本多すぎ!!

6月

ラクティスはRailsに突入。 まさか2ヶ月ちょっとで戻ってこれるとは思ってなかったのでびっくりした。反復こそ学習。

6月は新居が決定。引っ越しをして、同居生活が始まる。

同居生活1日目。前の家から持ってきてた冷蔵庫が壊れる。

同居生活2日目、オーブントースターが壊れる。

極めて順調な滑り出し。

もともと一人っ子で(生き別れた兄がいるけど笑)、10年以上1人暮らしをしていたので、人と住むなんてできるのか?と思ってたけど、大学生の時は寮生活をしていて、プライベートなんてないところで4年も生活していたことを思い出した。基本的にどこでも寝られるし、どこでも生きていける体質です。

この頃は昼ごはんにブロッコリーを食べることにハマっていて、ひたすらブロッコリーを食べていたらブロッコリー禁止令が出ました。

7月

ラクティスはRails,自動テスト,Rubyオブジェクト指向

ここら辺からRubyに対しての理解が1段階上がってかなり見通し良くなった。 忘れないうちにRubyのアウトプットしたいなーとDiscordの分報チャンネルで呟いていたらPaizaやAtCoder、をお薦めしてもらったり。

あと、伊藤さんからアウトプットの記事を紹介してもらったので、まずはそれからやることに。

blog.jnito.com

で、アウトプットしたことを発信したいという思いからブログを始める。

オブジェクト指向でなぜつくるのか』の輪読会も7月末から始まったので参加。

引っ越ししたのが落ち着いてコミュニティを意識し出したのがこの頃なのかもしれない。

隣の家が燃えた。

8月

ラクティスはJS。 カレンダーアプリやメモアプリを作成した。

Ruby以外の言語を学ぶのは初めてでめちゃくちゃ楽しかった。ただ、非同期処理!お前は別だ!

2つ目の輪読会であるRuby本(Ruby超入門とチェリー本)輪読会に参加。 今も続いてるくらいずっとやってる輪読会ですが、平日毎日やっているので自然と仲良くなれて、しんどい時などに支えてもらってます。ほんと、感謝しかない🙏

9月

ラクティスはnpm作成、Vue.js、アジャイルについての学習

AtCoderをはじめてみる。A問題やB問題を中心にやってましたが、B問題ですら難しいという現実。 Rubyの便利メソッドをたくさん知るきっかけになったのでよしとする。

退職してからは引きこもりを極めているので、自分の身に何も起きない。面白いことが全く起きない。散歩行った時に無駄に子供に絡まれるくらいで、ほんとにプログラミングしかしてないから何も起きない。プログラミングではエラーばっかり起きるのに。

リングフィットアドベンチャーをやらなくなった。

10月

ラクティスはチーム開発(スクラム開発)

現場Rails輪読会にRubyの章が終わるくらいから参加し始めた。これは厳密には9月から参加したかもしれないし、10月だったかもしれない。ただ、これくらいの時期。 現在も輪読会はやってるけど、初めからいましたよ感を出して参加してる輪読会の一つ。この輪読会のいいところはモブプロ形式で1つのアプリを作り上げていくので必然的にGitに詳しくなれるところ。

LT会初登壇。

大した発表はしてないけど、結果的にやってよかったなぁと思います。少しずつ自分の気持ちをアウトプットすることに慣れてきた気がする。

初モブプロ依頼。チーム開発で依頼されたコードレビューで全然わからなかったので依頼した。

それを皮切りに他の受講生の方とも生徒同士でモブプロしたり、少しずつモブプロ楽しい気持ちが芽生えてきた。 チーム開発でわからないことがあっても音声チャットで相談しながらモブプロしたり。今まで1人でもくもくと作業してきたので誰かと作業することが楽しかったのかもしれない。

教える・教わるのモブプロも楽けど、1つの問題に対してどんどん実装していくモブプロも楽しいことに気づく。

そのきっかけは@ikuma-tさん主催のこのモブプロに参加してからかな。

github.com

実家の冷蔵庫が壊れる。

11月

ラクティスはチーム開発、自作サービスを考え始める

最後のIssueがVueが絡む重めのIssueだったので結構時間かかったけど楽しかった。

github.com

そして、先月のモブプロの体験を活かして自分でもモブプロを主催してみることに。 https://github.com/haruguchi-yuma/janken

全2回のつもりをしてたんだけど、実は2回目は開催されていません。

輪読会メンバーでモブプロしたんですが、自作サービスが落ち着いたら考えようと思ってるうちに、メンバーの入れ替わりや、環境の変化、あとは自作サービスが落ち着くっていつだ問題。つまり自分の怠惰が原因で開催されてないので、どういう形かはわからないけど一旦蹴りをつけないといけないなという思い。

12月

ラクティスは自作サービス

イデア出しの段階でかなりつまづいて、結構辛かった記憶が、、、

ただ、アイデアだしで困っている人に一つアドバイスするとしたら的確なアドバイスがある。

それは、自作サービス進捗報告会に出ること。進捗なくてもいいし、アイデアが固まってなくてもいい。とにかく、今どんなこと考えているか話すだけでもいろいろアドバイスをいただけるのでおすすめ。

あとはアドベントカレンダーを書いたり伊藤さん主催の点字メーカープログラムに挑戦してみたりした。 このプログラムがTop Contributor賞を取るなんてこの頃は知らない。(未来予知)

まとめ

そんな感じで1年を振り返ってみましたが、ほとんどプログラミング漬けの毎日なのでプログラミングのことしか書くことがないことに気づきました。

来年の目標は「就職」なのでこれまたプログラミングですね。

あとは就職できてコロナが終息してたら旅行に行きたいです。

それではみなさん良いお年を。来年もよろしくお願いします。🙇‍♂️

困ったときはターミナルへ出力だ!プリントデバッグ入門

こんにちはフィヨルドブートキャンプで学習中のはるぐちです。

この記事はフィヨルドブートキャンプ Part 1 Advent Calendar 2021の14日目の記事になります。 前回はmasuyama13さんの「コードより先にコミットメッセージを書く」でした。

techblog.tebiki.co.jp

また、フィヨルドブートキャンプ Part 2 Advent Calendar 2021もあります。

対象読者

  • プログラミングを始めたばかりのあなた
  • フィヨルドブートキャンプ生
  • あの頃の自分

プログラミング経験がある人からすると「こんなん知ってるわ!!」となること間違いなしですが、過去の自分が知りたかったことをまとめた「超初心者向け」の内容ですので温かい眼差しで、目を細めて読んでいただけたら幸いです。

はじめに

「あれ?なんで動かないんだろ?」

プログラムを実行しようとすると必ずといっていいほど遭遇する(プログラムが思い通りに動かない)バグ。

僕もプログラミングを学習し始めた頃は、動かないプログラムに四苦八苦しました。(今でも)

英語で現れる読めないエラー。1文字のタイポのせいで空虚に消え去る1時間。変数の中身がnilだった時の絶望。「あぁ、無理だ。もう質問しようかなー」と分からないところを整理し始めた時に発見する解決方法。「絶対自分は間違ってないし」と強気に何度も実行するも「絶対的に自分が間違っている」現状。

いやですよね。プログラムが動かないのって。かっこよく1発で思い通りにプログラムを実行したいですよね。

安心してください!!1発でプログラムを動かそうなんてまぁ無理です!!

僕は2年間プログラミングを学習して(当初よりは)それなりにプログラムが書けるようになってきたとは思うんですが、プログラムは一向に動きません。ほとんど動きません。マジで全然動きません。たまに動きます。動くと可愛いです。

ということで、エラーを1回も吐かずに1発で実行できるプログラムを書くというのは土台無理な話なんですが、「プログラムが動かなくなった時に素早く原因を特定し、問題を解決できるようになる。」なら可能な気がします。そして、僕も、最初と比べたら問題解決までの時間が早くなった気がします。

この記事の方向性

プログラムが想定通り動かない時、原因を突き止め改善する作業ををデバッグといいます。デバッグをする手順としては以下のような手順が一般的です。

  1. プログラムが動かない、憤る
  2. エラーが出てないか確認する
  3. エラー文を見て怪しそうなところを調べる
  4. 怪しそうなところの変数やメソッドの戻り値が期待通りかどうか調べる
  5. 動く、喜ぶ

このようにデバッグをするときは、まずはエラー文を読め!ということになるんですが、この記事ではエラー文という謎の呪文をすっ飛ばして、とにかくターミナルに必要な情報を表示させるということに主眼を起きたいと思います。また、デバッグをするときはdebuggerなどの便利なツールもあるのですが、まずは最初に覚えたい「プリントデバッグ」という方法についてまとめていけたらなあと思います。

この方法を取得すると、エラーが出たときのみならず、コードを読むときに「この変数の中身なんだろう?」「このメソッドの戻り値点だっけ?」となった時も役立つと思います。

前置きが長くなりましたが、よろしくお願います。

ターミナルに出力して確かめる

メソッドを実行したときの戻り値や、変数の中身を知りたい時は「ターミナル(正確には標準出力)に出力するメソッド」を用います。次のコードを見てください。

# debug.rbというファイルに保存
numbers = [1, 2, 3, 4, 5]
even_numbers = numbers.select{ |num| num.even? }

ローカル変数even_numbersの中身に何が入っているのか知りたくなったとしましょう。(例が単純すぎて予測できますが、複雑なメソッドチェーンなどになっているときは分かりにくいですよね?ね?) この場合、pメソッドを使うとローカル変数even_numbersをターミナルに出力すことができます。

numbers = [1, 2, 3, 4, 5]

even_numbers = numbers.select{ |num| num.even? }
p even_numbers #デバッグのために追加

実行して確かめましょう

$ ruby debug.rb
[2, 4]

上の実行結果からeven_numbersは2と4というIntegerクラスのオブジェクトが入った配列であることがわかります。 ターミナル(標準出力)に出力するメソッドは他にもputsprintメソッドがありますが、それぞれ以下のような違いがあります。

p even_numbers
puts even_numbers
print even_numbers
$ ruby debug.rb
[2, 4]  # pメソッド(最後に改行されている)
2  # putsメソッド↓(配列の各要素が改行されている)
4
[2, 4] $ #printメソッド(改行されずにプロンプトが同一行にある)
メソッド 簡単な違い
p 配列の各要素を改行せずに表示し,全て出力した後で最後に改行が入る。データ構造(ここでは配列)がわかるので開発者にとって便利な情報が表示されている
puts 配列の各要素を改行して表示する。また、全て出力した後にも改行が入る。
print 1行で出力するため改行はない。上記では入力を受け付けるシェルのプロンプト$が同じ行に表示されているとこがわかりる。

色々違いがあることはわかったけど、どう使い分けるの?という疑問が湧いてくるかもしれません。 結論からいうとpメソッドは開発者にとって有益な情報を返してくれるので、基本的にはpメソッドを用いていれば大丈夫です。

pメソッドを使うとき注意点(結合度の違いに注意)

先ほどのコードを少し書き方を変えてpメソッドを使って式の評価結果を出力します。

numbers = [1, 2, 3, 4, 5]

# 例1
p numbers.select do |num|
  num.even?
end
=> #<Enumerator: [1, 2, 3, 4, 5]:select>

# 例2
p numbers.select do |num| num.even? end # 1行で書いてみたが、、、
=> #<Enumerator: [1, 2, 3, 4, 5]:select>

# 例3
p numbers.select{ |num| num.even? }
=> [2, 4]

do ~ endで記述(例1、2)したときと、{ ~ }で記述(例3)したときで出力結果が変わっています。どちらかというと{}で囲んだ時に出力されている情報が欲しいはずです。

つまり、上2つの実行ではnumbers.select do ~ endという式に対してpメソッドを用いて出力しようとしていますが、思っている通りの結果が返ってきません。

どうしてでしょうか?

これは {}よりdo ~ endの方が結合の度合いが強く、プログラムが以下のような解釈をしてしまっているせいです。

   ↓第一引数           ↓pメソッドに渡しているブロック
p(numbers.select) do |num| num.even? end
  • p メソッドの第一引数がnumbers.select
  • pメソッドにブロックを渡している(do ~ endの間)

このようなことを防ぐには最初に示した例のように、まず変数に代入し、その変数に対してpメソッドを使います。

even_numbers = 
  numbers.select do |num|
    num.even?
  end

p even_numbers
=> [2, 4]

メソッドチェーンの途中の結果を確認したい

メソッドをつなげて少し複雑な処理を1行でやってる(メソッドチェーン)場合はデバッグが面倒になります。 例えば、以下の例を見てみましょう。

names = ['sasaki', 'yamada', 'suzuki']

# メソッドをつなげて少し複雑な処理をしている
names.map{ |name| name + '!' }.map(&:upcase).sort.reverse

# 見やすく整えるとこんな感じ
names
  .map{ |name| name + '!' }
  .map(&:upcase)
  .sort
  .reverse
=> ["YAMADA!", "SUZUKI!", "SASAKI!"]

次の4つのことを行っています。

  1. 名前に!という文字列を連結
  2. 大文字にする
  3. 名前を昇順に並び替える
  4. 名前を降順に並び替える

このコードは正しくプログラムが動くためデバッグする必要はないのですが、もしプログラムが動かなかった場合どこのメソッドまでの処理が正しくて、どこのメソッドからの処理が間違っているのか問題の切り分けが難しい場合があります。

そんなとき、tapメソッドを用いるとメソッドチェーンの途中を確認するのに便利です。

names = ['sasaki', 'yamada', 'suzuki']
names
  .map{ |name| name + '!' }.tap{ |x| p x } # tapメソッドを追加
  .map(&:upcase)
  .sort.tap{ |x| p x } # tapメソッドを追加
  .reverse

=> ["sasaki!", "yamada!", "suzuki!"] # map{ |name| name + '!' }までの処理結果
=> ["SASAKI!", "SUZUKI!", "YAMADA!"] # sortまでの処理結果

るりまによるとtapメソッドは以下のように書いてあります。

self を引数としてブロックを評価し、self を返します。 メソッドチェインの途中で直ちに操作結果を表示するためにメソッドチェインに "入り込む" ことが、このメソッドの主目的です。
https://docs.ruby-lang.org/ja/latest/method/Object/i/tap.html

メソッドチェーンで処理が連なっているときはぜひtapメソッドを使ってみてください。

プリントデバッグしたけど見にくいなー

次のコードをみてください。

users = [ {id: 1, name: '太郎', address: '東京', age: 30 }, {id: 2, name: '次郎', address: '大阪', age: 20 }, {id: 3, name: '三郎', address: '名古屋', age: 10} ]

配列の中にハッシュが入っているなど、データが入れ子になっているとき、普通にデバッグしても見づらくよくわからない時があります。そんなときはppメソッドを使うと便利です。

p users
=> [{:id=>1, :name=>"太郎", :address=>"東京", :age=>30}, {:id=>2, :name=>"次郎", :address=>"大阪", :age=>20}, {:id=>3, :name=>"三郎", :address=>"名古屋", :age=>10}]

# 適度に整形して出力される
pp users
=> [{:id=>1, :name=>"太郎", :address=>"東京", :age=>30},
=> {:id=>2, :name=>"次郎", :address=>"大阪", :age=>20},
=> {:id=>3, :name=>"三郎", :address=>"名古屋", :age=>10}]

ppメソッドが見やすいように整形してくれていることがわかります。

【番外編】プリントデバッグ地獄に立ち向かう

プリントデバッグに慣れてくると、ある病を患ってしまいます。そう、「なんでもとりあえずpつけておく病」です。

この病は深刻で当時の僕をかなり苦しめました。pメソッドを使いすぎて本当に知りたい情報が埋もれてしまうのです。(不必要なpメソッドは消せばいいのですが、、、)

そんな時に便利なのが次の方法です。以下のコードはとりあえず用意したコードなので、意味がわからなくても大丈夫です。

def dot(moras)
  moras.map do |mora|
    p square = Array.new(6) { '-' }
    first_char, second_char = mora.start_with?(/[WY]/) ? mora.chars : mora.chars.reverse
    p first_char
    p second_char
    p FIRST_CONVERSION_TABLE[first_char]&.each { |i| square[i] = 'o' }
    p SECOND_CONVERSION_TABLE[second_char]&.each { |i| square[i] = 'o' }
    p square
  end
end

これは、、、やってしまっています。pメソッドが多すぎてプリントデバッグ地獄が起こっています。

例えば変数squareの中身が知りたいとき装飾をつけることで目立たせることができます。

def dot(moras)
  moras.map do |mora|
    p square = Array.new(6) { '-' }
    first_char, second_char = mora.start_with?(/[WY]/) ? mora.chars : mora.chars.reverse
    p first_char
    p second_char
    p FIRST_CONVERSION_TABLE[first_char]&.each { |i| square[i] = 'o' }
    p SECOND_CONVERSION_TABLE[second_char]&.each { |i| square[i] = 'o' }
    p '=' * 30  # 装飾追加
    p square
    p '=' * 30 # 装飾追加
  end
end

'====== ...'で挟み込んでいるので欲しい情報がわかりやすくなりました。

$ ruby sample_code.rb
["-", "-", "-", "-", "-", "-"]
"A"
nil
[0]
nil
"=============================="
["o", "-", "-", "-", "-", "-"]  # 本当に知りたかったsquareの中身
"=============================="
["-", "-", "-", "-", "-", "-"]
"A"
"K"
[0]
[5]
"=============================="
["o", "-", "-", "-", "-", "o"]  # 本当に知りたかったsquareの中身
"=============================="

とはいえ、不必要になったpメソッドはこまめに消しておきましょう。

【番外編】紛れ込むpメソッドを探すために。

一生懸命デバッグをしプログラムが思い通りの挙動をするようになりました。あとは提出するだけなのですが、デバッグ用に差し込んだpメソッドがどうやら紛れ込んでいるみたいです。 VScodeなどのIDEを使っているなら検索が使えますので、検索をして探し出しましょう。ここではVScodeので探してみます。

class Product
  TAX_RATE = 1.1
  
  attr_accessor :name, :price
  
  def total_price
    p price * TAX_RATE
  end

  def format_price
    "#{total_price}"
  end
end

上のコードからpメソッドを探し出したいとします。見たらわかりますが何百行もあると思ってください。検索をかけるにはVScodecommand + fを押します。

f:id:haruguchi_yuma:20211214100740p:plain

なんとpとつく単語が7つも出てきました。

そんなときは正規表現を使って\bp\bと検索してください。そのとき、正規表現を有効にするために.*のマークをonにします。

f:id:haruguchi_yuma:20211214100923p:plain

\bは単語の境界を表すメタ文字(特殊な役割の文字)でpだけにマッチするようになります。

最後に

いかがでしたでしょうか?プログラムが動かないとき、つい頭で原因を探ってしまいがちですが、実際に1つ1つプログラムの実行結果を調べることで解決に近づくのでぜひ使ってみてください!

改訂版・チェリー本発売記念 点字メーカープログラムに挑戦してみた!(Advent Calendar 2021/Qiita主催)

フィヨルドブートキャンプで学習中のはるぐちです。

伊藤淳一(@jnchito)さん著『プロを目指す人のためのRuby超入門/技術評論社』(通称チェリー本)の改訂2版が発売されたということでRubyプログラミング問題にチャレンジ! -改訂版・チェリー本発売記念-で出題されている「点字メーカープログラム」に挑戦してみました。アドベントカレンダー12月6日担当です。 qiita.com

こういう企画に参加するのは初めてだったのですが、

  • チェリー本に育てられたといっても過言ではないので発売記念イベントを是非盛り上げたい
  • 自分の書いたコードを他人に見てもらうのは良い学習になる
  • 他の人の書いたコードを読むのは良い学習になる

という思いから参加してみました。 よろしくお願いします!!!

プログラム作成前に考えたこと

仕様の確認

今回の点字メーカープログラムでは入力された文字列に対して点字のフォーマット(3×2)に合わせた文字列で出力するというものです。 入力値は訓令式のローマ字で構成される文字列で、1音ずつ半角スペースで区切られています。また、日本語音節でいう清音(あ行、か行、…、ら行、わ行)から「ゐ」、「ゑ」、「を」を除いた45音で考えるということになっています。つまり、濁音や促音、長音といった清音以外は考慮不要になっています。

例) A HI RU SI MA U MA KI RI Nなど

また、文字の間にスペースが空いてないなどの異常系の入力は考慮不要になっています。(よかった。)

ということでこれらの入力を想定した最低限動くプログラムを作成することにしました。というか、長音とか促音とか入ってくると自分の力では解ききれない可能性が😅

方針を立てる

プログラムの流れを整理してざっくりと方針を立てます。

  1. 'A HI RU'などを['A' , 'HI', 'RU']と半角スペースで区切って配列に分解する。
  2. 配列をループさせながら何らかの変換ロジックに従って変換する
    • 1音はNを除き「母音」+「0以上の子音」の組み合わせになっているのでそれぞれを分解する。
    • や行を除いて母音と子音で変換が干渉することはないのでそれぞれの変換を重ね掛けする。(最大2回変換する)
  3. 点字のフォーマット(3×2)に合わせて出力する

実際の作成物もおおよそこのような形で作成することができました。

実際に作ったものはこちら

GitHub PR https://github.com/JunichiIto/tenji-maker-challenge/pull/15

ロジックの解説

メインメソッド

メインメソッドから見ていきましょう。メインメソッドはto_tenjiというメソッドになっています。

def to_tenji(text)
  moras = text.split(' ') #方針立て1
  squares = dot(moras) #方針立て2
  format_tenji(squares) #方針立て3
end

メインメソッドの中身は作成前に立てた方針と同じような作りになっています。

まず、入力された文字列をString#splitを使って半角スペースで区切って1音ずつ配列に格納しています。変数名は音節を表す「モーラ(mora)」の複数形です。

次に、TenjiMaker#dotという点を打つ変換メソッド(後述)を作って変換しています。 戻り値は二重配列になっていて例えばA HI RUであれば次のような戻り値になっています。

# dot(['A' , 'HI', 'RU'])の戻り値
[
  ["o", "-", "-", "-", "-", "-"], # A
  ["o", "o", "o", "-", "-", "o"], # HI
  ["o", "-", "-", "o", "o", "-"] #RU
]

最後にそれらをフォーマットするメソッドを用いて出力しています。(後述)

ということで、点を打つ変換メソッドの中身を見ていきましょう。

変換ロジック(dotメソッド)

点を打つ場所についての考え方

点を打つ場所を考えるために下のサイトを元に(3×2)の場所について考えました。

全視情協:点字とは – 点字のしくみによると

点字は、縦3点、横2点の6点の組み合わせで作られています。そして、この単位をマスと言います。下の図をみてください。この図の①②④の点を組み合わせて母音を表し③⑤⑥の点を組み合わせて子音を表します。

① ④

② ⑤

③ ⑥

とあります。

つまり、点字のマスは縦に1、2、3…と番号が付けられていることがわかります。 正直、配列の操作のしやすさだけでいうと横に1、2…といった方が操作しやすいとような気がしてここはどうするか迷いました。(下参照)

# 配列で操作するならこっちの方がいいけど、、、
① ②

③ ④

⑤ ⑥

これはデータ構造をプログラムの都合に合わせるか、現実の点字のしくみに合わせるかというトレードオフになっていると思うのですが、仕事としてプログラムを書いたことのない自分にはどっちに寄せるべきか判断できなかったので以下のように基準を作りました。

  • できるだけ、現実の仕組みに合わせる
  • できるだけシンプルに表現する

できるだけ。というところで妥協し前者の方法(縦に番号をつけていく)で番号づけを行いながら配列操作のため番号を⓪番から始めるということを落とし所にしました。 つまり下の図のようになります。

# 現実の点字の仕組みに即しつつ、配列操作のため0番からはじめる
⓪ ③

① ④

② ⑤

そして、全視情協:点字とは – 点字のしくみを参考に各音と点を打つ場所(インデックス)を対応づけるテーブルを作りました。

FIRST_CONVERSION_TABLE = {
  'A' => [0],
  'I' => [0, 1],
  'U' => [0, 3],
  'E' => [0, 1, 3],
  'O' => [1, 3],
  'N' => [2, 4, 5],
  'Y' => [3]
}.freeze

SECOND_CONVERSION_TABLE = {
  'K' => [5],
  'S' => [4, 5],
  'T' => [2, 4],
  'N' => [2],
  'H' => [2, 5],
  'M' => [2, 4, 5],
  'R' => [4],
  'A' => [2],
  'U' => [2, 5],
  'O' => [2, 4]
}.freeze

このテーブルをよく見るとA,U,O,Nがどちらのテーブルにも重複して存在していますが、これは「ん」と「な行」でNの変換位置が異なることや「や行」が母音の変換位置が変わることを考慮し、役割に応じてそれぞれのテーブルに分けています。こうすることによってif分での複雑な条件分岐を避けることができました。

1音ずつループを回して打点していく

点を打つロジックはこのようになっています。

def dot(moras)
  moras.map do |mora|
    square = Array.new(6) { '-' }
    first_char, second_char = mora.start_with?(/[WY]/) ? mora.chars : mora.chars.reverse

    FIRST_CONVERSION_TABLE[first_char]&.each { |i| square[i] = 'o' }
    SECOND_CONVERSION_TABLE[second_char]&.each { |i| square[i] = 'o' }
    square
  end
end

このコード部分はmapメソッドで1音ずつ取り出し、点を打って配列にに格納するという作業を行なっています。

Array.new(6) { '-' }で基本となる1マス分の配列を作成しています。

[ '-', '-', '-', '-', '-', '-']`

ブロックを渡さずにArray.new(6, '-')とすると'-'という要素が同一オブジェクトになり、1つ変更すると全ての'-'が変更になってしまうので注意が必要です。

ポイントはfirst_char, second_char = mora.start_with?(/[WY]/) ? mora.chars : mora.chars.reverseの部分でString#charsで1音を1文字に分解するのですが、

  • 「わ行」や「や行」が入っている場合はA U Oといった母音はSECOND_CONVERSION_TABLEで変換してほしい
  • それ以外の音はA I U E Oといった母音はFIRST_COVERSION_TABEで変換してほしい

という比較的簡単な条件分岐になっています。

どのテーブルで変換するかを仕分けした後は、それぞれEnumarable#eachをつかって該当のインデックスに点(ここでは文字列oですが)を打っていきます。最後にループの内部で打点したsquaresという配列を返しています。

フォーマット部分

フォーマット部分は以下のようになっています。

def format_tenji(squares)
  transposed_squares = squares.map { |square| square.each_slice(3).to_a.transpose.map(&:join) }
  transposed_squares.transpose.map { |rows| rows.join(' ') }.join("\n")
end

もしかしたらかなり読みにくいかもしれません。😅

メソッド1行目に関して squares.map { |square| square.each_slice(3).to_a.transpose.map(&:join) }は縦に番号をつけた関係で、まず配列を2分割して行と列を入れ替える作業を行なっています。その後、内部の配列を結合して行ごとの配列にしていいます。

1音に対するフォーマットのイメージ図は以下の通りです。

# each_slice(3)3点ずつ2つに分ける
[[ 0, 1, 2] [3, 4, 5]]

# transposeで行列の置換

[
  [0, 3],
  [1, 4], 
  [2, 5]
],

# map(&:join)で内側の要素を結合する
[
  [03], # 1行目
  [14], # 2行目
  [25] # 3行目
]

メソッドの2行目 transposed_squares.transpose.map { |rows| rows.join(' ') }.join("\n")に関しては各音の1行目、2行目、3行目を揃えるためにtransposeして内部の要素を半角スペースで結合しています。

イメージ

# transposeで行列の置換
[ 
  [[03], [03], [03]...], # 1行目どうし
  [[14], [14], [14]...],  # 2行目どうし
  [[25], [25], [25]...] # 3行目どうし
]

# mapで行同士を結合
[
  [03 03 03...],
  [14 14 14...],
  [25 25 25...]
]

# join("\n") で改行しながら結合
[ 
  03 03 03 ... \n
  14 14 14 ...\n
  25 25 25 ...\n
]

最後にこれをputsして完成です。

作ってみての感想

アピールポイント

変換テーブルを2つ用意して、条件分岐の複雑さを回避したことでしょうか。 ただ、実際は促音や濁音などの清音以外の音が入ってくるのでこれは今回の仕様のみ使える方法だと割り切って考える必要がありそうですね。

フォーマット部分でややこしいことはしていますが、難しいことはしていないと思うので、ロジックの単純さがアピールポイントになると思います!!

苦労したところ

点字1マスの点の番号づけに苦労しました。

横に番号をつけていくと配列操作が楽になりフォーマットのロジックが簡単になりますが、変換テーブルを見たときにインデックスの意味がわからなくなるし、縦に番号をつけていくと変換テーブルの意味は分かりやすくなりますが、フォーマットするロジックは複雑になります。

点字の点の打ち方と比べてフォーマットは仕様によってコロコロ変わる恐れがあると思い、今回に関しては変わりにくい点字のインデックスの方をメンテナンスしやすいように、現実に即した番号づけをしました。

最後に

『プロを目指す人のためのRuby超入門』の改訂2版の発売おめでとうございます。現在フィヨルドブートキャンプでは初版の方で平日の夕方輪読会をしていますが、そこで一緒になって「あーでもない、こーでもない」と知識を探求する仲間に出会えました。この2版を使ってしっかり学んでいきたいと思います。

最後まで読んでくださってありがとうございました。まだまだ、アドベントカレンダーは続きますのでお楽しみください!!

初めてのLT会 vo.9に登壇しました。

10月9日にフィヨルドブートキャンプ内で行われたLT会に登壇しましたので、重い腰をあげて今更ブログにしていきます。テーマは「しくじり」ということで自分が過去しくじった体験を元に発表していきました。 以下は自分の発表スライドです。

speakerdeck.com

他の登壇者の内容やスライドに関してはタマキさんのこの記事がとても詳しく書かれているので参考にしてください。

shirotamaki.hatenablog.com

内容に関する記事というよりは登壇して、内容を考えて、発表してといった流れの中で自分がどんなことを考えて作ったのかという記事です。というか日記です。

心境をつらつら、たらたらと綴っていきます。ね。

登壇するかどうか問題

人前で自分のことを話すのはけっこう勇気がいる。そして、「しくじり」はできるなら言いたくない。なぜなら顔から火がでるほど恥ずかしいからだ。でも、そういったしくじりにこそ学びが詰まっているし、伝えるならそういったものじゃないといけない気がする。考えやすいテーマではあるんだけど、その点ではちょっと迷った。僕の場合は「孤学(ぼっち学習)」だったという自分のしくじりから、コミュニティと繋がりをもった「独学」に変えていこう。コミュニティに参加していろんな人との繋がりをもとう。ということを4月から継続して挑戦してきたので、この登壇自体がまさにしくじりに対する挑戦になると思って、勇気を持って登壇した。また、人とのコミュケーションは自己開示が大きいと思ってるのでそういう理由もある。

そのほかに迷った理由は、単純にコロナのワクチン接種を登壇日の近くに入れていたこと。最終的にはワクチン注射の日程はずらした。これは大正解でワクチン接種後4、5日寝込む羽目になった。

タイトルを決める

割と何も考えずタイトルを報告してしまった。というしくじりがある。

長い間学習を休んでいて、4月から復帰した。4月からは学習に対しても色々工夫をしたのでその部分を抽象化すると「学びを学ぶ」というメタ認知っぽい結論に至った。ただ、現実問題5分という時間制約がある中で工夫したこと全部を言えるわけではなく、取捨選択していくうちに「コミュニティ参加」の部分だけが残った。タイトルとのずれが発生。これは次登壇するときの反省にしたい。

内容を決める

1週間くらい前から1日1時間をLT会準備に当てた。

ノートに伝えたいことを箇条書きして、線で繋げていく。授業なら発問から組み立てていってそこで「おやっ」と思うようなフックポイントを作っていくのだけど、インタラクティブなコミュニケーションができないので思考のフックポイントをどこに作るか悩んだ。 ちなみにharuguchiというハンドルネームは本名ではないというフックポイントを序盤で作ったけど、思った以上に驚かれてしまい、内容が入ってこなかった人もいるようで失敗だった。もっと軽いポイントを作るべきだった。

 スライドを作る

授業を作るときと同じ流れで作ってみた。この作業からはほとんど独り言に近い呪文をみたいなにかを唱えながら作る。人に聞かせられる作業ではない。

  1. 内容は一旦スライドに全部テキスト情報として書く
  2. スライドの内容を話してみる
  3. 違和感のあるところを削る
  4. 情報の軽重をを考え、軽の部分は全削除
  5. 削除した内容は「話」で置き換えるか「図・イラスト」で置き換えるか、「単純に削るか」を考える
  6. 1から繰り返す

<しくじりポイント>
スライドに乗せる情報をできるだけ少なくするのを心がけた。ここまではいい。その分テンポよくスライドを流していき、情報量が多い部分は図やイラストで置き換えるようにした。これが失敗。というのも、今回のLT会はチャットする場所と発表する場所が違っており、ディスプレイが1枚の人は画面を切り替えてチャットするから、意外とスライドをじっくり見れていなかったのではと思う。なので、今回は「話す」という聴覚情報をメインにして「図やイラスト」という視覚情報をサブにしたほうが良かったかもな。

発表する

不特定多数の人前で話す機会が多いので、緊張しないだろうと思っていたけど緊張はした。ただ、慣れの部分も大きいのでこれは何度も挑戦していくしかない。時間がかつかつで話の間が取れなかったのが少し残念。

登壇後

次挑戦するハードルが下がった。やっぱり1回やるのとやらないのでは天と地ほどの差があることを実感。次は外部のLT会などにも挑戦していきたい。

感想

しくじらないと見えてこない世界がある。挑戦したら大なり小なり何かしら必ずしくじると思うので、しくじりは勲章みたいだなと思った。

そういう意味では良いしくじりは挑戦した結果。ダメなしくじりは、やりたいという思いがあるのになにもしない後悔であることが多いと思う。

この記事を書くのにかなり時間がかかっているが、これはLT会で発表した後すぐに感想を書かなかったという「何もしなかった」しくじりなので、ダメなしくじりだということは示しておきたい。

フィヨルドブートキャンプ8・9月の学習ふり返り

この記事はフィヨルドブートキャンプで学習しているかわせさんの記事に触発され、自分も学習の成長を感じられるようにふり返りたいな〜という願望から書いた記事でございます。

kawase-k.hatenablog.com

タイトルの感じからパクらせて真似させてもらっていますが、あの素敵な絵だけは真似できないのでみなさんぜひ読んでください。内容も素敵です。

8月のふりかえり

個人的な話。7月は引っ越しでバタバタしていたのですが、8月になってようやく落ち着いてきました。学習時間もおおよそ満足できるくらいになってきて、生活リズムも安定し、フィヨルド内のいろんなコミュニティに参加できるようになったのもこの頃から。

学習時間と内容

フィヨルド内での日報を書く時間・ブログを書く時間なども学習時間に含めています。何でもかんでも学習時間に入れて自分をほめていくスタイル。

やったこと 細かい内容 おおよその時間
JavaScriptの基本的な学習 Webサイトjsprimer,書籍『初めてのJavaScript』,FizzBuzzプログラム, カレンダープログラム 15h
非同期処理 YouTubeの動画で学習 8h
メモアプリ コマンドラインインターフェースで動くメモアプリの作成。 45h
npm作成 4x4のナンバープレース数独)生成npmを作った 12h
オブジェクト指向 書籍『オブジェクト指向でなぜつくるのか』、輪読会参加 10h
Ruby 書籍『Ruby超入門』の輪読会 11h
Railsの復習 書籍『Ruby on Rails 5 速習実践ガイド』 25h
ブログ このブログを始めた 5h
日報 フィヨルドブートキャンプでの日々の学習の記録 25h

最終的な学習時間は160hくらいになりました。日報にかなりの時間を使っていることが判明。

8月はJavaScriptの学習がメインでした。Rubyに続く2つ目の言語ということもあって、文法の差異や非同期処理など難しいところが多く、戸惑いながらの学習でした。しかし、今振り返ってみると違う言語を学習することでRubyについての理解も深まったので良かったです。

ラクティスでいうとメモアプリのCLI版を作るのはなかなか辛かった、、、。 記事にもしています。

haruguchi-yuma.hatenablog.com

enquirerなどのnpmの使い方がわからずかなりの時間を調べ物に費やしていた気がします。クラス化することに関してはオブジェクト指向(Ruby)のプラクティスである程できる状態になったのでJavaScriptでも同じ考えで作ることができました。

逆にnpmを作っている時間はとても楽しく「気づけば3時間経っていた」みたいなことも多かったです。テーマは「人の役には立たないが自分が欲しいものを作る」です。npmを公開したときはなんだかプログラマーの一歩目を踏み出したかのような感動がありました。

www.npmjs.com

8月の新たな取り組み

8月はいろんなことに挑戦した気がします。

  • 輪読会に参加
  • 仕事を辞めて以来のミートアップに参加
  • ブログを再開
  • 動画教材を使用してみた

輪読会に参加

Ruby本2冊(『Ruby超入門』と『プロを目指す人のためのRuby入門』)よみんなで読んでいく輪読会に参加しました。 それまで、『パーフェクトRuby on Rails』には参加したことがあったのですが、最初数回ほど行ってからフィヨルドブートキャンプ自体に参加できなくなってしまい断念。7月から『オブジェクト指向でなぜつくるのか』の輪読会に参加して、少しオンラインでのコミュニケーションに慣れてきたので思い切って参加してみました。

結果的には参加して良かったです。Rubyの知識がつく以外にもメンバーとの交流が楽しく、やっとオンラインコミュニティー慣れできるようになった気がします。みんなでワイワイ学習する雰囲気があり、夕方まで猛勉強して疲れた頭をリフレッシュさせるいい機会になっています。

仕事を辞めて以来のミートアアップに参加

ミートアップとはフィヨルドブートキャンプ内で行われる毎月1回のオンラインの交流会で、受講生・卒業生・メンターの方とご飯を食べながら実際に交流できる機会になります。卒業される方の作った自作サービスのデモがあったり、質問コーナーがあったり、フリートークのコーナーがあります。

昔、数回参加したことがあったのですがオンラインでコミュニケーションをとる難しさや恥ずかしさからしばらく参加していませんでした。輪読会に参加したことで少し顔見知り?(声見知り?)が増え、参加するハードルが下がったので思い切って参加してみました。話してみるとみんな「最初は緊張した」たとか「フリートークになったら接続切ってました」みたいな人もいて、自分だけじゃなかったんだと安心しました。

ブログを再開

過去に、似たようなふり返りブログは書いていたのですが定期的に継続することができずお蔵入りにし、8月から心機一転ブログタイトルを変更して始めてみました。これは、ミートキャンプでメンターさんが「ブログでなくていいけど、アウトプット大事だよ(意訳)」とおっしゃっていたので自分の書ける範囲で何か書き留めておこうと思い始めました。

ちなみにタイトルはスピッツのボーカルマサムネさんがやっているラジオ「ロック大陸漫遊記」からとりました。「プログラミング大陸漫遊記」にしたらよかった、、、

動画教材を使用してみた

JavaScriptの非同期処理がよく理解できず苦戦していると他の受講生の日報でYouTubeの動画教材のリンクが張ってありました。僕はどちらかというと自分のタイミングで読み進められる書籍での学習が好きでしたが、藁をもすがる気持ちで動画教材を使用してみました。集中力が切れないように1.5倍速で効いていると話者の例え話がイメージしやすく導入として動画で学習するのもありだなという気持ちになりました。

のちにVue.jsでの学習でも動画を使っているのでこの時の発見が生きました。

9月のふりかえり

9月はかなり集中して学習することができました。学習時間にも表れています。比較的アウトプットしている時間が多く、気づいたら3、4時間経っていた。ということが多かったように思います。

学習時間と内容

やったこと 細かい内容 おおよその時間
Vue.jsの基本的な学習 YouTubeの動画で学習、書籍『Vue入門』、Todoアプリ作成 42h
Vue CLI YouTubeでの学習、コンポーネント理解、Vue CLIでSPAのメモアプリ作成 14h
アジャイル開発/スクラム 書籍『アジャイル開発の教本』『SCRUM BOOT CAMP THE BOOK』 12h
チーム開発(スクラム開発) 割り振ってもらったIssueに対してPRを出す 7h
競技プログラミング AtCoder 8h
Git 書籍『独習Git』 10h
オブジェクト指向 書籍『オブジェクト指向でなぜつくるのか』輪読会参加、『オブジェクト指向設計実践ガイド』 15h
Ruby 書籍『Ruby超入門』『プロを目指す人のためのRuby入門』の輪読会 16h
Railsの復習 書籍『Ruby on Rails 5 速習実践ガイド』輪読会、『独習Ruby on Rails 30h
ブログ このブログ 3h
日報 フィヨルドブートキャンプでの日々の学習の記録 22h

全然計算合いませんが最終的な学習時間は205hになりました。

9月の前半はVue.jsの学習でした。いつもの如く、意味わからん!ということからのスタートでしたが、苦しんだ割には60時間弱で終わっているので、苦しいなりにうまく調べながら学習できたように感じます。 Vue.jsの学習に関したは最初書籍で学習していたのですが、難しすぎて全部読むことができませんでした。途中から動画教材に切り替え、具体的なアプリケーションを作りながら学んでいったので抜けてる知識も多いです。

Vue CLIを使ってSPA(シングルページアプリケーション)のメモアプリを作るプラクティスでは、コンポーネントを使って見た目部分を部品化してくのですが、親コンポーネントと子コンポーネントでのデータの受け渡しに苦戦しました。ここら辺のプラクティスからGoogleデベロッパツールを使い始めて、期待した値が入っているかの確認が上手くなったような気がします。正直、デベロッパーツールの機能の一部しか使いこなせてないですが、確実にハマったところから抜け出すのが早くなった気がします。

9月の後半はチーム開発のプラクティスを進めました。実際の仕事のように誰かと一緒に開発するということでワクワクと不安が半分ずつの状態。でも、プラクティスも佳境に入ったようで嬉しかったです。この頃から、Git操作とRailsが怪しかったので書籍で復習し始めました。

9月の新たな取り組み

9月もコミュニテイを意識した取り組みが多かったです。

新らしく輪読会に参加

9月から新らしく『Ruby on Rails 5 速習実践ガイド(現場Rails)』の輪読会に参加しました。この輪読会はフィヨルドブートキャンプのやつはしさんが主催する輪読会で朝の9時からDiscord上で行われています。自分にとっては初めましての方も多く、最初は参加するのに勇気が入りましたが、暖かく迎え入れてもらって居心地良く参加することができました。ちょうどチーム開発のプラクティスに入りRailsの復習をしたいと思っていたのでよかったです。

LT会に応募

フィヨルドブートキャンプでは不定期でLT会が開催されています。LT会とはライトニングトークの略で、登壇者が自分の気づきや発見、学習したことなどを短い時間で話す発表会のことです。 フィヨルド内では趣味でBotをつくる人がいたり、外部の学習会などでLT会に参加される人がいたり、日々誰かしらが挑戦しているコミュニテイです。そんな挑戦を見ているうちに自分も何かしたいなぁと思い。LT会に登壇ことに決めました。前向きな挑戦は周りに伝播するのだなぁと改めて思いました。

LT会は10月なので楽しみです。

質問に答える

フィヨルドブートキャンプ内のQ&AやDiscord上での質問に積極的に答えていきました。最初は間違ったこと言ってたらどうしようかなという思いがあったのですが、自分も人に助けてもらい理解することができたので積極的に答えていきたいです。間違っていたらメンターさんに訂正してもらえるという安心もあるかもしれません。

競技プログラミングやってみた

AtCoderの問題に挑戦してみました。https://kenkoooo.com/atcoder/#/table/

A,B問題は数をこなしていくうちに慣れてきたのですが、C問題は解けたり解けなかったりという感じ。そして、解けないと何時間でも考えてしまう性格なので、早々に封印しました。中毒性がありそうで要注意です。 1日1問だけって止められたらいいんだけど、、、数学っぽくて楽しかったので時間があったらまたやってみたいです。

2ヶ月を振り返って

やっとチーム開発のプラクティスまで来たかーという気持ちと、ここからまだまだ長いぞーという気持ち。

7月ごろから徐々にコミュニティに加わるようになって学習が一段と楽しくなってきました。一人でコツコツ学習するのも好きなのですが、学習で辛くなった時に仲間がいるっていうのは思っているよりも心理的安全を感じます。

やりたいことはまだまだあるので、ちょっとずつ挑戦して行けたらいいなと思います。

【Ruby】二次元配列についての色々な操作を考えてみた#2(配列の合計値)

こんにちは!はるぐちです。フィヨルドブートキャンプで学習中です。 この記事は二次元配列シリーズの第2弾で、二次元配列の合計値を計算することについて考えていきます。

前回の記事はこちら。

haruguchi-yuma.hatenablog.com

2.二次元配列の合計値を計算する

こんな二次元配列を用意します。

scores = [ [10, 20, 30 ] , [40, 50, 60], [70, 80, 90] ]

この配列全体の合計値を取得します。期待される値は10+20+…+90=450です。

結論

前回と同様、2パターン考えました。

  1. 配列の要素(内側の配列)を取り出しれぞれの合計値を出し、最後にそれらを足し合わせるパターン
  2. 配列を平坦化し、合計を出すパターン

以下がそのコードになります。

 scores.sum(&:sum)
#=> 450

scores.flatten.sum
#=> 450

解説

パターン1

上記の書き方は下の書き方の省略形になります。 下の書き方を例にどういった処理が行われているか確認していきましょう!

scores.sum{ |score| score.sum }

ブロック変数のスコアに対してsumメソッドを呼び出しています。 tapメソッドを使ってブロック変数scoreに何が入っているか確認します。

scores.sum{ |score| score.tap{ |o| p o }.sum } 
#=> [10, 20, 30]
#=> [40, 50, 60]
#=> [70, 80, 90]

ブロック変数scoreには[10, 20, 30]…などの内側の配列がブロック変数に入ってるのでそれぞれに対してsumメソッドで合計値を出しています。

scores.sum{ |score|  score.sum } 
# ブロックの中での処理のイメージ
[10, 20, 30].sum #=> 60
[40, 50, 60].sum #=> 150
[70, 80, 90].sum #=> 240

# 外側のsumの処理のイメージ
[ 60, 150, 240].sum #=> 450

パターン2

これは単純です。 flattenメソッドで配列のネストを無くします。

scores.flatten
#=> [10, 20, 30, 40, 50, 60, 70, 80, 90]

そこにsumメソッドで合計値を取得します。

るりまにはflattenの方が高速だと書いてあるので、速度を気にするならパターン2の方かもしれませんね。 参考:https://docs.ruby-lang.org/ja/latest/method/Array/i/sum.html

個人的にはsumメソッドにブロックを渡せるのが意外でした。 番外編ですが、sumメソッドにブロックを渡すと各要素の積の和を求めることもできます。

番外編 積の和

こんな配列を考える

nums_ary = [ [1, 2], [3, 4], [5, 6]]
# 各要素の積を足し合わせる

nums_ary.sum{ |nums| nums.inject(:*) }
#=> 44

# イメージはこんな感じ
# 1 * 2 = 2
# 3 * 4 = 12 
# 5 * 6 = 30
# 2 + 12 + 30 = 44

行列計算みたいですね。

つづくかも、、、