質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

4回答

3971閲覧

Python3 英単語採点 ループ処理 lambda式

python3_beginer

総合スコア46

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

0クリップ

投稿2018/04/12 03:56

編集2018/04/12 06:57

目標)
英単語採点アプリをつくりたい

採点方法

・正しい単語と完全一致→  2 点
・正しい単語と長さ(文字数)が異なる→ × 0 点
・正しい単語と長さは同じだが 1 文字だけ異なる→ △ 1 点
・正しい単語と長さは同じだが 2 文字以上異なる→ × 0 点

例)
正解: desk, 解答: desc → 1 点 (長さは同じだが 1 文字だけ異なる)
正解: phone, 解答: pheen → 0 点 (長さは同じだが 2 文字異なる)
正解: cute, 解答: cute → 2 点 (完全一致)

合計得点 → 3 点

なお、N回分英単語が入力される
正解の英単語と生徒の回答が半角スペースで与えられる

入力例1

2
desk deee
cute cuta

出力例1
1

入力例2
4
pen pen
note nota
head hhhh
arm aaa

出力例2
3

質問)

以下のループ処理のコードをlmabda式に書きなおしたいが、細かい条件分岐を一文とまとめられない。

お力添えお願い致します。

また、このような処理を関数やクラス等を利用してよりスマートなコードがございましたら、教えて頂きたいです。

コード N = int(input()) data = list(input().split() for i in range(N)) print(*data) score = 0 for x in data: if x[0] == x[-1]: score += 2 elif (len(x[0]) == len(x[-1])): #一文字以上異なる場合を処理、 しかし文字の位置が考慮されていないから不十分 match = [i for i in set(x[0]) if i in x[-1]] if len(match) == len(x[0])-1: score += 1 else: pass else: pass print(score)
コード N = int(input()) data = list(input().split() for i in range(N)) #完全一致 data_perfect = list(map(lambda x: x[0] == x[-1],data)) #長さのみ同じのリスト作成 data_same_length = list(map(lambda x: len(x[0]) == len(x[-1]),data)) #長さのみ同じリスト から 一文字のみ違うリストをつくりたい #この処理が上手く機能しない data_nearly = list(map(lambda x: for i in set(x[0]) if i in x[-1],data_same_length)) result = sum(map(int, [x and y for x,y in zip(data_perfect, data_nearly)])) print(result)

※追記質問

一文字だけ異なる場合のときに、私のループ処理では文字の位置が考慮されていない。

match = [i for i in set(x[0]) if i in x[-1]]

集合の値のみを見ているため、文字の位置が関係していない。

記入例)

note one!

この場合だと、ルール上は 文字の位置まで考慮するため、文字の長さが同じだが、スペルミスが2つ以上のため 0点

しかし、私のコードだと、文字の位置を考慮していないため、文字の長さが同じ、かつスペルミスが1つのため、1点

コメント頂いた方のコードを参照すると以下のようになる。

コード def mark(right_answer, answer): if len(right_answer) != len(answer): return 0 #文字の位置を考慮しがら、スペルミスを処理 diff = sum( 1 for ra, a in zip(right_answer, answer) if ra != a ) if diff == 0: return 2 if diff == 1: return 1 return 0 n = int(input()) total_score = sum( mark(*input().split()) for _ in range(n) ) print(total_score)

なぜ以下のコードが文字の位置を考慮しがら、スペルミスを処理できる理由が分かるようでわかりません。

先頭から文字の一致を確認し、スペルミスがあればその数を合計する処理でしょうか?

コード #文字の位置を考慮しがら、スペルミスを処理 diff = sum( 1 for ra, a in zip(right_answer, answer) if ra != a ) if diff == 0: return 2 if diff == 1: return 1 return 0

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

fuzzball

2018/04/12 04:18 編集

(失礼、勘違いでした)
fuzzball

2018/04/12 04:56 編集

やっぱり勘違いじゃなかった。「一文字だけ異なる」の判定で、位置が考慮されていません。note に対して one! でも+1になってしまいます。(質問とは関係ないですが‥)
python3_beginer

2018/04/12 06:57

ご指摘とおりですね。気づきませんでした。ありがとうございます。
guest

回答4

0

とりあえず殴り書き。

python

1score = sum([max(0, 2 - len(x[0]) + len(list(filter(lambda c: c[0] == c[1], zip(x[0], x[1]))))) for x in filter(lambda word: len(word[0]) == len(word[1]), data)])

ここからが本番w

投稿2018/04/12 08:28

編集2018/04/12 08:50
fuzzball

総合スコア16731

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

python3_beginer

2018/04/12 12:33

お返事ありがとうございます。 こちらのコード参考にさせて頂きます。 質問に対するご意見のおかげでまた一つ知識を深めることができました。 ありがとうございました。
guest

0

ベストアンサー

lambda式は複雑な処理にはあまり適しませんから、この場合は普通の関数で書いた方が良いでしょう。

まずは愚直に書いてみるのも一つの手です。

python

