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

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

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

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

4回答

4680閲覧

python 数え上げ

makaron

総合スコア11

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

1クリップ

投稿2018/09/30 04:38

編集2018/11/17 09:46

こんにちは。
pythonのコードの書き方に関して質問させていただきます。

以下のようなファイルを読み込み、数え上げしているのですが

name
りんご 12
みかん 13
ぶどう 12
やさい 12
みかん 34
レンコン 34
:
:

と連なっているファイルがあるとして、左のnameそれぞれに対応する右の番号が一致した回数をカウントしたいです。私が書いたコードがこちらです。

python

1count = {} 2for line in f: 3 inputs = line.split("\t") 4 if len(inputs) != 2: 5 continue 6 7 if (count.has_key(inputs[1])): 8 count[inputs[1]] += 1 9 else: 10 count[inputs[1]] = 1 11 12sorted_count = sorted(count.items(), key=lambda x:x[1], reverse=True) 13 14for i in xrange(100): 15 print str(i + 1) + " & " + sorted_count[i][0] + " & " + str(sorted_count[i][1]) + " \ " 16 f2.write(str(i + 1) + " & " + sorted_count[i][0] + " & " + str(sorted_count[i][1]) + " \n ") 17f.close()

(ファイルの読み込みは省略しています)
このコードは、nameに関わらずカウントしてしまいます。(右の値の出現回数だけをひたすら計算)
エラーは出ずに、実行できます。

ここで、私が行いたいのはnameごとにカウントするということです。
例えば、1行目のりんごに関して右の値の出現回数を調べる。
それが終わったら、みかんに関して右の値の出現回数を調べる(この時、りんごの右の値は無視)
次は、ぶどうに関して右の値の出現回数を調べる(この時、りんご、みかんの右の値は無視)
のように、1回調べたものは次の思考では無視するということです。
その上で上のコードのように数を数えたいです。
最終的には、nameごとの右の値の出現数を合計していきたいと考えています。

わかる方いらっしゃいましたら教えていただきたいです。
よろしくお願い致します。

コメント欄に記載されているjun68yktさんの丁寧な解説のおかげでデータ数が少ない場合の処理は行うことができました(コードが多いので省略させていただきます)
しかし、データ数が増えた場合に処理が行えない(結果が出なくなってしまう)ので、データ数が多い場合でも実行できるコードの書き方があれば教えていただきたいです。
よろしくお願いいたします。

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

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

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

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

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

guest

回答4

0

ベストアンサー

こんにちは。

defaultdict を使うと、与えられたキーに対応する値がない場合の初期値を指定した dict を作ることができます。これを使うことで、makaronさんのご質問にある要件は、

python

1count = defaultdict(lambda: defaultdict(int))

というdictに処理結果を保存することで、実現できると思います。

以下、上記の考えをもとに、標準入力からデータを読み込んで処理し、結果を標準出力にリストするpythonスクリプト q149275.py を作成し、これにデータファイル input.txt を読み込ませたログです。( $はシェルプロンプトです)

$ python -V

Python 2.7.10
$ cat q149275.py

python

1# coding: utf-8 2 3import sys 4from collections import defaultdict 5 6count = defaultdict(lambda: defaultdict(int)) 7 8for line in sys.stdin: 9 inputs = line.split("\t") 10 if len(inputs) != 2: 11 continue 12 count[inputs[0]][int(inputs[1])] += 1 13 14for name in count.keys(): 15 print(name) 16 for k, v in sorted(count[name].items(), key=lambda x: x[1], reverse=True): 17 print('\t%d: %d 個' % (k, v))

$ cat input.txt | python q149275.py

レンコン 1: 6 個 8: 6 個 11: 6 個 3: 4 個 7: 4 個 10: 4 個 19: 4 個 4: 3 個 9: 3 個 13: 3 個 5: 2 個 12: 2 個 14: 2 個 15: 2 個 16: 2 個 17: 2 個 18: 2 個 2: 1 個 6: 1 個 りんご 14: 6 個 7: 5 個 16: 5 個 17: 5 個 1: 4 個 2: 4 個 6: 4 個 15: 4 個 3: 3 個 11: 3 個 19: 3 個 4: 2 個 5: 2 個 8: 2 個 9: 2 個 10: 2 個 12: 2 個 13: 2 個 18: 1 個 20: 1 個 みかん 13: 7 個 16: 7 個 2: 6 個 6: 6 個 19: 6 個 12: 4 個 17: 4 個 1: 3 個 3: 3 個 4: 3 個 18: 3 個 5: 2 個 7: 2 個 10: 2 個 11: 2 個 20: 2 個 9: 1 個 14: 1 個 15: 1 個 ぶどう 20: 11 個 2: 5 個 15: 5 個 3: 4 個 5: 4 個 11: 4 個 18: 4 個 7: 3 個 10: 3 個 13: 3 個 14: 3 個 1: 2 個 4: 2 個 6: 2 個 9: 2 個 17: 2 個 12: 1 個 16: 1 個 19: 1 個 やさい 4: 5 個 8: 4 個 9: 4 個 10: 4 個 13: 4 個 18: 4 個 1: 3 個 3: 3 個 7: 3 個 16: 3 個 17: 3 個 5: 2 個 6: 2 個 11: 2 個 15: 2 個 20: 2 個 2: 1 個 12: 1 個

$

なお、上記の入力で使用した input.txt は以下です。(データが300行あります)

text

1name 2レンコン 1 3りんご 17 4ぶどう 11 5ぶどう 2 6ぶどう 16 7やさい 1 8りんご 17 9みかん 4 10みかん 2 11りんご 4 12ぶどう 20 13レンコン 12 14(・・・以下略)

上記の q149275.py の入力元、および結果の出力先、出力形式などは、質問者様の都合にあわせて修正いただければと思います。

以上、参考になれば幸いです。

追記(2018/10/5)

こんにちは。コメントから頂きました要件から、以下の 3点のPythonスクリプト

