Pythonで2択から好きな方を選んでランキングをつくるプログラムを作成

  • 2019-12-05
  • 2020-03-03
  • Python

Pythonを使って2択からランキングを作成するプログラムを作成

映画「ソーシャル・ネットワーク」の序盤で主人公がつくっていた、「二人の女子の顔写真を表示し好きな方を選んで女子をランク付けするwebアプリ」
に近いものを自分でつくってみたいと思います。
まずは、Pythonでコンソールで名前から好きな方を選択するだけのシンプルなプログラムを作ってみます。

なのでここでは、
「多数のデータの中からランダムに二つを抽出し、どちらが好きかの選択を繰り返すことでデータを自分の好きな順に並び替えるプログラム」
を作成します

データは、

  1. 自分でデータを作成
  2. エクセルの任意の列から

のどちらかかから取得できるようにしました

また、ランク付けの方法はレート方式を採用しています

モジュールのインポート

In [1]:
import random
import sys
import xlrd 

データのクラスを作成

各データの情報を格納するクラスを作成します

In [2]:
class DataInfo:
    """Part Index"""
    def __init__(self,name):
        self.name = name #名前
        self.rate = 1500 #レート
        self.count = 0   #呼び出し回数
        self.have = []   #既に呼び出された相手を格納するリスト

データをデータクラスに格納する関数

excelからの読み込みはxlrdモジュールを使います。

In [3]:
def Add_from_list(infos,data):
    """リストから格納"""
    for d in data:
        x = DataInfo(d)
        infos.append(x)
    
    print(str(data)+'のデータを追加しました')
    
def Add_from_excel(infos,filename,sheetnum=1,colnum=1):
    """エクセルファイルから格納
        @parameter
        filename: ファイル名
        sheetnum: エクセルのシートの番号(デフォルト1)
        colum   : 列の番号(デフォルト1)
    """
    wb = xlrd.open_workbook(file_name)
    sheet = wb.sheet_by_index(sheetnum - 1)
    col = sheet.col_values(colnum - 1)

    Add_from_list(infos,col)

要素にクラスオブジェクトを含むリストでは要素数を取得するlen()関数が使えないので、クラスオブジェクトを含むリストの要素を取得する関数を定義

In [4]:
def Get_len(infos):
    """クラスを含むリストの要素数を取得"""
    i = 0
    for info in infos:
        i+=1
    return i

データから組合わされていない二つを選択し、好きな方を選択させる

ランダムに二つを選ぶロジックは

  1. データを出現回数が少ない順に並べ、最も出現回数が少ないデータからランダムに一つを選択
  2. 二つ目のデータをランダムに一つ選択
  3. 二つ目のデータが「一つ目と異なるデータ」かつ「出現回数が残りの中で最小」かつ「まだ一つ目と当たっていないデータ」が得られるまで繰り返す

このようにすることで、選択に偏りがでないようにしました

また、レートの計算方法はポケモンの方式と同じものを採用しています

In [5]:
def choose2(infos):
    """データクラスから二つを選択する関数"""
    num = Get_len(infos) #要素数
    
    #出現回数の最低値
    min = 100000
    for i in infos:
        if i.count <= min:
            min = i.count 
    
    #出現回数が最小のデータの個数
    mincount = 0        
    for i in infos:
        if i.count == min:
            mincount += 1 
            
    infos = sorted(infos, key=lambda t:t.count) #出現回数順に並び替え

    #一つ目の選択
    if mincount == 1:
        a = 0
    else:
        a = random.randint(0,mincount-1)
    infos[a].count += 1
        
    #二つ目の選択
    if mincount == 1:
        plus = 1
    else:
        plus = 0
        
    #目的の物が得られるまで繰り返す
    counter = 0
    while(True):
        b = random.randint(0,num-1)
        already = False
        for e in infos[a].have:
            if e == infos[b].name:
                already = True
                break
        if a!=b and infos[b].count <= min + plus and already == False:
            infos[b].count += 1
            infos[a].have.append(infos[b].name)
            infos[b].have.append(infos[a].name)
            break
        
        #バグで無限ループになることを回避
            counter+=1
        if counter >= 5*num:
            print("二つ目の選択ミス")
            break
            
    return infos[a].name,infos[b].name


