Skip to content

wcコマンドの実装#12

Open
kutimiti1234 wants to merge 41 commits intomainfrom
my-wc
Open

wcコマンドの実装#12
kutimiti1234 wants to merge 41 commits intomainfrom
my-wc

Conversation

@kutimiti1234
Copy link
Owner

  • 標準入力を受け取れるように設定
  • 複数ファイルを吹けとれるように設定
  • "-l" "-w" "-c"のオプションに対応

Copy link

@JunichiIto JunichiIto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コメントしました!

05.wc/wc.rb Outdated
print_output(output_text, max_width)
end

DISPLAY_ADJUST_NUMBER = 1

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

number以外の名前がいいかも?
https://bootcamp.fjord.jp/pages/number-count-size-length

また定数はrequireの下あたりにまとめられることが多いですね

05.wc/wc.rb Outdated
{ line_display_width: max_lines_width, word_display_width: max_words_width, bytesize_display_width: max_bytesizes_width }
end

def parse_and_remove_options(argv)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removeってどの処理を指してるんでしょう?

05.wc/wc.rb Outdated
require 'optparse'

def main
argv_options = parse_and_remove_options(ARGV)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

05.wc/wc.rb Outdated

def main
argv_options = parse_and_remove_options(ARGV)
output_text = []

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

配列やハッシュは複数形にすることが多いです

05.wc/wc.rb Outdated
argv_options = parse_and_remove_options(ARGV)
output_text = []
ARGF.each(nil) do |input_text|
wc_info = []

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

最後に to_h するのではなく、最初からハッシュとして扱う方が自然だと思います

Suggested change
wc_info = []
wc_info = {}

05.wc/wc.rb Outdated
line = output_line[:line]&.to_s&.rjust(max_width[:line_display_width])
word = output_line[:word]&.to_s&.rjust(max_width[:word_display_width])
bytesize = output_line[:bytesize]&.to_s&.rjust(max_width[:bytesize_display_width])
filename = output_line[:filename] == '-' ? '' : output_line[:filename]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ファイル名が "-" になるケースってどんなときですか?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image
ARGFで標準入力を受け付ける場合に、スクショの通り"-"となってしまいます。

Copy link

@JunichiIto JunichiIto Jul 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほどー。
その情報が第三者にはぱっとわからないので(=マジックナンバーならぬマジックストリング)、情報を付加した方がいいですね。

そして今回の提出物ではこの処理がなくなってますね。これはなぜ? 🤔

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こちら削除行った理由としては、ARGF以外の処理で受け取れればもしかして標準入力を"-"として受け取らずにすむのではないかと考えて、一旦削除していたためです。

その情報が第三者にはぱっとわからないので(=マジックナンバーならぬマジックストリング)、情報を付加した方がいいですね。

こちらについて今回のpushでより説明的な形で実装しました。

05.wc/wc.rb Outdated

def get_max_width(output_text)
max_lines_width = output_text.map { |entry| entry[:line].to_s.length + DISPLAY_ADJUST_NUMBER }.max || 0
max_words_width = output_text.map { |entry| entry[:word].to_s.length }.max + DISPLAY_ADJUST_NUMBER || 0

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
max_words_width = output_text.map { |entry| entry[:word].to_s.length }.max + DISPLAY_ADJUST_NUMBER || 0
max_words_width = output_text.map { |entry| entry[:word].to_s.length + DISPLAY_ADJUST_NUMBER }.max || 0

ここだけこうしないのはなぜ?と思いました

05.wc/wc.rb Outdated
end

def get_max_width(output_text)
max_lines_width = output_text.map { |entry| entry[:line].to_s.length + DISPLAY_ADJUST_NUMBER }.max || 0

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

|| 0 っているんですか?(maxがnilになるケースってある?)

Copy link
Owner Author

@kutimiti1234 kutimiti1234 Jul 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

改めて考えると特に必要ないと思います。

05.wc/wc.rb Outdated
max_lines_width = output_text.map { |entry| entry[:line].to_s.length + DISPLAY_ADJUST_NUMBER }.max || 0
max_words_width = output_text.map { |entry| entry[:word].to_s.length }.max + DISPLAY_ADJUST_NUMBER || 0
max_bytesizes_width = output_text.map { |entry| entry[:bytesize].to_s.length + DISPLAY_ADJUST_NUMBER }.max || 0
{ line_display_width: max_lines_width, word_display_width: max_words_width, bytesize_display_width: max_bytesizes_width }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