を作成しました。これらは以下のレポジトリより取得できますので、git clone するかダウンロードしてください。

https://github.com/jun68ykt/q149275

以下、これらのスクリプトで行うことを順を追って説明します。

1. 準備

  • data_gen.py, make_map.py, count.py には、 chmod +x で実行権限を与えます。

  

  • また、これら3つのソースの shebang は、#!/usr/bin/env python3 となっており、 python3 を使う前提になっていますが、これは質問者様の環境に合わせて適宜、修正をお願い致します。

  

  • これら3つの Pythonファイルのうち、実質的な数え上げを行っているのは、count.pyに含まれる関数 countです。

  

  • また、以下の説明では環境変数 PATH にカレントディレクトリ . が含まれていることを前提にしています。

  

2. テストデータの作成

以下のコマンドで、data.txt にテストデータのファイルが作成されます。

shell

1$ data_gen.py > data.txt

data.txt は以下のようなものになります。

text

1# names: ['A', 'B', 'C', 'D', 'E'] 2# num_codes: 10 3# num_data: 20 4 5D X-07 6B X-05 7B X-01 8B X-03 9B X-06 10C X-02 11B X-08 12C X-04 13B X-04 14C X-08 15A X-08 16D X-08 17C X-03 18D X-09 19B X-02 20D X-10 21C X-07 22E X-04 23D X-03 24C X-09

上記テストデータの補足説明です。

  • names はデータ行の1カラム目に出現する文字列の配列です。
  • データ行の2カラム目の文字列を便宜上、コード(code)と呼ぶことにしています。
  • data_gen.py で生成されるコードは X-[0-9]{2}という形式になります。
  • num_codes: 10 は、コードの数字部分が 01から 10 を取り得ることを表します。
  • num_data は生成されるデータ行の数です。

namesの数と num_codes は、それぞれ、起動時の第1、第2引数で指定できます。たとえば

shell

1$ data_gen.py 9 6 > data.txt

とすると、data.txtの冒頭は以下のようになります。

txt

1# names: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'] 2# num_codes: 6 3# num_data: 21 4 5A X-03 6A X-06 7H X-01 8(...以下略)

なお、 num_data は、

python

1num_data = int(len(names) * num_codes / 2.5)

としています。

3. マップファイルの生成

上記 2. で作成したデータファイル data.txt を読み込んで、シンボルテーブルと、各シンボルの出現した行インデクスを含むファイルを生成します。生成されるファイルを説明の便宜上、マップファイルといいます。マップファイルの形式は json になります。

以下のようにパイプとリダイレクトを使って、マップファイルを生成します。

shell

1$ cat data.txt | make_map.py > data.map.json

data.map.json は、以下のようなものになります。

json

1{ 2 "symbols": ["A", "X-03", "X-06", "H", "X-01", "I", "X-04", "E", "F", "X-05", "C", "G", "X-02", "D", "B"], 3 "data": [ 4 [0, 1], 5 [0, 2], 6 [3, 4], 7 [3, 2], 8 [5, 2], 9 [3, 6], 10 [7, 1], 11 [8, 9], 12 [10, 9], 13 [8, 1], 14 [11, 12], 15 [8, 6], 16 [3, 12], 17 [13, 1], 18 [0, 12], 19 [11, 1], 20 [8, 12], 21 [10, 4], 22 [10, 2], 23 [14, 2], 24 [8, 2] 25 ], 26 "indexes": [ 27 [0, 1, 14], 28 [0, 6, 9, 13, 15], 29 [1, 3, 4, 18, 19, 20], 30 [2, 3, 5, 12], 31 [2, 17], 32 [4], 33 [5, 11], 34 [6], 35 [7, 9, 11, 16, 20], 36 [7, 8], 37 [8, 17, 18], 38 [10, 15], 39 [10, 12, 14, 16], 40 [13], 41 [19] 42 ] 43}

上記のマップファイルの説明です。

  • "symbols" は、 入力された data.txtのデータ行に出現する、すべての name と code の文字列を出現順に並べた配列です。
  • "data" は、入力された data.txtのデータ行を構成する name と code を、

[name_symbol_index, code_symbol_index] 
という、長さ2の配列に置き換え、この長さ2の配列を要素とする配列です。

  • "indexes" は、 "symbols" の各要素の文字列が出現するデータ行のインデクスを昇順に並べた配列を要素とする配列です。
  • たとえば上記の例では、"indexes" の先頭要素は [0, 1, 14]ですが、これは、 symbols[0]である文字列 "A"が、 "data" 配列のインデクス 0, 1, 14 すなわち、元のデータファイルのデータブロックの中の1,2,15 行目に出現することを表しています。

4. 数えあげ

上記 3. で作成した data.map.json を読み込んで、各nameについての数えあげを行った結果を得るには以下のようにします。

shell

1$ cat data.map.json | count.py

上記により、以下のように、各 name を数え上げた個数が標準出力に表示されます。

A: 13

B: 2
C: 5
D: 2
E: 4
F: 8
G: 5
H: 12
I: 4

  
使い方の説明は以上です。

テストデータのファイル data.txtdata_gen.py で作成せずに、以下

txt

1りんご 12 2みかん 34 3ぶどう 56 4りんご 34 5やさい 34 6豆   12

のように作成すると、これの数え上げの結果は以下となります。

shell

1$ cat data.txt | make_map.py | count.py 2ぶどう: 1 3みかん: 3 4やさい: 1 5りんご: 4 6豆: 1 7

追記

こんにちは。

その後、考えてみましたが、約2000万という個数のデータを扱うのであれば、データの元にするはテキストファイルでよいとしても、数え上げのような分析をするために、いったんデータベースに入れるのがよいと思います。

例えば仮にデータベースをMySQLだとして、以下のようなテーブルを作ります。扱う数も大きいので、DECIMALを使って最大桁数の65を指定しています。

