第137回【Python】Vtuber

現在取り組んでいるのは、paiza ラーニング問題集「クエリメニュー」になります。

はじめに

猫とキャンプと野球観戦と AWS が大好きな旦那、LeoSaki です。モフモフしたい。

Python をゼロから勉強してみよう、のコーナー 137 回目です。

自宅のリビングにいると、レオくんが必ず LeoSaki(旦那)の右隣りで転がります。LeoSaki(旦那)の左側がどんなに空いていても、必ず右隣に回り込んでいます。ちなみに、LeoSaki(旦那)の定位置の右側は小物机が置いてあり、狭いです。左側は何もないのに。

それでは、今日も頑張ってみようと思います。

Vtuber

あなたは流行に乗っかり、Vtuber としての活動をスタートしました。活動も軌道にのり、配信をするたびに視聴者が superchat を送ってくれたり、メンバーシップ制度に加入してくれるようになりました。
(わからない方は 「youtube superchat」「youtube membership」 などで検索してみてください。)
あなたはお礼として superchat を読むお礼配信をおこなうことにしました。
その配信で、前回の配信の superchat の総額が高いアカウントから順に、superchat をした全てのアカウントの名前を読んだ後、メンバーシップに入ってくれた全てのアカウントの名前を辞書順昇順で読むことにしました。
superchat の金額が同じ場合、同じ金額の中で辞書順降順でアカウント名を読むことにしました。
前回の配信の superchat とメンバーシップ加入の履歴が与えられるので、読む順番にアカウント名を出力するプログラムを作成してください。

N
E_1
...
E_N

・1 行目では、superchat とメンバーシップ加入の回数の和 N が与えられます。
・続く N 行のうち、 i 行目では、i 番目のイベントの内容 E_i が以下のいずれかの形式で与えれられます。

name give money !
name さんが money 円の superchat を送ったことを表す。

name join membership!
name さんがメンバーシップに加入したことを表す。


期待する出力

name_s_1
...
name_m_1
...

・superchat の総額が高い人から順にアカウント名を 1 行 1 アカウントで出力したのち、メンバーシップに加入した人のアカウント名を 辞書順に 1 行 1 アカウントで以上の形式で出力してください。
・superchat の総額が同じである場合、同じ金額の中で辞書順の降順(z → a)でアカウント名を出力してください。
・また、出力の末尾には改行を入れてください。


すべてのテストケースにおいて、以下の条件をみたします。

・1 ≦ N ≦ 100,000
・S_i は “name give money !" , “name join membership!" のいずれか
・name は長さ 20 文字以下の英数字からなる文字列
・100 ≦ money ≦ 50000
・アカウント名に重複はないことが保証されている。
・1 つのアカウントについて、メンバーシップに加入するイベントが複数回起こることはない。


入力例

5
aiueo give 2489 !
kk join membership!
coffee_addiction join membership!
so_cute give 837 !
yoyo give 9284 !

出力例

yoyo
aiueo
so_cute
coffee_addiction
kk

「youtube superchat」「youtube membership」 などで検索しないといけないのは誰か? LeoSaki(旦那)です。まったく興味がないので本気でわかりません。

Python
N = int(input())
dic = {}
s = set()

for _ in range(N):
    E = input().split()
    name = E[0]
    event = E[1]
    if event == 'give':
        money = int(E[2])
        if name in dic:
            dic[name] = (dic[name][0] + money,name)
        else:
            dic[name] = (money,name)
    else:
        s.add(name)

for n,m in sorted(dic.items(),key=lambda x:x[1],reverse=True):
    print(n)
    
for name in sorted(s):
    print(name)

複数のソートパターンはいろいろあるので、そのときに即した書き方を探る必要がありそう。できるだけ 1 回で終わるパターンを考えるのもまた楽し。

VBA
Sub query_primer__vtuber()

    N = Cells(1, 1)

    Dim superchat As Object, members As Object
    Set superchat = CreateObject("Scripting.Dictionary")
    Set members = CreateObject("Scripting.Dictionary")
    
    For i = 1 To N
        e = Split(Cells(i + 1, 1), " ")
        name_ = e(0)
        event_ = e(1)
        If event_ = "give" Then
            money = Val(e(2))
            If superchat.exists(name_) Then
                superchat(name_) = superchat(name_) + money
            Else
                superchat.Add name_, money
            End If
        Else
            members.Add name_, name_
        End If
    Next

    Dim arrList()    
    arrKeys = superchat.Keys
    ReDim arrList(superchat.Count - 1, 1)
    For j = LBound(arrKeys) To UBound(arrKeys)
        arrList(j, 0) = arrKeys(j)
        arrList(j, 1) = superchat(arrKeys(j))
    Next
    Call QuickSort_dic(arrList, LBound(arrList, 1), UBound(arrList, 1), True)
    superchat.RemoveAll
    For j = LBound(arrList) To UBound(arrList)
        superchat.Add arrList(j, 0), arrList(j, 1)
    Next
  
    arrKeys = superchat.Keys
    ReDim arrList(superchat.Count - 1, 1)
    For j = LBound(arrKeys) To UBound(arrKeys)
        arrList(j, 0) = superchat(arrKeys(j))
        arrList(j, 1) = arrKeys(j)
    Next
    Call QuickSort_num(arrList, LBound(arrList, 1), UBound(arrList, 1))
    superchat.RemoveAll
    For j = LBound(arrList) To UBound(arrList)
        superchat.Add arrList(j, 1), arrList(j, 0)
    Next
    
    For Each name_ In superchat
        Debug.Print name_
    Next

    arrKeys = members.Keys
    ReDim arrList(members.Count - 1, 1)
    For j = LBound(arrKeys) To UBound(arrKeys)
        arrList(j, 0) = arrKeys(j)
        arrList(j, 1) = members(arrKeys(j))
    Next
    Call QuickSort_dic(arrList, LBound(arrList, 1), UBound(arrList, 1), False)
    members.RemoveAll
    For j = LBound(arrList) To UBound(arrList)
        members.Add arrList(j, 0), arrList(j, 1)
    Next
    
    For Each name_ In members
        Debug.Print name_
    Next
    