1def main(): 2 n = int(input()) 3 4 lst = [] 5 for i in range(n): 6 lst.append(input().split()) 7 8 score = 0 9 for true_ans, exam_ans in lst: 10 if len(true_ans) != len(exam_ans): 11 score += 0 12 else: 13 diff_count = 0 14 for c1, c2 in zip(true_ans, exam_ans): 15 if c1 != c2: 16 diff_count += 1 17 if 0 == diff_count: 18 score += 2 19 elif 1 == diff_count: 20 score += 1 21 else: 22 score += 0 23 24 print(score) 25 26if __name__ == "__main__": 27 main()

上を踏まえた上で、採点処理は関数化して切り出してみます。

python

1def count_diff(true_ans, exam_ans): 2 diff_count = 0 3 for c1, c2 in zip(true_ans, exam_ans): 4 if c1 != c2: 5 diff_count += 1 6 return diff_count 7 8def saiten(true_ans, exam_ans): 9 if len(true_ans) != len(exam_ans): 10 return 0 11 else: 12 n = count_diff(true_ans, exam_ans) 13 if 0 == n: 14 return 2 15 elif 1 >= n: 16 return 1 17 else: 18 return 0 19 20def main(): 21 n = int(input()) 22 23 lst = [] 24 for i in range(n): 25 lst.append(input().split()) 26 27 score = 0 28 for true_ans, exam_ans in lst: 29 score += saiten(true_ans, exam_ans) 30 31 print(score) 32 33if __name__ == "__main__": 34 main()

すっきりしたといえばすっきりしましたが、コード行数は大して減ってはいないという見方もできるでしょう。だけれど、これ以上複雑なことをやるとかえって見通しが悪くなるので、これくらいで良いのです。

余談ですが、本当は私が先に思いついたのは下の例の方です。「まずは1問だけ採点する関数を作ろう。そうすれば、あとはできたも同然だ。そのために、まずは異なり文字数を数える関数を作るか」というような発想で書きました。参考にしてください。

追記

「lambda式は複雑な処理にはあまり適しません」と書いてしまいましたが、あえてlambdaバージョンのsaitenを作ってみた例。

python

1saiten = lambda true_ans, exam_ans:( 2 0 3 if len(true_ans) != len(exam_ans) else 4 (lambda count_diff:( 5 [2, 1][count_diff] if count_diff <= 1 else 0))( 6 sum([1 for c1, c2 in zip(true_ans, exam_ans) if c1 != c2])))

ご覧の通り可読性は悪いのですが、できなくはありません。

質問の追記に関して

恐らくzipや内包表記が入り乱れているのでわかりづらいのだと思います。
ナイーブに書くとこうなります。

python

1def count_diff(true_ans, exam_ans): 2 """同じ文字列長を仮定 3 """ 4 diff_count = 0 5 for i in range(len(true_ans)): 6 if true_ans[i] != exam_ans[i]: 7 diff_count += 1 8 return diff_count

これはたぶん追っていけばわかるはずです。
ついでにzipの出力も確認しておくことにしましょう。

python

1>>> list(zip("head", "hhhh")) 2[('h', 'h'), ('e', 'h'), ('a', 'h'), ('d', 'h')]

あとはリスト内包表記にするだけで、これは簡単です。

python

1sum([1 for c1, c2 in zip(right_answer, answer) if c1 != c2])

ただしLouiS0616さんのこのコードは、

python

1diff = sum( 2 1 for ra, a in zip(right_answer, answer) if ra != a 3 )
  1. リスト内包表記ではなくジェネレータ式を使っている
  2. ジェネレータ式を関数の引数として書く場合、かっこを省略できるという糖衣構文を使っている

ので一見すると理解しがたいものに見えます。

1については、ジェネレータ式はジェネレータを作るものです。ジェネレータとはなにかというと、だいたい次のようなポジションだと思ってください。

  • イテレータ:forで繰り返し処理できるもの。文字列、リスト、タプル、"ジェネレータ"など
  • ジェネレータ:動的に要素が生成されるイテレータ

2については、本来ジェネレータ式は()で囲うことで作ることができるのですが、関数の唯一の引数として渡された場合はこの()を省略できるというルールがあります。それを使われています。

参考:
ジェネレータ式の文法について調べてみた

投稿2018/04/12 04:44

編集2018/04/12 07:49
hayataka2049

総合スコア30933

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

LouiS0616

2018/04/12 05:03

lambda count_diff: max(0, 2-count_diff) の方がよりシンプルな気がします。
hayataka2049

2018/04/12 05:17

なんだかReLUみたいです。
python3_beginer

2018/04/12 12:30

お返事ありがとうございます。 追記の質問も含めて、わかりやすい説明でとても勉強になりました。 >>> list(zip("head", "hhhh")) [('h', 'h'), ('e', 'h'), ('a', 'h'), ('d', 'h')] こちらの理解が乏しかったと思います。 以前は難しい~とあきらめていましたが、少しずづ奥が深くて面白い~と思えるようになりました。 丁寧な説明のおかげです。 ご指導ありがとうございます。
guest