カラム内容主キー
idINT行インデクス
nameDECIMAL(65,0)各行の左側の数
codeDECIMAL(65,0)各行の右側の数

データファイルの10行目(行インデクス=9) が以下

114760239 781870726160588800

であったとき、このテーブルは以下のようなレコードを持ちます。

idnamecode
9114760239781870726160588800

このようにデータファイルの内容を、データベースのテーブルとして移すことができれば、数え上げを行うロジックにSQLを使うことができます。

投稿2018/09/30 05:42

編集2018/11/21 22:28
jun68ykt

総合スコア9058

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

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

makaron

2018/09/30 07:19

とても丁寧な解説ありがとうございます。すごく為になり、感動しております。defaultdict今まで知らなかったので、勉強になりました。 今回の私の質問仕方が悪く誤解させるような形になってしまったのですが、私が求めたいものとしては、 (りんご 1)のように一括りとして考えるのではなく、(りんご 1)のとき、その行より後から出てきたものは全て左の値に関わらず(ぶどう、みかんでも)1が右に出てきたらカウントするというようにコードを書き換えたいです。 下のような形を目指しています。 この場合はどうすれば良いのかなどご存知でしたら教えていただきたいです。 例 りんご 12 みかん 34 ぶどう 56 りんご 34 やさい 34 豆   12 1行目のりんご 右の値  12 12はこのデータの中に2個ある  だから 2 4行目のりんご     34  34は4行目以降2個ある     だから 2 りんごは2+2=4 とする わかりにくくて申し訳ありません。 宜しくお願いします。
jun68ykt

2018/09/30 07:49

なるほど。確かに若干込み入ってますね。 そこで確認させて欲しいのですが、入力がそちらの例 りんご 12 みかん 34 ぶどう 56 りんご 34 やさい 34 豆   12 の場合に、結果として欲しい出力テキストは、どのようなものになりますでしょうか? 説明のために、 たとえば、"りんご 12" の 12 のことをコードと呼ぶと、同じコードを持っている行数を数えて、コードと出現回数を一行にして(ソートは考慮せずに)出力すると、上記の例だと 12  2 34  3 56  1 というものになるかと思いますが、欲しい結果は多分こうではないのですよね?
jun68ykt

2018/09/30 08:03 編集

ちょっと考えてみましたが、上記の例だと、こうですかね? りんご 4 みかん 3 ぶどう 1 やさい 1 豆  1 ”やさい" の場合、 5行目で初めて出現して、コードは 34。コード 34 の行は、2行目の "みかん 34" と、4行目の "りんご 34" の2つがあるが、これは "やさい”が初めて出現する 5行目より前なのでカウントしない。また、6行目以降には、コード 34の行はない。ゆえに、”やさい" の場合 該当数は 1 。"豆" も同様に 1 。 上記であってますでしょうか?
jun68ykt

2018/09/30 08:30 編集

もう一点確認させてください。入力が以下のような例 りんご 12 みかん 34 ぶどう 56 りんご 34 やさい 34 りんご 12 の場合に、1行目と6行目は同じで、"りんご 12" ですが、この場合、りんごの数はいくつになるのが望ましいでしょうか?つまり、最後の1行だけで2と数えるのか、あくまで1行は1と数えるのか、という確認です。
makaron

2018/10/01 04:55

お返事ありがとうございます。 遅い返答となってしまい申し訳ありません。 はい、二つ目の返答のように解釈していただいて大丈夫です。わかりにくい説明だったのにありがとうございます。 また、三つ目の返答についてですが重複は本来発生しないはずなので私が書いた例が間違っていました。 りんご 12 が二回出てくることはないと考えていただいて結構です。 宜しくお願いします。
jun68ykt

2018/10/04 20:17

こんにちは。 要件を把握したうえで、いくつかの Python スクリプトを作成して、その説明を回答のほうに追記しました。作成したスクリプトはGitHub から取得できます。ご要望の結果と違う点や、ご質問等あれば、またご返信ください。
jun68ykt

2018/10/04 20:57

追伸 中間形式としてJSONを介して処理を行うことにしたのは、数え上げのためのデータ構造を作る準備的な処理と、数えあげロジック本体とを明確に分離するためです。このようにすれば、数えあげロジック本体のコード量が減って、要件を満たすものになっているかの確認がしやすいと考えました。
makaron

2018/10/05 05:49 編集

とてもわかりやすいご回答ありがとうございます。jsonまでのコードの理解と実行を行うことができました。 ありがとうございます。count.pyのところでImportError: No module named modと言うエラーが出てしまいました。これは、modが入っていないため実行できないということはわかったのですが対処法がわかりません。教えていただけると幸いです。
makaron

2018/10/05 06:04

pip install modは実行したのですがエラーが出てしまいます。
jun68ykt

2018/10/05 10:54 編集

こんにちは。 mod.py はダウンロード頂いたソースコードの中に含まれていると思うので、 pip で install する必要はないと思いますが、モジュールのimportができないことを解決するのは本題ではないので、以下のコミット https://git.io/fxOtN で、 count.py に count 関数を移動しました。 ですので、もし git clone されたのであれば git pull して頂くか、もしくは、再度まとめてダウンロードして頂ければと思います。 また不明点あればお知らせください。
makaron

2018/10/05 13:39

ありがとうございます。そこは無事、進めることができました。最後の自分のデータを用いて実行するところで再び躓いてしまいました。cat ファイル名.txt | make_map.py | count.pyで実行しても何も出力されません。エラーが出ないので原因がわからないのですが、何か思い当たることがあれば教えていただきたいです。
jun68ykt

2018/10/05 18:08

なるほどですね。ちょっと調べますので、その > 何も出力されません。 という結果になった、ファイル名.txt を cat ファイル名.txt することで出力される内容をコピペして頂けますでしょうか?
jun68ykt

2018/10/06 01:15