max_width[:line_display_width] みたいなキーだとちょっと冗長ですね。
max_width[:line] でも十分かなーと思います

05.wc/wc.rb Outdated

def print_output(output_text, max_width)
output_text.each do |output_line|
puts format_output_line(output_line, max_width)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

format_output_line はメソッド化しなくてもいい気がしました
https://bootcamp.fjord.jp/pages/when-you-should-define-method

05.wc/wc.rb Outdated

require 'optparse'

DDISPLAY_ADJUST_LENGTH = 1

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
DDISPLAY_ADJUST_LENGTH = 1
DISPLAY_ADJUST_LENGTH = 1

で良かったりしません?

あと、このプログラムには display, print, output という用語が出てきますが、それぞれ意図があって使い分けてますか?(僕からするとどれもほぼ同じ意味に思える)

05.wc/wc.rb Outdated
wc_info[:bytesize] = input_text.size if options[:c]
output_texts << wc_info
end
output_texts << file_total_info(output_texts) if output_texts.size > MULTIPLE_DISPLAY

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2以上なら複数(multiple)というのはある意味当たり前なので、MULTIPLE_DISPLAYという定数名はこれといった情報を提供していないことになります。
なので、定数をなくしても構わないかも?

Suggested change
output_texts << file_total_info(output_texts) if output_texts.size > MULTIPLE_DISPLAY
output_texts << file_total_info(output_texts) if output_texts.size > 1

05.wc/wc.rb Outdated
wc_info[:line] = input_text.lines.count if options[:l]
wc_info[:word] = input_text.split(/\s+/).size if options[:w]
wc_info[:bytesize] = input_text.size if options[:c]
output_texts << wc_info

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

左辺と右辺の名前を見直してみましょう。
https://bootcamp.fjord.jp/pages/readable-variable-name

05.wc/wc.rb Outdated
output_texts << wc_info
end
output_texts << file_total_info(output_texts) if output_texts.size > MULTIPLE_DISPLAY
max_width = get_max_widths(output_texts)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

05.wc/wc.rb Outdated
file_total_info
end

def print_output(output_text, max_width)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

「printする」と「出力する(output)」はほぼ同じ意味なので、もう少し具体性のあるメソッド名を付けた方がいいかも

05.wc/wc.rb Outdated
end

def get_max_widths(output_text)
max_lines_width = output_text.map { |entry| entry[:line].to_s.length + DDISPLAY_ADJUST_LENGTH }.max

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+ DDISPLAY_ADJUST_LENGTH の処理は名前からして表示のタイミングで行う処理では?と思いました。
get_max_widthsというメソッド名なら純粋に最大の文字列長を取得する方が驚きが少ないと思います。

参考 https://blog.jnito.com/entry/2023/07/12/115733

05.wc/wc.rb Outdated
opt.parse!(argv)
end

unless options.key?(:l) || options.key?(:w) || options.key?(:c)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
unless options.key?(:l) || options.key?(:w) || options.key?(:c)
if options.empty?

でも同じかな?

05.wc/wc.rb Outdated
end

def file_total_info(output_texts)
file_total_info = output_texts.inject({}) do |result, hash|

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

メソッド名と同じ変数名は避けましょう
https://qiita.com/jnchito/items/fb438aa0786bb3cbf919

あと、hashという名前も抽象的過ぎるのでもっと具体性のある名前にした方がいいと思います
https://bootcamp.fjord.jp/articles/78

05.wc/wc.rb Outdated
Comment on lines +15 to +16
file_name = ARGF.filename
wc_info[:filename] = file_name

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
file_name = ARGF.filename
wc_info[:filename] = file_name
wc_info[:filename] = ARGF.filename

変数に入れなくていいかも

05.wc/wc.rb Outdated
line = output_line[:line]&.to_s&.rjust(max_width[:line_display_width])
word = output_line[:word]&.to_s&.rjust(max_width[:word_display_width])
bytesize = output_line[:bytesize]&.to_s&.rjust(max_width[:bytesize_display_width])
filename = output_line[:filename] == '-' ? '' : output_line[:filename]
Copy link