def select(namea,nameb,infos):
    """二つ選択肢から一つを選ばせ、レート計算をする関数"""
    for n in infos:
        if n.name == namea:
            infoa = n
        if n.name == nameb:
            infob = n
            
    print(str(infoa.name)+'  --- vs ---  '+str(infob.name))  
    print('lかrを入力してください')
    inp = input()
    if inp == "l":
        infoa.rate += 16 + 0.04*(infob.rate - infoa.rate)
        infob.rate -= 16 + 0.04*(infob.rate - infoa.rate)
    elif inp == "r":
        infob.rate += 16 + 0.04*(infoa.rate - infob.rate)
        infoa.rate -= 16 + 0.04*(infoa.rate - infob.rate)
    else:
        print("ノーレーティング")

def main_loop(infos,max=20):
    """2択の選択の処理を繰り替えす関数
        max: 最大試行回数(デフォルト20)
    """
    counter = 0
    while(True):
        counter+=1
        print(counter)
        a,b = choose2(infos)
        select(a,b,infos)
        
        min = 100000
        for i in infos:
            if i.count <= min:
                min = i.count
        if min == Get_len(infos)-1:
            break
        if counter >= max:
            break

実行

自分でリストを生成、もしくはデータの入ったエクセルファイルを選択し、実行します

ここでは実際にフルーツのデータをエクセルで作成して、フルーツランキングをつくりました

In [6]:
infos = []#データクラスのオブジェクトを格納するリスト

"""リストからデータを取得するとき
    datalist = ['りんご', 'バナナ', 'もも', 'オレンジ', 'なし', 'メロン', 'すいか', 'パイナップル', 'キウイ', 'グレープフルーツ', 'ブドウ', 'イチゴ']
    Add_from_list(infos,datalist)
"""

file_name = "excel_data/fruit.xlsx" #ファイルの相対パス
Add_from_excel(infos,file_name,sheetnum=1,colnum=1) #シート1の1列目

main_loop(infos,25) #最大で25回選択する

result = sorted(infos, key=lambda t:10000-t.rate)#レート順に並び替え

#結果を表示する
print("結果--------------------------------------")
print("順位:名前:レート:登場回数")
for i,d in enumerate(result):
    print(str(i+1) +":"+ str(d.name) + ":" + str(int(d.rate)) +":"+ str(d.count))
出力
['りんご', 'バナナ', 'もも', 'オレンジ', 'なし', 'メロン', 'すいか', 'パイナップル', 'キウイ', 'グレープフルーツ', 'ブドウ', 'イチゴ']のデータを追加しました
1
もも  --- vs ---  イチゴ
lかrを入力してください
l
2
パイナップル  --- vs ---  ブドウ
lかrを入力してください
l
3
メロン  --- vs ---  りんご
lかrを入力してください
r
"""省略"""
24
もも  --- vs ---  バナナ
lかrを入力してください
l
25
すいか  --- vs ---  グレープフルーツ
lかrを入力してください
l
結果--------------------------------------
順位:名前:レート:登場回数
1:もも:1559:4
2:りんご:1557:4
3:なし:1545:4
4:パイナップル:1545:4
5:ブドウ:1503:4
6:オレンジ:1502:4
7:メロン:1498:4
8:イチゴ:1486:4
9:すいか:1472:5
10:グレープフルーツ:1455:5
11:バナナ:1444:4
12:キウイ:1442:4

課題

  • 順位に関係なく出現回数を元に二つを抽出するので試行回数が多くなり、効率が悪い。
  • 途中で終了できるようにしないと飽きる
  • 二つ目の選択肢が条件を満たさないのが選ばれ続けると処理時間が長くなる
  • 打ち間違えたときのために一つ戻れるようにする必要がある

次回以降はこれを応用して2枚の画像から好きな方を選択するプログラムに改良していきます

 

コード(全部)

import random
import sys
import xlrd 

class DataInfo:
    """Part Index"""
    def __init__(self,name):
        self.name = name #名前
        self.rate = 1500 #レート
        self.count = 0   #呼び出し回数
        self.have = []   #既に呼び出された相手を格納するリスト