入力ファイルの内容が以下 りんご 12 みかん 34 ぶどう 56 りんご 34 やさい 34 豆   12 である場合の結果が分かる画面キャプチャを以下に貼りましたので、お手すきでご確認ください。 https://git.io/fx3zR
makaron

2018/10/06 02:31 編集

こんにちは。画面確認いたしました。同じ要領でやっている気がするのですが、、、cat sample.txt | python make_map.py | python count.py を実行しても何も出力されません (りんご 12 みかん 34 ぶどう 56 りんご 34 やさい 34 りんご 12で行っています。)出力される結果がないので、コピペも出来なくて下のようになります・ $cat sample.txt | python make_map.py | python count.py $ また、次に進むというような感じです。
makaron

2018/10/06 02:22

また、meka_mapの所でこのようになります。 cat sample.txt | python make_map.py { "symbols": [], "data": [ ], "indexes": [ ] }
makaron

2018/10/06 02:43

左の値をアルファベットや数字にしてみたら実行することができました!! ありがとうございます! ちなみにこの結果を右の値で降順に並べかえることは可能ですか?コマンドでのやり方はわかるのですが、このコードに組み込むことはできますか?
jun68ykt

2018/10/06 03:08

こんにちは。 > ちなみにこの結果を右の値で降順に並べかえることは可能ですか? はい。現状では count.py で結果の出力時に sorted を使って、name の昇順にソートしていますが、 これをカウント結果の個数の降順ソートするには、以下の修正 https://git.io/fx3at のように、dict の値の降順になるようなソートキーを keyとして指定します。 > 左の値をアルファベットや数字にしてみたら実行することができました!! なるほどです。 ちょっと気になる点としては、makaronさんお使いの Python は Python のバージョン3系ではなく 2系でしょうか? python -V というコマンドを打つと何と出ますか? それと、私が使っているPCは Mac で、 data.txtの文字コードはUTF-8ですが、makaronさんの、うまくいかなかった sample.txt の文字コードは何か?という点も気になります。 cat sample.txt | python make_map.py { "symbols": [], "data": [ ], "indexes": [ ] } ということだと、入力ファイルのデータ行が、空白文字で区切られた2つのトークン( name と code)から構成されるものとして認識されていなくて、結果、データ行が1行もないものとして処理されてしまっているのでは?と推測しています。
jun68ykt

2018/10/06 03:20

Python2系で検証していないので、以下もあくまで推測になります。 疑うべき点として思いつくのは、もしお使いの Python がバージョン2系で、 かつ、 sample.txt の各行で、name(例:みかん)と code(例:34) の 区切り文字として、半角ではなく全角のスペースをお使いの場合、 tokens = line.split() if not len(tokens) == 2: continue の len(tokens) が 2 になってないのかもしれませんね。 それですべてのデータ行が処理されず、 continue でスキップされてしまっているのでは? と思っています。
makaron

2018/10/06 03:50

python 2.7.6でした。 python3系にアップロードしたら実行することができました! なので多分バージョンのせいですね。 また、降順への変更の仕方もありがとうございます。実行することができました! いろいろと細かい所を教えてくださり、またとても丁寧な説明をありがとうございます。勉強になりましたし、とても助かりました。
jun68ykt

2018/10/06 03:55

解決されたようですね、よかったです!
makaron

2018/11/17 09:42

こんにちは。 以前教えていただいたこちらのコードで分析を行い、データ数をどんどん大きくしていったところ、一定数を超えると結果が表示されなくなってしましました。もし、その原因がわかるようでしたら教えていただきたいです。
makaron

2018/11/17 09:48

度々申しわけありません。 質問として新規にあげたいので一時的にベストアンサー外させていただきます。 また、後でベストアンサー押します!
jun68ykt

2018/11/17 23:18

@makaronさん こんにちは。 今朝、上記のコメントを拝読するのと同時に、私のスコアが ー5 されていて、若干びっくりしました(笑) 一度ベストアンサーに選ばれたあと、何らかの理由でベストアンサーを外されると、(当然といえば当然ですが、) ベストアンサーに選ばれたことによる加点が差し引かれるようになっているのですね。 こういうことは初めてだったのでやや焦りました。 さて、本題ですが >データ数をどんどん大きくしていったところ、一定数を超えると結果が表示されなくなってしましました。 > もし、その原因がわかるようでしたら教えていただきたいです。 とのことですが、おそらくメモリ不足でプログラムが異常終了したのだと思われます。というのも、私の作成した、make_map.py も count.py も処理対象のデータ全体をメモリで持つようにしているからです。これらのコードを回答したときは、ロジックの分かり易さを最優先して、中間にJSONファイルを使い、count.pyはそのJSONを丸ごと読み込んだうえで数え上げを行いますが、コンピュータのメモリは無限にあるわけではないので、 >データ数をどんどん大きくしていった ら、どこかで許容量をオーバーして、プログラムが意図した正常な動作をせずに異常終了すると予想できます。回答を書くときに、 「データを全部メモリに持つプログラムになっているので、データ数を増やしていったら、どこかでプログラムが異常終了することになります」 というお断りをひと言つけ加えておくべきでしたね。 私の作成した、make_map.py と count.py で行っていることを、大量の入力データでも耐え得るようにすることは、いくつか方策が考えつきます。 ついては、makaronさんが入力としてお使いの、データが多量に含まれているテキストファイルをそのまま、または圧縮して ZIP ファイル等にし、そのテキストまたはZIPファイルをFTPでどこかのサーバーに上げて頂くか、もしくはfirestorage のようなデータ共有できる場所に置いてもらって、それを、私ふくめ回答者がダウンロードできるようにして、そのURLを教えて頂くことは可能でしょうか?
raccy

2018/11/18 00:30

最初の質問と聞きたいことが変わっていますので、質問の内容を変えるのではなく、この質問を参照にあげながら、今現在問題になっていることを別途新たに「質問する」方が、回答しやすくなって他の回答もつきやすいと思いますよ。
Zuishin

2018/11/18 00:42