@JunichiIto JunichiIto Jul 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほどー。
その情報が第三者にはぱっとわからないので(=マジックナンバーならぬマジックストリング)、情報を付加した方がいいですね。

そして今回の提出物ではこの処理がなくなってますね。これはなぜ? 🤔

@JunichiIto
Copy link

コメントしました!

@kutimiti1234
Copy link
Owner Author

全体的に
https://bootcamp.fjord.jp/pages/avoid-too-specific-var-name
を参考に、wc_修飾子を省略しました。ロジックを見ればそれぞれの変数がwcに対する変数であることがわかると考えたためです。
(関数名についてのみ普遍的な操作ではなく、特殊な操作であることを強調したいと考えたためwc_修飾子を残しました。)

05.wc/wc.rb Outdated
Comment on lines +12 to +20
counts = []
ARGF.each(nil) do |standard_input_or_file|
count = {}

count[:filename] = ARGF.filename
count[:line] = standard_input_or_file.lines.count if options[:l]
count[:word] = standard_input_or_file.split(/\s+/).size if options[:w]
count[:bytesize] = standard_input_or_file.size if options[:c]
counts << count
Copy link
Owner Author

@kutimiti1234 kutimiti1234 Jul 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここではwcコマンドの実際に行っている処理として"count"が一番適当であると考えて、配列をcountsにして、それぞれの集計値をcountとして格納しました。
また"ARGF.each(nil) do |input_text|"のinput_textが抽象的すぎたため、具体的に入力として考えられる2つの要素「標準入力」「ファイル」を表す名前にしました。

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

うーん、standard_input_or_file は逆にちょっと具体的過ぎますね。
input_textの方がまだほどよい抽象度だったように思います。
https://bootcamp.fjord.jp/articles/78

05.wc/wc.rb Outdated
Comment on lines +22 to +25
rows = counts
# 複数行出力する場合は、集計行を表示する
rows << get_wc_total_row(counts) if counts.size > 1
max_column_widths = get_max_column_widths(rows)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

countsに集計行を入れてしまうと、countsの実態から外れてしまうため、rowsに集約する形にしました

05.wc/wc.rb Outdated
Comment on lines +29 to +50
def get_wc_total_row(counts)
total_row = counts.inject({}) do |result, column_name|
result.merge(column_name) { |_key, current_val, adding_value| current_val + adding_value }
end
total_row[:filename] = TOTAL
total_row
end

def show_wc_rows(rows, max_column_widths)
rows.each do |row|
cells = {}
row.each_pair do |column_name, cell|
cells[column_name] = if column_name == :filename
# 標準入力を受け取る際に例外的に"-"が入力されるため除外する
cell unless cell == STANDARD_INPUT_EXCEPTIONAL_WORD
else
cell.to_s.rjust(max_column_widths[column_name] + SHOW_ADJUST_SPACE_SIZE)
end
end
puts "#{cells[:line]}#{cells[:word]}#{cells[:bytesize]} #{cells[:filename]}"
end
end
Copy link
Owner Author

@kutimiti1234 kutimiti1234 Jul 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

最終出力を行う関数について、イメージとしてexcelの表を考えていたため、変数名にrow.column.cellを採用しました。

rowのhashにアクセスする際のkeyとしては、"column_name"で統一しました。count_nameと悩みましたが、filenameの列も存在するため、より包括的と考えられるcolumnを採用しました。

Copy link

@JunichiIto JunichiIto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コメントしました!

05.wc/wc.rb Outdated
Comment on lines +12 to +20
counts = []
ARGF.each(nil) do |standard_input_or_file|
count = {}

count[:filename] = ARGF.filename
count[:line] = standard_input_or_file.lines.count if options[:l]
count[:word] = standard_input_or_file.split(/\s+/).size if options[:w]
count[:bytesize] = standard_input_or_file.size if options[:c]
counts << count

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

うーん、standard_input_or_file は逆にちょっと具体的過ぎますね。
input_textの方がまだほどよい抽象度だったように思います。
https://bootcamp.fjord.jp/articles/78

05.wc/wc.rb Outdated
count[:bytesize] = standard_input_or_file.size if options[:c]
counts << count
end
rows = counts

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ある変数を別の変数に入れ替える、ということは滅多にやらないですね。
ここはcountsを使い続けるという形でも良いと思ったのですが、そうしなかった理由はありますか?

Copy link
Owner Author