0

他の回答者様とロジックは同様です。

Python

1INT_MAX = __import__('sys').maxsize 2 3 4def mark(ans_pair): 5 ''' 6 ・正しい単語と文字数が異なる 0点 7 8 ・正しい単語と文字数は同じだが2文字以上異なる 0点 9 ・正しい単語と文字数は同じだが1文字だけ異なる 1点 10 ・正しい単語と文字数は同じだが0文字だけ異なる 2点 11 ''' 12 ans_r, ans_t = ans_pair 13 wrong_count = ( 14 INT_MAX if len(ans_r) != len(ans_t) 15 else sum(r != t for r, t in zip(ans_r, ans_t)) 16 ) 17 return 0 if 2 <= wrong_count else [2, 1][wrong_count] 18 19 20test_paper = [ 21 input().split() 22 for _ in range(int(input())) 23] 24total = sum(map(mark, test_paper))

投稿2018/04/12 08:45

arch_

総合スコア158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

python3_beginer

2018/04/12 12:33

お返事ありがとうございます。 関数を使ったコードありがとうございます。参考します。 このようなコードを早く自力で書けるようになりたいです。
guest

0

ラムダ式ではないですけど、部分的に関数にするという発想は良いと思います。

Python

1def mark(right_answer, answer): 2 if len(right_answer) != len(answer): 3 return 0 4 5 diff = sum( 6 1 for ra, a in zip(right_answer, answer) if ra != a 7 ) 8 if diff == 0: 9 return 2 10 if diff == 1: 11 return 1 12 13 return 0 14 15 16n = int(input()) 17total_score = sum( 18 mark(*input().split()) for _ in range(n) 19) 20 21print(total_score)

Wandbox


以下のループ処理のコードをlmabda式に書きなおしたいが、細かい条件分岐を一文とまとめられない。

条件式を使えば分岐も可能ですが、かなり読みづらくなります。
ラムダ式で書きづらいときは通常の関数を定義した方が良いです。

この処理が上手く機能しない

data_nearly = list(map(lambda x: for i in set(x[0]) if i in x[-1],data_same_length))

ラムダ式内でfor文は使えないです。
ループ処理を書くためには、内包表記を使うか、再帰を使った黒テクニックが必要です。

追記を受けて

なぜ以下のコードが文字の位置を考慮しがら、スペルミスを処理できる理由が分かるようでわかりません。

次のように書いているのと似ています。並行して一文字ずつ取り出し、比較しているのです。

Python

1diff = 0 2for ra, a in zip(right_answer, answer): 3 if ra != a: 4 diff += 1

似ているという表現を使ったのは、内包表記ありきのテクニックを使っているからです。
次の処理を追ってみます。

Python

1>>> [(ra, a) for ra, a in zip('hoge', 'huge') if ra != a] 2[('o', 'u')]

if ra != aを満たすときだけ、リストに要素が追加されることがわかります。

また、sum(1... )という記法は、シーケンス長を算出するときにしばしば使います。

Python

1>>> sum(1 for _ in range(3)) 23 3>>> sum(1 for _ in range(1, 4)) 43 5>>> sum(1 for _ in 'hoge') 64

毎周『1』を生成して、それを累計しているわけです。


上記二つのテクニックを組み合わせると、先の処理は次のように説明できます。

Python

1diff = sum( 2 1 for ra, a in zip(right_answer, answer) if ra != a 3)
  1. 二つの文字列を並列に比較する。

・もし文字が同じでないときは、リストに1を追加する。
・そうでないときは、リストに何も追加しない。
0. リストの合計を算出する。この場合要素が全部1なので、リスト長を求めているのと同じ。

実際にはジェネレータを利用しているのでもう少し効率的ですが、まだ気にしなくて良いでしょう。

おまけ

ゲームの特性によっては、クロージャを使うと便利です。

Python

1def make_mark(right_answer): 2 right_len = len(right_answer) 3 4 def mark(answer): 5 if len(answer) != right_len: 6 return 0 7 8 diff = sum( 9 1 for ra, a in zip(right_answer, answer) if ra != a 10 ) 11 if diff == 0: 12 return 2 13 if diff == 1: 14 return 1 15 16 return 0 17 18 return mark 19 20 21n = int(input()) 22mark = make_mark(input()) 23 24for _ in range(n): 25 answer = input() 26 print(mark(answer))

Wandbox

投稿2018/04/12 04:08

編集2018/04/12 07:24
LouiS0616

総合スコア35658

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

python3_beginer

2018/04/12 12:23

お返事ありがとうございます。 追記の質問も加えて、わかりやすい説明でとても勉強になりました。 >>> [(ra, a) for ra, a in zip('hoge', 'huge') if ra != a] [('o', 'u')] こちらの説明のように、具体的に何をどう処理しているのか考えながら進めてみます。 クロージャに関しては、現状では難しいですが、復習しながら身に着けていきたいです。 ご指導ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問