これだけ丁寧に回答され、当初の質問は解決されているのに、そこからベストアンサーを奪うような回答ができるはずもありませんね。ネタ回答ならできますが。
makaron

2018/11/18 01:46

jun68yktさま 不快な思いをさせてしまい申し訳ありませんでした。 私も使い方がわからず、質問として再掲するにはそうするしかないと思い外してしまいました(新しい質問としてjun68yktさんのコードを上げるのはもっと失礼な気がしたので、、)申し訳ありません。 教えていただいた通りにダウンロード可能なようにやり方を調べて上げるのでお待ちください。お早いお返事ありがとうございました。
makaron

2018/11/18 01:47

raccyさん アドバイスありがとうございます! 参考にさせていただきます。
makaron

2018/11/18 01:48

zuishinさま はい、とても丁寧な回答をいただきました。 そうですよね。拝見していただくだけでもありがとうございました!
makaron

2018/11/18 01:54

jun68yktさま もうひとつ質問させていただきたいです。 データが20000000行あるので、もしかしたら時間がかかるだけなのかもしれないと思いました。1000000行までは時間はかかりましたが実行できました。 (5時間ほどです)この場合、時間がかかっているだけの可能性はありますか?
makaron

2018/11/18 08:33

たびたび申し訳ありません。 データなんですが、リモートで使っていていろいろ保存を試みてみたのですが、txtファイルの中身をコピペするくらいしか思いつかず20000000行ほどあるのでちょっと無謀に思えまして何か方法ご存知でしたら教えていただきたいです。 データの中身としては、 りんご 12 みかん 34 ぶどう 56 りんご 34 やさい 34 豆   12 が21800000行続きます。 実行できなくなるのは1000000行からで、 100000は2分で実行ができ500000は3時間ほどかかりました(timeコマンドで計測しました) 何度も質問申し訳ありません。宜しくお願いします。
jun68ykt

2018/11/18 11:12

@raccyさん フォローありがとうございます。 @makaronさん > @raccyさん も仰ってますとおり、このような場合 > 別途新たに「質問する」方が、回答しやすくなって他の回答もつきやすい と思われ、ひいては、makaronさんにとって有益な回答がより多く得られる可能性が高くなると思います。 また、なるべく多くの回答者様から考え方の違うアプローチを複数頂いたほうが、makaronさんの問題が早く、かつより良い形で解決することを期待できます。今後はご検討ください。
jun68ykt

2018/11/18 11:14

@Zuishinさん > これだけ丁寧に回答され、当初の質問は解決されているのに、そこからベストアンサーを奪うような回答ができるはずもありませんね。 フォローありがとうございます。 質問内容が興味深いものだったので、少し丁寧に対応しました。 今回の場合、ベストアンサーを取り消しされたといっても、質問者様としては特に悪意があってのことではなく、単に 「その時は解決できたと思ったが、その後、色々やってみると、実は本当にやりたいことの解決にはなっていませんでした」 という現状を伝えたかったのだろうと思っております。 今後ともよろしくお願いいたします。
jun68ykt

2018/11/18 11:17

@makaronさん > 不快な思いをさせてしまい申し訳ありませんでした。 No problem. 大丈夫です。 >不快な思い というよりは、 「何か間違ったことを書いてしまったかもしれない。それは何だ?」 という、率直な疑問でした。 本題に入ります。 まずは > 20000000行ほどあるので というのを聞いて、まずは「多っ!」といのが率直な印象ですね。 ゼロを数えましたが、二千万行ですよね? ではデータファイルの受け渡しをしないで済ませましょう。 > この場合、時間がかかっているだけの可能性はありますか? はい。あると思います。 もし、20000000行ほどある入力でプログラムを実行したときに、 どこかで MemoryError が原因でプログラムが異常終了した、 ということではなく、シェルのプロンプトが返ってこないとしたら > 時間がかかっているだけ と思われます。 ただし > 1000000行 の処理時間が >5時間ほど だったからといって、その20倍の 20000000行 だったら、単純に約5時間×20 = 100時間 で終わるとも言い切れません。 私の書いたコードを、20000000行ものデータで使うとすると、 果たして実用に耐えうる時間内に終わるのかどうかについては、 計算量のオーダーがどうなるのかを調べる必要があります。 ですが、それ以前に、私の回答では20000000行ものデータが入ってくる想定では 作られていないので、データ構造やアルゴリズムで生かせるものは使って、 何らか別の手段によるデータ運用を考えたほうが良さそうな気がしています。
jun68ykt

2018/11/18 11:20

@makaronさん 上記をふまえ、こちらからの確認させて頂きたい点として、以下の3点ほどあります。 >データの中身としては、 >りんご 12 >みかん 34 >ぶどう 56 >りんご 34 >やさい 34 >豆   12 >が21800000行続きます。 とのことで、上記のように、データファイルの各行はスペース(またはタブ)で区切られた、2つのカラムによって構成されていますが、 (1) 上記の簡単な例では、各データ行の1カラム目に出現する文字列は  "りんご", "みかん", "ぶどう", "やさい", "豆"  の5種類ですが、21800000行ある実際のデータでは、この1カラム目に出現する  文字列は何種類ぐらいあるのでしょうか? (2) 同じく、上記の簡単な例では、各データ行の2カラム目に出現する文字列は  "12", "34", "56" の3種類ですが、21800000行ある実際のデータでは、この2カラム目に出現する 文字列は何種類ぐらいあるのでしょうか? (3) makaronさんの追加のご質問としては 上記の21800000行(またはそれ以上)のデータ行を含む入力があっても、実用に耐えうるようなものにするには、現在の(私が回答に挙げた)プログラムをどう改修していけばよいか? または改修するのでは無理なら、別の方法としてどういう方法が考えられるか?  ということだと思ってよいでしょうか? とり急ぎ、知りたいのは、以上の3点です。
makaron

2018/11/18 12:13 編集