@kutimiti1234 kutimiti1234 Jul 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こちらについてですが、

  rows << get_wc_total_row(counts) if counts.size > 1

の左辺と右辺で実態が異なるように考えたためrowsに書き換えました。

05.wc/wc.rb Outdated
counts << count
end
rows = counts
# 複数行出力する場合は、集計行を表示する

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このコメントはWHYを説明してないので不要ですね。(コードを見ればわかる内容になっている)
https://qiita.com/jnchito/items/f0d90af4ed44b7484103

05.wc/wc.rb Outdated

def get_wc_total_row(counts)
total_row = counts.inject({}) do |result, column_name|
result.merge(column_name) { |_key, current_val, adding_value| current_val + adding_value }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filenameも文字列連結されていくのはちょっとメモリの無駄遣いな気がするのでできたら避けたいですね。
(昨今のマシンスペックならメモリの消費もほとんど問題にならないとはいえ)

05.wc/wc.rb Outdated
def show_wc_rows(rows, max_column_widths)
rows.each do |row|
cells = {}
row.each_pair do |column_name, cell|

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
row.each_pair do |column_name, cell|
row.each do |column_name, cell|

each_pair を使う人って僕の周りでは見たことがないので、eachの方がいいと思います。
(僕もeach_pairってなんだっけ?と思って調べてしまった)

05.wc/wc.rb Outdated
row.each_pair do |column_name, cell|
cells[column_name] = if column_name == :filename
# 標準入力を受け取る際に例外的に"-"が入力されるため除外する
cell unless cell == STANDARD_INPUT_EXCEPTIONAL_WORD

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cell unless cell == STANDARD_INPUT_EXCEPTIONAL_WORD
cell unless cell == STDIN_FILENAME

標準入力は除外するんだなーというのが伝われば十分かも

05.wc/wc.rb Outdated
# 標準入力を受け取る際に例外的に"-"が入力されるため除外する
cell unless cell == STANDARD_INPUT_EXCEPTIONAL_WORD
else
cell.to_s.rjust(max_column_widths[column_name] + SHOW_ADJUST_SPACE_SIZE)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cell.to_s.rjust(max_column_widths[column_name] + SHOW_ADJUST_SPACE_SIZE)
hoge = max_column_widths[column_name] + SHOW_ADJUST_SPACE_SIZE
cell.to_s.rjust(hoge)

変数に入れた方が読みやすそうです

05.wc/wc.rb Outdated
end
end

def get_max_column_widths(wc_rows)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

戻り値を返すメソッドは何でも get って名付けられますし、Rubyの場合は get_ や set_ で始まるメソッド名を避ける慣習があるのでget以外の動詞を付けたいですね

05.wc/wc.rb Outdated
end

def get_max_column_widths(wc_rows)
max_lines_width = wc_rows.map { |entry| entry[:line].to_s.length }.max

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
max_lines_width = wc_rows.map { |entry| entry[:line].to_s.length }.max
max_lines_width = wc_rows.map { |entry| entry[:line].to_s.length }.max

RuboCopに怒られませんでした?

Copy link
Owner Author

@kutimiti1234 kutimiti1234 Jul 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こちらに関してですが、修正前のコードでrubocopを実行しても特にエラーは出ませんでした。
見た目として揃っていないので、次回の修正に含めたいと思います。

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

うーん、おかしいですね。僕が試したらエラーになりました。

wc.rb:55:60: C: [Correctable] Layout/ExtraSpacing: Unnecessary spacing detected.
    max = rows.map { |entry| entry[column_name].to_s.length  }.max
                                                           ^

わざとスペースを追加したコードと、それをrubocopにかけたときの結果をスクショで見せてもらえますか?

あと、以下のコマンドの実行結果も見たいです。

rubocop --show-cops Layout/ExtraSpacing

以下のようにEnabled: trueになっていればこのルールは有効化されているはずです。

$ rubocop --show-cops Layout/ExtraSpacing
# Supports --autocorrect
Layout/ExtraSpacing:
  Description: Do not use unnecessary spacing.
  Enabled: true
  VersionAdded: '0.49'
  AllowForAlignment: true
  AllowBeforeTrailingComments: false
  ForceEqualSignAlignment: false

05.wc/wc.rb Outdated

require 'optparse'