def Add_from_list(infos,data):
    """リストから格納"""
    for d in data:
        x = DataInfo(d)
        infos.append(x)
    
    print(str(data)+'のデータを追加しました')
    
def Add_from_excel(infos,filename,sheetnum=1,colnum=1):
    """エクセルファイルから格納
        @parameter
        filename: ファイル名
        sheetnum: エクセルのシートの番号(デフォルト1)
        colum   : 列の番号(デフォルト1)
    """
    wb = xlrd.open_workbook(file_name)
    sheet = wb.sheet_by_index(sheetnum - 1)
    col = sheet.col_values(colnum - 1)

    Add_from_list(infos,col)


def Get_len(infos):
    """クラスを含むリストの要素数を取得"""
    i = 0
    for info in infos:
        i+=1
    return i


def choose2(infos):
    """データクラスから二つを選択する関数"""
    num = Get_len(infos) #要素数
    
    #出現回数の最低値
    min = 100000
    for i in infos:
        if i.count <= min:
            min = i.count 
    
    #出現回数が最小のデータの個数
    mincount = 0        
    for i in infos:
        if i.count == min:
            mincount += 1 
            
    infos = sorted(infos, key=lambda t:t.count) #出現回数順に並び替え

    #一つ目の選択
    if mincount == 1:
        a = 0
    else:
        a = random.randint(0,mincount-1)
    infos[a].count += 1
        
    #二つ目の選択
    if mincount == 1:
        plus = 1
    else:
        plus = 0
        
    #目的の物が得られるまで繰り返す
    counter = 0
    while(True):
        b = random.randint(0,num-1)
        already = False
        for e in infos[a].have:
            if e == infos[b].name:
                already = True
                break
        if a!=b and infos[b].count <= min + plus and already == False:
            infos[b].count += 1
            infos[a].have.append(infos[b].name)
            infos[b].have.append(infos[a].name)
            break
        
        #バグで無限ループになることを回避
            counter+=1
        if counter >= 5*num:
            print("二つ目の選択ミス")
            break
            
    return infos[a].name,infos[b].name


def select(namea,nameb,infos):
    """二つ選択肢から一つを選ばせ、レート計算をする関数"""
    for n in infos:
        if n.name == namea:
            infoa = n
        if n.name == nameb:
            infob = n
            
    print(str(infoa.name)+'  --- vs ---  '+str(infob.name))  
    print('lかrを入力してください')
    inp = input()
    if inp == "l":
        infoa.rate += 16 + 0.04*(infob.rate - infoa.rate)
        infob.rate -= 16 + 0.04*(infob.rate - infoa.rate)
    elif inp == "r":
        infob.rate += 16 + 0.04*(infoa.rate - infob.rate)
        infoa.rate -= 16 + 0.04*(infoa.rate - infob.rate)
    else:
        print("ノーレーティング")

def main_loop(infos,max=20):
    """2択の選択の処理を繰り替えす関数
        max: 最大試行回数(デフォルト20)
    """
    counter = 0
    while(True):
        counter+=1
        print(counter)
        a,b = choose2(infos)
        select(a,b,infos)
        
        min = 100000
        for i in infos:
            if i.count <= min:
                min = i.count
        if min == Get_len(infos)-1:
            break
        if counter >= max:
            break


infos = []#データクラスのオブジェクトを格納するリスト

"""リストからデータを取得するとき
    datalist = ['りんご', 'バナナ', 'もも', 'オレンジ', 'なし', 'メロン', 'すいか', 'パイナップル', 'キウイ', 'グレープフルーツ', 'ブドウ', 'イチゴ']
    Add_from_list(infos,datalist)
"""

file_name = "excel_data/fruit.xlsx" #ファイルの相対パス
Add_from_excel(infos,file_name,sheetnum=1,colnum=1) #シート1の1列目

main_loop(infos,25) #最大で25回選択する

result = sorted(infos, key=lambda t:10000-t.rate)#レート順に並び替え

#結果を表示する
print("結果--------------------------------------")
print("順位:名前:レート:登場回数")
for i,d in enumerate(result):
    print(str(i+1) +":"+ str(d.name) + ":" + str(int(d.rate)) +":"+ str(d.count))