jun68yktさん 丁寧な返答ありがとうございます。また、他の回答者様にもフォローしてくださってありがとうございます。さらに、私のことを考えてのアドバイスまでありがとうございます! 数え間違いではなくデータ数が多いです。今までもこれくらいのデータを扱っていたのですが、今回ばかりは今までに比べてもかなり多くのデータを用いています。 質問に順番に答えさせていただきます。 (1)30万くらいだと思われます。数えてはないので確かではないのですが、今までの仕様経験上これくらいだと思われます。 (2)こちらも同じく30万もしくはそれ以下だと思われます。 (3)はい、その通りです。説明不足なものを理解してくださってありがとうございます。 データの一例は下記のようなものです(最初の行を抜きました)
makaron

2018/11/18 12:14

77285694 697726300182548480 732565655820468224 781869880639356928 2168183685 781714574487588864 752872573059293184 781870709890961409 114760239 781870726160588800 4056777252 781861234199056385 88882337 781690001859940352 3191633905 781868694230016000 309633997 781856678492778497 773225542451654656 781806157652303872
makaron

2018/11/18 12:15

本来はタブで区切られています
makaron

2018/11/18 12:18

度重なる質問にもお答えくださってありがとうございます。コードを書くのがとても苦手なため、自分でjun68yktさんのものを見ても何も改善点が見つかりませんでした(完璧でした) 以前書いてくださったコードを使いこなせず申し訳ありません。 またアドバイスよろしくお願いします。
jun68ykt

2018/11/18 13:45

@makaronさん ご返信ありがとうございます。 実際の入力ファイルは > ・・・ > 3191633905 781868694230016000 > 309633997 781856678492778497 > 773225542451654656 781806157652303872 というものなのですね。これを見て、もう少し細かいところで、 確認させて欲しいことが出てきましたので、お手数ですが、教えてください。 以下の 3点です。 (4) データ行は、タブで区切られた2つの文字列で、2つとも半角数字だけから構成される文字列であると考えてよいでしょうか? (5) サンプルを見たところ、「桁をそろえるための先頭のゼロ埋め」はされていなさそうですね。  先頭のゼロ埋めとは、たとえばサンプルの中に 1カラム目に “88882337” が出てくる行がありますが、これの先頭に “0” がいくつか付加された “088882337” だったり、 “0088882337” だったりは、どの行にも出現しないように思ってよいですか? ???? 「先頭のゼロ埋めはない」のであれば、1カラム目、2カラム目ともに、単なる文字列ではなく整数して扱える目途がたち、文字列の比較をするより整数の比較のほうが速いので、高速化を図れることが期待できます。 (6) 各カラムを整数値と考えてよい場合、出現しうる値の最小値は 0 でしょうか? また、最大値があれば教えてください。
makaron

2018/11/18 14:01

お早いご返事ありがとうございます。 順にお答えしていきます。 (4)はい、そうです。 数字 タブ 数字 改行 と言った感じです。 (5)はい、0埋めはされていないです。どの行にも出現いたしません。また、すべて整数です。(高速化期待できるのですね!嬉しいです!) (6)0という数字は存在しないです。awkで調べたところ78987275134790087652036837376でした。でも特に決まりなどはありません。調べて分かったというだけです。 よろしくお願いします。
jun68ykt

2018/11/18 14:04

ご返信ありがとうございます。少し考える時間をください。またこちらから回答します。
makaron

2018/11/18 14:07

はい、よろしくお願いいたします。
jun68ykt

2018/11/21 22:37 編集

こんにちは。 先ほど追記したようにテキストファイルの内容をデータベースに投入することができれば、数え上げのロジック自体は、部分的にSQLを使うということを除いて、大筋のロジック自体は初めに回答したものとそれほど大きな違いはなく書けると思います。 次のステップに進む前に、 何らかのデータベースを用意して追記に書いたようなテーブルを作成し、データファイルの内容を投入することはできますか? ということを確認させて頂きたいです。
makaron

2018/11/27 06:23

確認が遅くなってしまい申し訳ありません。 はい、データベースを使用することは可能です!
makaron

2018/11/27 06:24

しかし、データベースに関しては大まかな知識しかないので、詳しい使い方やコードの書き方はあまりわかっておりません。
guest

0

考えてみました。
動作の説明はコメントとしてコードに記載しているので省略します。
提示されているりんご~豆のテストデータにて結果は正しかったので、自信ありませんが大丈夫と思います。多分。
ポイントとしては、行毎にファイルを読み込むことでメモリを節約しています。

Python

1import time 2# コード、名称毎に数え上げ 3 4dic_cnt = {}# コード毎、名称毎に個数を保持する2重辞書 5 # {コード:{名称:個数, ...}, ...} 6prev = time.time() 7with open('data.csv','r') as f: 8 line = f.readline().strip() 9 while line: 10 line = line.split() 11 name,code = line[0],line[1] 12 line = f.readline().strip() 13 14 # 始めて出現したコード -> 登録 15 if code not in dic_cnt: 16 dic_cnt[code] = {} 17 18 # 初めて出現した名称 -> 登録 19 if name not in dic_cnt[code]: 20 dic_cnt[code][name] = 0 21 22 # 既存の名前の個数をインクリメント 23 for name,count in dic_cnt[code].items(): 24 dic_cnt[code][name] += 1 25 26print('counting done.{:.2f}sec'.format(time.time()-prev)) 27prev = time.time() 28 29# 名称毎に集計 30dic_ret = {} 31for v in dic_cnt.values(): 32 for name,cnt in v.items(): 33 if name not in dic_ret: 34 dic_ret[name] = 0 35 dic_ret[name] += cnt 36 37# 結果ファイル出力 38with open('ret.csv','w') as f: 39 for name,cnt in dic_ret.items(): 40 f.write('{} {}\n'.format(name,cnt)) 41 42print('sum up done.{:.2f}sec'.format(time.time()-prev)) 43prev = time.time()

テストデータでの検証結果