SHOW_ADJUST_SPACE_SIZE = 1

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SHOW_ADJUST_SPACE_SIZE = 1
SEPARATOR_SIZE = 1

これぐらいの名前でも十分かも?

05.wc/wc.rb Outdated
count[:bytesize] = input_text.size if options[:c]
counts << count
end
counts << calculate_counts_total(counts) if counts.size > 1

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
counts << calculate_counts_total(counts) if counts.size > 1
counts << calculate_total_counts(counts) if counts.size > 1

こっちの方が英語として自然かも

05.wc/wc.rb Outdated
counts << count
end
counts << calculate_counts_total(counts) if counts.size > 1
max_column_widths = calculate_wc_max_column_widths(counts)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
max_column_widths = calculate_wc_max_column_widths(counts)
max_widths = calculate_max_widths(counts)

修飾語は省略できそうです

https://bootcamp.fjord.jp/pages/avoid-too-specific-var-name

05.wc/wc.rb Outdated

def main
options = parse_options(ARGV)
counts = []

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

左辺と右辺で実態が異なるように考えたためrowsに書き換えました

ここですが、テーブル構造(行と列で構成されるデータ)は例外的に左辺と右辺が異なってもいいと思います。
また、14行目のcountはcountsにした方がハッシュである感が出ると思います。
(countだと1や5みたいな数値が入ってそうに見える)

なので、最初からここはrowsにして、

rows << counts

みたいな形で書くのはOKです。

05.wc/wc.rb Outdated
end
counts << calculate_counts_total(counts) if counts.size > 1
max_column_widths = calculate_wc_max_column_widths(counts)
show_wc_rows(counts, max_column_widths)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
show_wc_rows(counts, max_column_widths)
show_rows(counts, max_column_widths)

ここもwcは省略できそう

05.wc/wc.rb Outdated
Comment on lines +28 to +34
counts_without_filename = counts.map { |count| count.except(:filename) }

total = counts_without_filename.inject({}) do |result, count|
result.merge(count) do |_key, current_val, adding_value|
current_val + adding_value
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
counts_without_filename = counts.map { |count| count.except(:filename) }
total = counts_without_filename.inject({}) do |result, count|
result.merge(count) do |_key, current_val, adding_value|
current_val + adding_value
end
end
total = counts.inject({}) do |result, count|
result.merge(count) do |key, current_val, adding_value|
current_val + adding_value unless key == :filename
end
end

こうした方が短くなります。

column_width = max_column_widths[column_name] + SEPARATOR_SIZE
cell.to_s.rjust(column_width)
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

参考: eachの代わりにto_hメソッドを使う方法もあります。

cells = row.to_h do |column_name, cell|
  value = if column_name == :filename
          # ...
  [column_name, value]
end

05.wc/wc.rb Outdated
cell.to_s.rjust(column_width)
end
end
puts "#{cells[:line]}#{cells[:word]}#{cells[:bytesize]} #{cells[:filename]}"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

44行目を

" #{cell}" unless cell == STDIN_FILENAME

にすると、ここはまとめてjoinできます。

Suggested change
puts "#{cells[:line]}#{cells[:word]}#{cells[:bytesize]} #{cells[:filename]}"
puts cells.values_at(:line, :word, :bytesize, :filename).join

05.wc/wc.rb Outdated
count[:filename] = ARGF.filename
count[:line] = input_text.lines.count if options[:l]
count[:word] = input_text.split(/\s+/).size if options[:w]
count[:bytesize] = input_text.size if options[:c]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
count[:bytesize] = input_text.size if options[:c]
count[:byte] = input_text.size if options[:c]

全部4文字にすると見た目にスッキリするかも(些細な改善ですが)

05.wc/wc.rb Outdated
def calculate_wc_max_column_widths(rows)
max_lines_width = rows.map { |entry| entry[:line].to_s.length }.max
max_words_width = rows.map { |entry| entry[:word].to_s.length }.max
max_bytesizes_width = rows.map { |entry| entry[:bytesize].to_s.length }.max

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

55-57行目はentryに渡している添え字が違うだけなのでDRYにできそうです

05.wc/wc.rb Outdated
end

def get_max_column_widths(wc_rows)
max_lines_width = wc_rows.map { |entry| entry[:line].to_s.length }.max

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

うーん、おかしいですね。僕が試したらエラーになりました。