End Sub

Private Sub QuickSort_dic(ByRef arrList() As Variant, ByVal minIDX As Long, ByVal maxIDX As Long, reverse As Boolean)
 
    Dim valMEDIAN As Variant
    Dim arrTEMP() As Variant
    Dim i As Long
    Dim j As Long
     
    'ソート範囲の上下限を設定
    i = minIDX
    j = maxIDX
     
    '基準値としてインデックスの中央値のデータを使用します。
    valMEDIAN = arrList(Int((minIDX + maxIDX) / 2), 0)
 
    Do
        If reverse Then
            'インデックスの小さい方から値を比較していく
            Do While StrComp(arrList(i, 0), valMEDIAN) > 0
                i = i + 1
            Loop
             
            'インデックスの大きい方から値を比較していく
            Do While StrComp(arrList(j, 0), valMEDIAN) < 0
                j = j - 1
            Loop
        Else
            'インデックスの小さい方から値を比較していく
            Do While StrComp(arrList(i, 0), valMEDIAN) < 0
                i = i + 1
            Loop
             
            'インデックスの大きい方から値を比較していく
            Do While StrComp(arrList(j, 0), valMEDIAN) > 0
                j = j - 1
            Loop
        End If
        
         
        'インデックスが逆転したらループ抜け
        If i >= j Then Exit Do
         
        '配列を定義
        ReDim arrTEMP(0, 1)
         
        '配列内のデータの入れ替え1
        arrTEMP(0, 0) = arrList(i, 0)
        arrList(i, 0) = arrList(j, 0)
        arrList(j, 0) = arrTEMP(0, 0)
         
        '配列内のデータの入れ替え2
        arrTEMP(0, 1) = arrList(i, 1)
        arrList(i, 1) = arrList(j, 1)
        arrList(j, 1) = arrTEMP(0, 1)
         
        '配列の初期化
        Erase arrTEMP
         
        'インデックスの加減算
        i = i + 1
        j = j - 1
    Loop
     
    '再帰でソートが完了するまで繰り返し
    If (minIDX < i - 1) Then Call QuickSort_dic(arrList, minIDX, i - 1, reverse)
    If (maxIDX > j + 1) Then Call QuickSort_dic(arrList, j + 1, maxIDX, reverse)
     
End Sub

Private Sub QuickSort_num(ByRef arrList() As Variant, ByVal minIDX As Long, ByVal maxIDX As Long)
 
    Dim valMEDIAN As Variant
    Dim arrTEMP() As Variant
    Dim i As Long
    Dim j As Long
     
    'ソート範囲の上下限を設定
    i = minIDX
    j = maxIDX
     
    '基準値としてインデックスの中央値のデータを使用します。
    valMEDIAN = arrList(Int((minIDX + maxIDX) / 2), 0)
 
    Do
        'インデックスの小さい方から値を比較していく
        Do While arrList(i, 0) > valMEDIAN
            i = i + 1
        Loop
         
        'インデックスの大きい方から値を比較していく
        Do While arrList(j, 0) < valMEDIAN
            j = j - 1
        Loop
         
        'インデックスが逆転したらループ抜け
        If i >= j Then Exit Do
         
        '配列を定義
        ReDim arrTEMP(0, 1)
         
        '配列内のデータの入れ替え1
        arrTEMP(0, 0) = arrList(i, 0)
        arrList(i, 0) = arrList(j, 0)
        arrList(j, 0) = arrTEMP(0, 0)
         
        '配列内のデータの入れ替え2
        arrTEMP(0, 1) = arrList(i, 1)
        arrList(i, 1) = arrList(j, 1)
        arrList(j, 1) = arrTEMP(0, 1)
         
        '配列の初期化
        Erase arrTEMP
         
        'インデックスの加減算
        i = i + 1
        j = j - 1
    Loop
     
    '再帰でソートが完了するまで繰り返し
    If (minIDX < i - 1) Then Call QuickSort_num(arrList, minIDX, i - 1)
    If (maxIDX > j + 1) Then Call QuickSort_num(arrList, j + 1, maxIDX)
     
End Sub

いつも利用している QuickSort 関数を降順対応させました。Python 同様、reverse の引数に True を渡してあげると、降順になります。先に名前を降順に並べ替えた後、金額を降順に並べ替える方法で、問題の要件を達成しました。なかなか面倒なコードになっています。

こちらもいつもの如く、paiza が用意しているほぼ境界値データでテストしたところ、約 57 秒で完了しました。Debug.Print をしなければ、約 1.6 秒でした。

最後に

Python の方は、思いついたことを簡単に実現できることが多いです。VBA の方は、かなり苦労しました。複数キーで並べ替えするときは、複数キーを繋げる方法が一番簡単だと思っているのですが、今回はキーがどんどん変わってしまうので、その手は使えない。

結局、複数回ソートを繰り返す方法になってしまったので実行時間が不安だったのですが、そこそこ速度は出ているので、問題はない気がします。もっと早い方法がありましたら、教えていただけますと幸いです。

引き続き、よろしくお願いいたします。

Pythonpaiza,学習,Python

Posted by LeoSaki