以下、行数2千万、名称種類30万、コード種類20万で試した結果です。
Corei7搭載機で約5分。まあ我慢できるレベルかと。
ちなみにコード種類数が少なくなると計算量は急激に増加しますのでご注意を。
その際はデータベースを利用するなど別の手法を考えるべきです。

以下、実行結果

counting done.310.84sec sum up done.15.48sec

以下、入力データ data.csv 約420MB

name27454 code70170 name45711 code106756 name139751 code28233 :

以下、結果データ ret.csv 約5MB

name210822 4235 name52534 3304 name10758 3314 :

テストデータ作成コード

なお、サンプルデータは以下のコードで作成しました。

Python

1# テストデータ作成 2import random 3random.seed(123) # 乱数固定 4ROW,NAME,CODE = 20000000,300000,200000 # 行数、名称数、コード数 5dup = {} # # 名称+コード重複行チェック用 6prev = time.time() 7with open('data.csv','w') as f: 8 for _ in range(ROW): 9 name = 'name{}'.format(random.randint(1,NAME)) 10 code = 'code{}'.format(random.randint(1,CODE)) 11 if name+code not in dup: # 名称+コード重複行は存在しない仕様 12 f.write('{} {}\n'.format(name,code)) 13 dup[name+code] = 1 14 15print('create data done.{:.2f}sec'.format(time.time()-prev)) # create data done.88.37sec 16prev = time.time() 17print(ROW,NAME,CODE)

データ分析コード

ついでに、データファイルの分析をおこなうコードを作成しました。
行数や名称,コード(種類)数、名称,コード文字長の最小、最大、平均を出力します。

Python

1# データの分析 2 3# 文字列の最小、最大、合計を保持 4def get_len_info( s, mm): 5 s_len = len(s) 6 mm[2] += s_len 7 8 if s_len < mm[0]: 9 mm[0] = s_len 10 if s_len > mm[1]: 11 mm[1] = s_len 12 13# 行 0=名称, 1=コード 14# 列 0=最小, 1=最大, 2=合計 15len_info = [[99999,-1,0],[99999,-1,0]] 16 17row_cnt,name_set,code_set = 0,set(),set() 18with open('data.csv','r') as f: 19 line = f.readline().strip() 20 while line: 21 line = line.split() 22 name,code = line[0],line[1] 23 line = f.readline().strip() 24 25 name_set.add(name) 26 code_set.add(code) 27 28 # 文字列長の最小、最大、合計を保持 29 get_len_info(name,len_info[0]) 30 get_len_info(code,len_info[1]) 31 32 row_cnt += 1 33 34name_cnt,code_cnt = len(name_set),len(code_set) 35 36print('行数[{}] 名称(種類)数[{}] コード(種類)数[{}]'.format(row_cnt,name_cnt,code_cnt)) 37print('名称長 最小[{:.0f}] 最大[{:.0f}] 平均[{:.2f}]'.format( len_info[0][0], len_info[0][1], len_info[0][2]/row_cnt)) 38print('コド長 最小[{:.0f}] 最大[{:.0f}] 平均[{:.2f}]'.format( len_info[1][0], len_info[1][1], len_info[1][2]/row_cnt)) 39""" 40行数[19996722] 名称(種類)数[300000] コード(種類)数[200000] 41名称長 最小[5] 最大[10] 平均[9.63] 42コド長 最小[5] 最大[10] 平均[9.44] 43"""

投稿2018/11/18 13:42

編集2018/11/19 01:59
can110

総合スコア38233

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

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

makaron

2018/11/18 14:14

お返事ありがとうございます! 申し訳ないのですが頭がついて行っておらずいくつか質問させていただきたいです。 行ごとにファイルを読み込むというのは元々あるtxtファイルから1行ずつ読み込んでいくことで時間短縮になるということでしょうか? PlainTextというのはcan110さまがランダムに用意してくださって、(質問の条件に合うように)実行できたというものですか? PlainTextを二つ用意していただいておりますが二つの違いが理解できませんでした。 勉強不足で申し訳ありません。 お答えいただけると幸いです。 よろしくお願いいたします。
can110

2018/11/18 14:22

>~時間短縮になるということでしょうか? いえ。メモリ節約になります。 > PlainTextというのは~ はい。実際にサンプルデータを生成してみて実行できました。 >PlainTextを二つ用意~ 分かりにくかったので修正しました。
makaron

2018/11/18 14:29

お返事ありがとうございます。 メモリ節約ですね、ありがとうございます。 他二つも理解できました! そして、コードなんですがこちらも勉強不足で理解が及んでいないので質問させていただきたいです。 # テストデータ作成 import random random.seed(123) # 乱数固定 ROW,NAME,CODE = 20000000,300000,200000 # 行数、名称数、コード数 ここまでで、データの例を作成したという解釈でよろしいですか? (つまり私は元々データがあるのでこれはいらない?) そしてその後のコードで実行時間を計算しているということでよろしいですか?そして実行結果が一番上に記載されているものになったので早くなったということでしょうか? 初心者でしてコードが理解できず申し訳ありません。 また最後のprint(ROW,NAME,CODE)は何をprintすることになるのでしょうか? 質問ばかりで申し訳ありません。 よろしくお願いします。
can110

2018/11/18 14:32

その理解でよいです。 > また最後のprint(ROW,NAME,CODE)は~ これは容易に類推、ご理解できると思うのですが…行数、名称数、コード数です。
makaron

2018/11/18 14:39

はい、それはわかっているのですがそれまでの推移がわからず混乱してしまいました。 説明がわかりにくく申し訳ないです。 これはret.csvに記載されているものということですか? (つまり求めたいカウント数がprintされるという解釈であっていますか?)
makaron

2018/11/18 14:41

また、私はデータをtxtファイルで扱っているのですがこのコードのcsvをそのまま.txtに変えての応用は可能ですか?
can110

2018/11/18 14:46