wc.rb:55:60: C: [Correctable] Layout/ExtraSpacing: Unnecessary spacing detected.
    max = rows.map { |entry| entry[column_name].to_s.length  }.max
                                                           ^

わざとスペースを追加したコードと、それをrubocopにかけたときの結果をスクショで見せてもらえますか?

あと、以下のコマンドの実行結果も見たいです。

rubocop --show-cops Layout/ExtraSpacing

以下のようにEnabled: trueになっていればこのルールは有効化されているはずです。

$ rubocop --show-cops Layout/ExtraSpacing
# Supports --autocorrect
Layout/ExtraSpacing:
  Description: Do not use unnecessary spacing.
  Enabled: true
  VersionAdded: '0.49'
  AllowForAlignment: true
  AllowBeforeTrailingComments: false
  ForceEqualSignAlignment: false

@kutimiti1234
Copy link
Owner Author

すみません。
こちら試したところきちんと作動しておりました。
image

提出前にRubocopを使用したつもりだったのですが、何か勘違いしていたようです。
お手数をおかけしてしまい申し訳ありません。

@kutimiti1234
Copy link
Owner Author

    puts cells.values_at(:line, :word, :bytesize, :filename).join

で実装してしまうと、以下の通りとなってしまうので、
image

cell.to_s.rjust(column_width) + ' ' * SEPARATOR_SIZE

の形に修正しました。(下の画像は修正後)
image

05.wc/wc.rb Outdated
Comment on lines +54 to +57
keys = rows.map(&:keys).flatten.uniq
keys.each do |key|
max_widths[key] = rows.map { |entry| entry[key].to_s.length }.max unless key == :filename
end
Copy link
Owner Author

@kutimiti1234 kutimiti1234 Jul 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

 keys = rows.map(&:keys).flatten.uniq

について、もっと驚きの少ない実装がないか以下の二通りの方法を考えましたが、要件を満たさなさそうだったため上記を採用しました。

  1. rows.eachを使う方法
    • rowのキーを用いることができても、余分なループが生じてしまうこと
  2. rows[0]などで最初のハッシュを取得してキーを取得する
    • 突然最初のハッシュを取得して唐突に思えること。
    • 実質的に上記の案と変わらない。
  keys = rows.map(&:keys).flatten.uniq

についてkeysをnameとするか迷いましたが、左辺と右辺の一致を考えるとmap(&:keys)が右辺にあるのであれば、左辺にnameが来るのは不自然であると思いkeysを採用しました。

max_widths[key] = rows.map { |entry| entry[key].to_s.length }.max unless key == :filename

上記の左辺と右辺は、右側に{....}.maxが存在するためmax_widthで問題ないと判断しました。

Copy link

@JunichiIto JunichiIto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コメントしました〜。

05.wc/wc.rb Outdated
columns[name] = if name == :filename
value unless value == STDIN_FILENAME
else
column_width = max_column_widths[name]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
column_width = max_column_widths[name]
width = max_widths[name]

ここも修飾語はなくしましょう

05.wc/wc.rb Outdated
value.to_s.rjust(column_width) + ' ' * SEPARATOR_SIZE
end
end
puts columns.values_at(:line, :word, :byte, :filename).join

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

で実装してしまうと、以下の通りとなってしまうので

あ、すいません、前回のコード例がちょっと間違ってました。
意図してたコードはこうです。

Suggested change
puts columns.values_at(:line, :word, :byte, :filename).join
puts columns.values_at(:line, :word, :byte, :filename).join(' ')

SEPARATOR_SIZE はなくしちゃってもいいかもしれませんね

05.wc/wc.rb Outdated
Comment on lines +54 to +55
keys = rows.map(&:keys).flatten.uniq
keys.each do |key|

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

もっと驚きの少ない実装がないか以下の二通りの方法を考えましたが

ここはもっとシンプルに考えていいと思います。

Suggested change
keys = rows.map(&:keys).flatten.uniq
keys.each do |key|
%i[line word byte].each do |key|

こんな感じで十分かなと思ったのですが、どうでしょうか?

あと、eachの代わりにto_hを使うとこのメソッドの実装をよりシンプルにできそうです
https://docs.ruby-lang.org/ja/latest/method/Array/i/to_h.html

