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

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

ただいまの
回答率

90.62%

  • Python 3.x

    5864questions

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

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

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 291

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

採点方法

・正しい単語と完全一致→  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
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • fuzzball

    2018/04/12 13:18 編集

    (失礼、勘違いでした)

    キャンセル

  • fuzzball

    2018/04/12 13:55 編集

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

    キャンセル

  • python3_beginer

    2018/04/12 15:57

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

    キャンセル

回答 4

checkベストアンサー

+2

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

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

def main():
    n = int(input())

    lst = []
    for i in range(n):
        lst.append(input().split())

    score = 0
    for true_ans, exam_ans in lst:
        if len(true_ans) != len(exam_ans):
            score += 0
        else:
            diff_count = 0
            for c1, c2 in zip(true_ans, exam_ans):
                if c1 != c2:
                    diff_count += 1
            if 0 == diff_count:
                score += 2
            elif 1 == diff_count:
                score +=  1
            else:
                score +=  0

    print(score)

if __name__ == "__main__":
    main()

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

def count_diff(true_ans, exam_ans):
    diff_count = 0
    for c1, c2 in zip(true_ans, exam_ans):
        if c1 != c2:
            diff_count += 1
    return diff_count

def saiten(true_ans, exam_ans):
    if len(true_ans) != len(exam_ans):
        return 0
    else:
        n = count_diff(true_ans, exam_ans)
        if 0 == n:
            return 2
        elif 1 >= n:
            return 1
        else:
            return 0

def main():
    n = int(input())

    lst = []
    for i in range(n):
        lst.append(input().split())

    score = 0
    for true_ans, exam_ans in lst:
        score += saiten(true_ans, exam_ans)

    print(score)

if __name__ == "__main__":
    main()

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

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

 追記

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

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


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

 質問の追記に関して

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

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

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

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

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

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

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

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

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

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

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

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/04/12 14:03

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

    キャンセル

  • 2018/04/12 14:17

    なんだかReLUみたいです。

    キャンセル

  • 2018/04/12 21:30

    お返事ありがとうございます。

    追記の質問も含めて、わかりやすい説明でとても勉強になりました。

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

    こちらの理解が乏しかったと思います。

    以前は難しい~とあきらめていましたが、少しずづ奥が深くて面白い~と思えるようになりました。
    丁寧な説明のおかげです。
    ご指導ありがとうございます。

    キャンセル

+2

とりあえず殴り書き。

score = 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 21:33

    お返事ありがとうございます。

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

    キャンセル

+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)

Wandbox


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

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

この処理が上手く機能しない
data_nearly = list(map(lambda x: for i in set(x[0]) if i in x[-1],data_same_length))

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

追記を受けて

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

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

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

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

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

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

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

>>> sum(1 for _ in range(3))
3
>>> sum(1 for _ in range(1, 4))
3
>>> sum(1 for _ in 'hoge')
4

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


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

diff = sum(
    1 for ra, a in zip(right_answer, answer) if ra != a
)
  1. 二つの文字列を並列に比較する。
    ・もし文字が同じでないときは、リストに1を追加する。
    ・そうでないときは、リストに何も追加しない。
  2. リストの合計を算出する。この場合要素が全部1なので、リスト長を求めているのと同じ。

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

おまけ

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

def make_mark(right_answer):
    right_len = len(right_answer)

    def mark(answer):
        if len(answer) != right_len:
            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

    return mark


n = int(input())
mark = make_mark(input())

for _ in range(n):
    answer = input()
    print(mark(answer))

Wandbox

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/04/12 21:23

    お返事ありがとうございます。
    追記の質問も加えて、わかりやすい説明でとても勉強になりました。

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

    こちらの説明のように、具体的に何をどう処理しているのか考えながら進めてみます。
    クロージャに関しては、現状では難しいですが、復習しながら身に着けていきたいです。

    ご指導ありがとうございました。

    キャンセル

+1

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

INT_MAX = __import__('sys').maxsize


def mark(ans_pair):
    '''
    ・正しい単語と文字数が異なる 0点

    ・正しい単語と文字数は同じだが2文字以上異なる 0点
    ・正しい単語と文字数は同じだが1文字だけ異なる 1点
    ・正しい単語と文字数は同じだが0文字だけ異なる 2点
    '''
    ans_r, ans_t = ans_pair
    wrong_count = (
        INT_MAX if len(ans_r) != len(ans_t)
        else sum(r != t for r, t in zip(ans_r, ans_t))
    )
    return 0 if 2 <= wrong_count else [2, 1][wrong_count]


test_paper = [
    input().split()
    for _ in range(int(input()))
]
total = sum(map(mark, test_paper))

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/04/12 21:33

    お返事ありがとうございます。

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

    キャンセル

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

  • ただいまの回答率 90.62%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • Python 3.x

    5864questions

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