まず、肝心の数え上げのコードが漏れていましたので回答に追記しています。失礼しました。 > これはret.csvに記載されているものということですか? いえ。テストデータ生成のためのパラメータ(行数~)がいくつだったかの確認のために出力しているだけです。 なお、テストデータは乱数で生成しているので重複行が発生します。それを除外してファイルに出力しているので実際のdata.csvの行数はそれよりわずかに少ない行数になります。
can110

2018/11/18 14:49

> また、私はデータをtxtファイルで扱っているのですが~ どのような部分を変える必要があるかまずはご自身で考えてみてみましよう。 基本的にタブ区切りだろうが空白区切りだろうがtxtもcsvも同じモノです。
makaron

2018/11/18 14:52

そういうことだったのですね、最後のファイルを数え上げのファイルだと思い一生懸命理解しようとしていました。それなら理解できました! 今上げていたコードですと with open('ret.csv','w') as f: for name,cnt in dic_ret.items(): f.write('{} {}\n'.format(name,cnt)) print('sum up done.{:.2f}sec'.format(time.time()-prev)) prev = time.time() が結果の出力ということで結果はret.csvに記載されまた、かかった時間も記載してくださったということですね。ありがとうございます! ret.csvだと name1234354のように最初にnameがついていますが実際もこのnameというのはつきますか?コードを読むとつかないように感じられたので一応お伺いしました。
makaron

2018/11/18 14:53

二つの目の回答もありがとうございます!はい、まずは自分で考えてみます!うかがってばかりで申し訳なかったです。ちょっとcsvを調べてみます。
can110

2018/11/18 15:09

>name1234354のように最初にnameがついていますが実際もこのnameというのはつきますか? 「みかん~」などの簡単な入力データファイルで実際に実行してみると すぐにご理解いただけるかと思います。コードを読むだけでなく実際に動かすことも大事です。
makaron

2018/11/18 15:21

今実際に動かしてみたところ正常に動きました。また、教えていただいたところの意味も理解できました!1000000行を実行したところかなりの速さで行うことができました。20000000行はこれから動かしてみようと思います。また、結果を明日お送りいたします。 ご回答ありがとうございました。
makaron

2018/11/19 01:19

こんにちは。 今朝方結果を見たところ、スムーズに進んでいました。 20000000行ができているのかまだわかっていません。(時間がなくそ浴でくる結果と同じくらいか確認できていないです) ちょっとまだしっかり見ていないので今日の夜確認してみます。 あくまで、コードが動いたことをお伝えするためにコメントいたしました。本日の夜、もう一度動かしてみます!また、進捗のご連絡いたします。
can110

2018/11/19 02:03

データ分析コードを追記しました。 可能であれば参考のため分析実行した結果も教えてください。
makaron

2018/11/20 01:02

こんにちは。 遅くなり申し訳ありません。 無事、問題なく分析行うことができました! ありがとうございます。コードの追記もありがとうございました。 counting done.24714.07sec sum up done.39.21sec こちらのような結果になりました。
makaron

2018/11/20 01:03

データ分析コードはこれから拝見させていただきます。
guest

0

jun68yktさんとのコメントでのやり取りを眺めて、コードを起こしてみました。

python

1from collections import defaultdict 2 3txt = """りんご 12 4みかん 34 5ぶどう 56 6りんご 34 7やさい 34 8豆   12 9""" 10 11data = [line.split() for line in txt.splitlines()] 12 13result_dict = defaultdict(int) 14 15for i, (key, val) in enumerate(data): 16 for k, v in data[i:]: 17 if v == val: 18 result_dict[key] += 1 19 20for k, v in sorted(result_dict.items(), key=lambda x:x[1], reverse=True): 21 print(k, v) 22""" => 23りんご 4 24みかん 3 25ぶどう 1 26やさい 1 27豆 1 28""" 29

件数があると計算量的にきついかもしれないので、改良した方が良いかも。

投稿2018/09/30 23:26

hayataka2049

総合スコア30933

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

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

makaron

2018/10/06 02:28 編集

hayataka2049さん ご回答ありがとうございます。 ファイルの1部を引っ張り、実行してみたところ正しい結果を出すことができました。ありがとうございます。 しかし、データの中身が多く可能であればファイルの中身を読み込んで同様の処理を行いたいのですが、その場合3行目のdata=の部分を変更するのが良いでしょうか? ご回答ありがとうございました。 こちらについても可能でしたら教えていただけたら幸いです。
guest

0

name
りんご 12
りんご 13
りんご 12
りんご 12

というファイルの場合、「りんご 12」と「りんご 13」で別々に出現回数を調べるという話だと思います。
なので、
count[("りんご", "12")] = 3
count[("りんご", "13")] = 1
となるように作るのはどうでしょう。ディクショナリのkeyはリストはダメなので、タプルにします。

Python

1count = {} 2for line in f: 3 inputs = tuple(line.split("\t")) 4 if len(inputs) != 2: 5 continue 6 7 if (count.has_key(inputs)): 8 count[inputs] += 1 9 else: 10 count[inputs] = 1 11 12sorted_count = sorted(count.items(), key=lambda x:x[1], reverse=True)

投稿2018/09/30 05:41

takey

総合スコア312

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

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

makaron

2018/09/30 07:04

お返事ありがとうございます。 まだ、pythonの学習期間が短くタプルというものがわかっておらず、ディクショなりとの違いを教えていただきたいです。コードを実行するとエラーになってしまうのですが、このコードにタプルというものを組み合わせる感じでしょうか?
takey

2018/09/30 14:20

エラーメッセージを貼ってください
makaron

2018/10/01 07:22

ご返信ありがとうございます。 遅くなってしまい申し訳ありません。 わたしが挿入したファイルを読み込ませるコードに原因があり、そこを直したら修正できました。 この実行結果を別のファイルに書き込む書き方についても教えていただきたいです。以前のコードのf2.writeを色々変えてみたのですが対応のさせ方がわかりませんでした。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問