05.wc/wc.rb Outdated
Comment on lines +70 to +75
if options.empty?
options[:l] = true
options[:w] = true
options[:c] = true
end
options

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if options.empty?
options[:l] = true
options[:w] = true
options[:c] = true
end
options
options.empty? ? { l: true, w: true, c: true } : options

こんな感じにするとよりスッキリするかも

05.wc/wc.rb Outdated
end

def calculate_total_rows(rows)
total = rows.inject({}) do |result, count|

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
total = rows.inject({}) do |result, count|
total = rows.inject({}) do |result, counts|

14行目の命名に合わせるならcountsが良さそうです

05.wc/wc.rb Outdated
end

def show_rows(rows, max_column_widths)
rows.each do |row|

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rows.each do |row|
rows.each do |counts|

rowsの中身はcountsと呼ぶ、と一貫性を持たせると良いかもと思いました

Copy link

@JunichiIto JunichiIto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コメントしました!

Comment on lines +11 to +20
rows = []
ARGF.each(nil) do |input_text|
counts = {}

counts[:filename] = ARGF.filename
counts[:line] = input_text.lines.count if options[:l]
counts[:word] = input_text.split(/\s+/).size if options[:w]
counts[:byte] = input_text.size if options[:c]
rows << counts
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rows = []
ARGF.each(nil) do |input_text|
counts = {}
counts[:filename] = ARGF.filename
counts[:line] = input_text.lines.count if options[:l]
counts[:word] = input_text.split(/\s+/).size if options[:w]
counts[:byte] = input_text.size if options[:c]
rows << counts
end
rows = build_rows(options)

rowsを作る処理もメソッド化するとmainメソッドの見通しが良くなりそうです。

05.wc/wc.rb Outdated
columns = {}
counts.each do |name, value|
columns[name] = if name == :filename
value unless value == STDIN_FILENAME

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

正規表現で置き換えるのも一手です。

STDIN_PATTERN = /^-$/
value.sub(STDIN_PATTERN, '')

正規表現の見方については https://qiita.com/jnchito/items/893c887fbf19e17d3ff9 のその1とその3をチェックしてみてください

05.wc/wc.rb Outdated
Comment on lines +38 to +46
columns = {}
counts.each do |name, value|
columns[name] = if name == :filename
value unless value == STDIN_FILENAME
else
width = max_widths[name]
value.to_s.rjust(width)
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

参考:ややトリッキーですが、to_hを使ってかなり行数を減らすこともできます。

Suggested change
columns = {}
counts.each do |name, value|
columns[name] = if name == :filename
value unless value == STDIN_FILENAME
else
width = max_widths[name]
value.to_s.rjust(width)
end
end
columns = counts.to_h do |name, value|
text = name == :filename ? value.sub(STDIN_PATTERN, '') : value.to_s.rjust(max_widths[name])
[name, text]
end

05.wc/wc.rb Outdated
Comment on lines +22 to +23
max_widths = calculate_max_widths(rows)
show_rows(rows, max_widths)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
max_widths = calculate_max_widths(rows)
show_rows(rows, max_widths)
show_rows(rows)

rowsだけ渡してもらえればあとはいい感じに出力しておきまっせ、という設計の方がメソッドの再利用性が高そうです

Copy link

@JunichiIto JunichiIto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1点コメントしましたが大きな問題ではないのでこれでOKです!🙆‍♂️

Comment on lines +16 to +25
rows = []
ARGF.each(nil) do |input_text|
counts = {}

counts[:filename] = ARGF.filename
counts[:line] = input_text.lines.count if options[:l]
counts[:word] = input_text.split(/\s+/).size if options[:w]
counts[:byte] = input_text.size if options[:c]
rows << counts
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rows = []
ARGF.each(nil) do |input_text|
counts = {}
counts[:filename] = ARGF.filename
counts[:line] = input_text.lines.count if options[:l]
counts[:word] = input_text.split(/\s+/).size if options[:w]
counts[:byte] = input_text.size if options[:c]
rows << counts
end
rows = ARGF.each(nil).map do |input_text|
counts = { filename: ARGF.filename }
counts[:line] = input_text.lines.count if options[:l]
counts[:word] = input_text.split(/\s+/).size if options[:w]
counts[:byte] = input_text.size if options[:c]
counts
end

空の配列を用意してループで詰め込むだけの処理はmapで置き換えられます。(チェリー本参照)

あとfilenameは最初から入れてしまうのもアリです

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants