libs.utils.formatter

libs/utils/formatter.py

  1"""
  2libs/utils/formatter.py
  3"""
  4
  5import random
  6import re
  7
  8import pandas as pd
  9
 10import libs.global_value as g
 11from libs.types import StyleOptions
 12from libs.utils import textutil
 13
 14
 15def floatfmt_adjust(df: pd.DataFrame, index: bool = False) -> list[str]:
 16    """
 17    カラム名に応じたfloatfmtのリストを返す
 18
 19    Args:
 20        df (pd.DataFrame): チェックするデータ
 21        index (bool, optional): リストにIndexを含める. Defaults to False.
 22
 23    Returns:
 24        list[str]: floatfmtに指定するリスト
 25
 26    """
 27    fmt: list[str] = []
 28    if df.empty:
 29        return fmt
 30
 31    field: list[str] = df.columns.tolist()
 32    if index:
 33        field.insert(0, str(df.index.name))
 34
 35    for x in field:
 36        match x:
 37            case v if v.endswith("_rate") or v.endswith("率") or v.endswith("(%)"):
 38                fmt.append(".2%")
 39            case v if v.endswith("_count"):
 40                fmt.append(".0f")
 41            case "ゲーム数" | "win" | "lose" | "draw" | "top2" | "top3":
 42                fmt.append(".0f")
 43            case "通算" | "通算ポイント" | "point_sum":
 44                fmt.append("+.1f")
 45            case "平均" | "平均ポイント" | "point_avg" | "平均収支" | "区間ポイント" | "区間平均":
 46                fmt.append("+.1f")
 47            case "1位(ポイント)" | "2位(ポイント)" | "3位(ポイント)" | "4位(ポイント)" | "5位(ポイント)":
 48                fmt.append("+.1f")
 49            case "1st" | "2nd" | "3rd" | "4th" | "1位" | "2位" | "3位" | "4位" | "rank1" | "rank2" | "rank3" | "rank4":
 50                fmt.append(".0f")
 51            case "トビ" | "flying":
 52                fmt.append(".0f")
 53            case "平均順位" | "平順" | "rank_avg":
 54                fmt.append(".2f")
 55            case "順位差" | "トップ差" | "平均素点":
 56                fmt.append(".1f")
 57            case "rpoint_max" | "rpoint_min" | "rpoint_mean":
 58                fmt.append(".0f")
 59            case _:
 60                fmt.append("")
 61
 62    return fmt
 63
 64
 65def column_alignment(df: pd.DataFrame, header: bool = False, index: bool = False) -> list[str]:
 66    """
 67    カラム位置
 68
 69    Args:
 70        df (pd.DataFrame): チェックするデータ
 71        header (bool, optional): ヘッダを対象にする
 72        index (bool, optional): リストにIndexを含める. Defaults to False.
 73
 74    Returns:
 75        list[str]: colalignに指定するリスト
 76
 77    """
 78    fmt: list[str] = []  # global, right, center, left, decimal, None
 79    if df.empty:
 80        return fmt
 81
 82    field: list[str] = df.columns.tolist()
 83    if index:
 84        field.insert(0, str(df.index.name))
 85
 86    if header:  # ヘッダ(すべて左寄せ)
 87        fmt = ["left"] * len(field)
 88    else:
 89        for x in field:
 90            match x:
 91                case "日時" | "playtime":
 92                    fmt.append("left")
 93                case "プレイヤー名" | "name" | "team" | "player":
 94                    fmt.append("left")
 95                case "内容" | "和了役" | "matter":
 96                    fmt.append("left")
 97                case "段位" | "grade":
 98                    fmt.append("left")
 99                case "順位分布" | "rank_distr" | "rank_distr4":
100                    fmt.append("left")
101                case "平均順位" | "rank_avg":
102                    fmt.append("center")
103                case _:
104                    fmt.append("right")
105
106    return fmt
107
108
109def name_replace(target: str, add_mark: bool = False, not_replace: bool = False) -> str:
110    """
111    表記ブレ修正(正規化)
112
113    Args:
114        target (str): 対象プレイヤー名
115        add_mark (bool, optional): ゲストマークを付与する. Defaults to False.
116        not_replace (bool, optional): ゲスト置換なし(強制/個人戦) Defaults to False.
117          - *True*: ゲストを置換しない
118          - *False*: ゲストを置換する
119
120    Returns:
121        str: 表記ブレ修正後のプレイヤー名
122
123    """
124    chk_pattern = [
125        target,  # 無加工
126        textutil.str_conv(target, textutil.ConversionType.HtoZ),  # 半角 -> 全角
127        textutil.str_conv(target, textutil.ConversionType.KtoH),  # カタ -> ひら
128        textutil.str_conv(target, textutil.ConversionType.HtoK),  # ひら -> カタ
129        honor_remove(target),  # 敬称削除
130        honor_remove(textutil.str_conv(target, textutil.ConversionType.HtoZ)),  # 敬称削除 + 半角 -> 全角
131        honor_remove(textutil.str_conv(target, textutil.ConversionType.KtoH)),  # 敬称削除 + カタ -> ひら
132        honor_remove(textutil.str_conv(target, textutil.ConversionType.HtoK)),  # 敬称削除 + ひら -> カタ
133    ]
134    chk_pattern = sorted(set(chk_pattern), key=chk_pattern.index)  # 順序を維持したまま重複排除
135
136    if g.params.individual or not_replace:
137        for name in chk_pattern:
138            if name in g.cfg.member.lists:  # メンバーリスト
139                return name
140            if name in g.cfg.member.all_lists:  # 別名を含むリスト
141                if ret_name := g.cfg.member.resolve_name(name):
142                    return ret_name
143    else:
144        for team in chk_pattern:
145            if team in g.cfg.team.lists:  # チームリスト
146                return team
147
148    # リストに見つからない場合
149    name = honor_remove(target)
150    if g.params.unregistered_replace and not not_replace:
151        name = g.cfg.member.guest_name
152    if name != g.cfg.member.guest_name and add_mark:
153        name = f"{name}({g.cfg.setting.guest_mark})"
154
155    return name
156
157
158def honor_remove(name: str) -> str:
159    """
160    敬称削除
161
162    Args:
163        name (str): 対象の名前
164
165    Returns:
166        str: 敬称を削除した名前
167
168    """
169    honor = r"(くん|さん|ちゃん|クン|サン|チャン|君)$"
170    if re.match(rf".*{honor}", name):
171        if not re.match(rf".*(っ|ッ|ー){honor}", name):
172            name = re.sub(rf"{honor}", "", name)
173
174    return name
175
176
177def anonymous_mapping(name_list: list[str], initial: int = 0) -> dict[str, str]:
178    """
179    名前リストから変換用辞書を生成
180
181    Args:
182        name_list (list[str]): 名前リスト
183        initial (int, optional): インデックス初期値. Defaults to 0.
184
185    Returns:
186        dict[str, str]: マッピング用辞書
187
188    """
189    ret: dict[str, str] = {}
190
191    if g.params.individual:
192        prefix = "Player"
193        id_list = {x["name"]: x["id"] for x in g.cfg.member.info}
194    else:
195        prefix = "Team"
196        id_list = {x["team"]: x["id"] for x in g.cfg.team.info}
197
198    if len(name_list) == 1:
199        name = name_list[0]
200        if name in id_list:
201            idx = id_list[name]
202        else:
203            idx = int(random.random() * 100 + 100)
204        ret[name] = f"{prefix}_{idx + initial:03d}"
205    else:
206        random.shuffle(name_list)
207        for idx, name in enumerate(name_list):
208            ret[name] = f"{prefix}_{idx + initial:03d}"
209
210    return ret
211
212
213def df_rename(df: pd.DataFrame, options: StyleOptions) -> pd.DataFrame:
214    """
215    カラム名をリネームする
216
217    Args:
218        df (pd.DataFrame): 対象データフレーム
219        options (StyleOptions): 変換モード
220
221    Returns:
222        pd.DataFrame: リネーム後のデータフレーム
223
224    """
225    rename_dict: dict[str, str] = {
226        #
227        "p1": "東家",
228        "p2": "南家",
229        "p3": "西家",
230        "p4": "北家",
231        "alias": "別名",
232        "members": "所属メンバー",
233        "last_update": "最終更新日",
234        "elapsed_day": "経過日数",
235        #
236        "playtime": "日時",
237        "rate": "レート",
238        "participation_rate": "ゲーム参加率",
239        "total_count": "集計ゲーム数",
240        "matter_count": "回数",
241        "ex_total": "ポイント合計",
242        "deposit": "供託",
243        "comment": "コメント",
244        "source": "入力元",
245        "rule_version": "ルール識別子",
246        "war_record": "戦績(勝-敗-分)",
247        #
248        "rpoint": "素点",
249        "rpoint_avg": "平均素点",
250        "balance_avg": "平均収支",
251        "point_dev": "得点偏差",
252        "rank_dev": "順位偏差",
253        "grade": "段位",
254        #
255        "rank1_rate-count": "1位率(回)",
256        "rank1_rate": "1位率",
257        "rank2_rate-count": "2位率(回)",
258        "rank2_rate": "2位率",
259        "rank3_rate-count": "3位率(回)",
260        "rank3_rate": "3位率",
261        "rank4_rate-count": "4位率(回)",
262        "rank4_rate": "4位率",
263        "top2_rate-count": "連対率(回)",
264        "top2_rate": "連対率",
265        "top2": "連対数",
266        "top3_rate-count": "ラス回避率(回)",
267        "top3_rate": "ラス回避率",
268        "top3": "ラス回避数",
269        "flying_rate-count": "トビ率(回)",
270        "flying_rate": "トビ率",
271        "flying_count": "トビ数",
272        "yakuman_rate-count": "役満和了率(回)",
273        # 収支
274        "avg_balance": "平均収支",
275        "top2_balance": "連対収支",
276        "lose2_balance": "逆連対収支",
277        "rank1_balance": "1位収支",
278        "rank2_balance": "2位収支",
279        "rank3_balance": "3位収支",
280        "rank4_balance": "4位収支",
281        # レコード
282        "top1_max": "連続トップ",
283        "top2_max": "連続連対",
284        "top3_max": "連続ラス回避",
285        "lose2_max": "連続トップなし",
286        "lose3_max": "連続逆連対",
287        "lose4_max": "連続ラス",
288        "point_max": "最大獲得ポイント",
289        "point_min": "最小獲得ポイント",
290        "rpoint_max": "最大素点",
291        "rpoint_min": "最小素点",
292        # 直接対決
293        "results": "対戦結果",
294        "win%": "勝率",
295        "my_point_sum": "獲得ポイント(自分)",
296        "my_point_avg": "平均ポイント(自分)",
297        "vs_point_sum": "獲得ポイント(相手)",
298        "vs_point_avg": "平均ポイント(相手)",
299        "my_rpoint_avg": "平均素点(自分)",
300        "my_rank_avg": "平均順位(自分)",
301        "my_rank_distr": "順位分布(自分)",
302        "vs_rpoint_avg": "平均素点(相手)",
303        "vs_rank_avg": "平均順位(相手)",
304        "vs_rank_distr": "順位分布(相手)",
305        #
306        "p1_name": "東家 名前",
307        "p2_name": "南家 名前",
308        "p3_name": "西家 名前",
309        "p4_name": "北家 名前",
310        "p1_yakuman": "東家 メモ",
311        "p2_yakuman": "南家 メモ",
312        "p3_yakuman": "西家 メモ",
313        "p4_yakuman": "北家 メモ",
314        "p1_remarks": "東家 メモ",
315        "p2_remarks": "南家 メモ",
316        "p3_remarks": "西家 メモ",
317        "p4_remarks": "北家 メモ",
318        "p1_rpoint": "東家 素点",
319        "p2_rpoint": "南家 素点",
320        "p3_rpoint": "西家 素点",
321        "p4_rpoint": "北家 素点",
322        "p1_rank": "東家 順位",
323        "p2_rank": "南家 順位",
324        "p3_rank": "西家 順位",
325        "p4_rank": "北家 順位",
326        "p1_point": "東家 ポイント",
327        "p2_point": "南家 ポイント",
328        "p3_point": "西家 ポイント",
329        "p4_point": "北家 ポイント",
330        "p1_str": "東家 入力素点",
331        "p2_str": "南家 入力素点",
332        "p3_str": "西家 入力素点",
333        "p4_str": "北家 入力素点",
334        # レポート - 上位成績
335        "collection": "集計月",
336        "name1": "1位(名前)",
337        "point1": "1位(ポイント)",
338        "name2": "2位(名前)",
339        "point2": "2位(ポイント)",
340        "name3": "3位(名前)",
341        "point3": "3位(ポイント)",
342        "name4": "4位(名前)",
343        "point4": "4位(ポイント)",
344        "name5": "5位(名前)",
345        "point5": "5位(ポイント)",
346        # メモ
347        "regulation": "卓外清算",
348        "remarks": "メモ",
349        #
350        "memo": "備考",
351    }
352
353    match options.rename_type:
354        case StyleOptions.RenameType.NONE:
355            return df
356        case StyleOptions.RenameType.NORMAL:
357            short = False
358        case StyleOptions.RenameType.SHORT:
359            short = True
360
361    for x in df.columns:
362        match x:
363            case "rank":
364                rename_dict[x] = "#" if short else "順位"
365            case "name" | "player":
366                rename_dict[x] = "名前" if short else "プレイヤー名"
367            case "team":
368                rename_dict[x] = "チーム" if short else "チーム名"
369            case "point":
370                rename_dict[x] = "ポイント" if short else "獲得ポイント"
371            case "seat":
372                rename_dict[x] = "席" if short else "座席"
373            case "count" | "game" | "game_count":
374                rename_dict[x] = "ゲーム数"
375            case "pt_total" | "total_point" | "point_sum" | "total_mix":
376                rename_dict[x] = "通算" if short else "通算ポイント"
377            case "pt_avg" | "avg_point" | "point_avg" | "avg_mix":
378                rename_dict[x] = "平均" if short else "平均ポイント"
379            case "ex_point":
380                rename_dict[x] = "ポイント" if short else "卓外ポイント"
381            case "rank_distr" | "rank_distr1" | "rank_distr2" | "rank_distr3" | "rank_distr4":
382                rename_dict[x] = "順位分布"
383            case "rank_avg":
384                rename_dict[x] = "平順" if short else "平均順位"
385            case "1st" | "rank1" | "1st_count" | "1st_mix":
386                rename_dict[x] = "1位数"
387            case "2nd" | "rank2" | "2nd_count" | "2nd_mix":
388                rename_dict[x] = "2位数"
389            case "3rd" | "rank3" | "3rd_count" | "3rd_mix":
390                rename_dict[x] = "3位数"
391            case "4th" | "rank4" | "4th_count" | "4th_mix":
392                rename_dict[x] = "4位数"
393            case "flying" | "flying_mix":
394                rename_dict[x] = "飛" if short else "トビ"
395            case "pt_diff":
396                rename_dict[x] = "差分"
397            case "diff_from_above":
398                rename_dict[x] = "順位差"
399            case "diff_from_top":
400                rename_dict[x] = "トップ差"
401            case "yakuman_mix":
402                rename_dict[x] = "役満和了"
403            case "yakuman_count" | "yakuman":
404                rename_dict[x] = "役満和了数"
405            case "yakuman_rate":
406                rename_dict[x] = "役満和了率"
407            case "win":
408                rename_dict[x] = "勝" if short else "勝ち"
409            case "lose":
410                rename_dict[x] = "負" if short else "負け"
411            case "draw":
412                rename_dict[x] = "分" if short else "引き分け"
413            case "matter":
414                match options.data_kind:
415                    case StyleOptions.DataKind.REMARKS_YAKUMAN:
416                        rename_dict[x] = "和了役"
417                    case StyleOptions.DataKind.REMARKS_REGULATION | StyleOptions.DataKind.REMARKS_OTHER:
418                        rename_dict[x] = "内容"
419                    case _:
420                        rename_dict[x] = "内容"
421
422    if not g.params.individual:
423        rename_dict.update(name="チーム" if short else "チーム名")
424
425    return df.rename(columns=rename_dict)
426
427
428def df_drop(df: pd.DataFrame, drop_items: list[str]) -> pd.DataFrame:
429    """
430    非表示項目をドロップ
431
432    Args:
433        df (pd.DataFrame): ターゲット
434        drop_items (list): 非表示項目
435
436    Returns:
437        pd.DataFrame: 加工後
438
439    """
440    original = df.columns.to_list()
441    columns = df_rename(df, StyleOptions(rename_type=StyleOptions.RenameType.NORMAL)).columns.to_list()  # カラム名変換
442    position = [columns.index(item) for item in drop_items if item in columns]
443    df.drop(columns=[original[x] for x in position], inplace=True)
444
445    return df
446
447
448def group_strings(lines: list[str], limit: int = 3000) -> list[str]:
449    """
450    指定文字数まで改行で連結
451
452    Args:
453        lines (list[str]): 連結対象
454        limit (int, optional): 制限値. Defaults to 3000.
455
456    Returns:
457        list[str]: 連結結果
458
459    """
460    result: list[str] = []
461    buffer: list[str] = []
462
463    for i, line in enumerate(lines):
464        is_last = i == len(lines) - 1  # 最終ブロック判定
465        max_char = limit * 1.5 if is_last else limit  # 1ブロックの最大値
466
467        # 仮に追加したときの文字列長を計算
468        temp = buffer + [line]
469        total_len = len("".join(temp))
470
471        if total_len <= max_char:
472            buffer.append(line)
473        else:
474            if buffer:
475                result.append("\n".join(buffer))
476            buffer = [line]
477
478    if buffer:
479        result.append("\n".join(buffer))
480
481    # 改行の集約
482    result = [str(x).replace("\n```\n\n```\n", "\n```\n```\n") for x in result]
483    result = [str(x).replace("\n\n\t", "\n\t") for x in result]
484
485    return result
486
487
488def split_strings(msg: str, limit: int = 3000) -> list[str]:
489    """
490    指定文字数で分割
491
492    Args:
493        msg (str): 分割対象
494        limit (int, optional): 分割文字数. Defaults to 3000.
495
496    Returns:
497        list[str]: 分割結果
498
499    """
500    result: list[str] = []
501    buffer: list[str] = []
502    codeblock: bool = False
503
504    for line in msg.splitlines(keepends=True):
505        # 仮に追加したときの文字列長を計算
506        temp = buffer + [line]
507        total_len = len("".join(temp))
508
509        if total_len < limit:
510            buffer.append(line)
511            if line != line.replace("```", ""):  # 1行でopen/closeされる想定ではない
512                codeblock = not (codeblock)
513        else:
514            if buffer:
515                if codeblock:  # codeblock open状態
516                    buffer.append("```\n")
517                    result.append("".join(buffer))
518                    buffer = [f"```\n{line}"]
519                else:
520                    result.append("".join(buffer))
521                    buffer = [line]
522
523    if result:
524        return result
525    return [msg]
def floatfmt_adjust(df: pandas.DataFrame, index: bool = False) -> list[str]:
16def floatfmt_adjust(df: pd.DataFrame, index: bool = False) -> list[str]:
17    """
18    カラム名に応じたfloatfmtのリストを返す
19
20    Args:
21        df (pd.DataFrame): チェックするデータ
22        index (bool, optional): リストにIndexを含める. Defaults to False.
23
24    Returns:
25        list[str]: floatfmtに指定するリスト
26
27    """
28    fmt: list[str] = []
29    if df.empty:
30        return fmt
31
32    field: list[str] = df.columns.tolist()
33    if index:
34        field.insert(0, str(df.index.name))
35
36    for x in field:
37        match x:
38            case v if v.endswith("_rate") or v.endswith("率") or v.endswith("(%)"):
39                fmt.append(".2%")
40            case v if v.endswith("_count"):
41                fmt.append(".0f")
42            case "ゲーム数" | "win" | "lose" | "draw" | "top2" | "top3":
43                fmt.append(".0f")
44            case "通算" | "通算ポイント" | "point_sum":
45                fmt.append("+.1f")
46            case "平均" | "平均ポイント" | "point_avg" | "平均収支" | "区間ポイント" | "区間平均":
47                fmt.append("+.1f")
48            case "1位(ポイント)" | "2位(ポイント)" | "3位(ポイント)" | "4位(ポイント)" | "5位(ポイント)":
49                fmt.append("+.1f")
50            case "1st" | "2nd" | "3rd" | "4th" | "1位" | "2位" | "3位" | "4位" | "rank1" | "rank2" | "rank3" | "rank4":
51                fmt.append(".0f")
52            case "トビ" | "flying":
53                fmt.append(".0f")
54            case "平均順位" | "平順" | "rank_avg":
55                fmt.append(".2f")
56            case "順位差" | "トップ差" | "平均素点":
57                fmt.append(".1f")
58            case "rpoint_max" | "rpoint_min" | "rpoint_mean":
59                fmt.append(".0f")
60            case _:
61                fmt.append("")
62
63    return fmt

カラム名に応じたfloatfmtのリストを返す

Arguments:
  • df (pd.DataFrame): チェックするデータ
  • index (bool, optional): リストにIndexを含める. Defaults to False.
Returns:

list[str]: floatfmtに指定するリスト

def column_alignment( df: pandas.DataFrame, header: bool = False, index: bool = False) -> list[str]:
 66def column_alignment(df: pd.DataFrame, header: bool = False, index: bool = False) -> list[str]:
 67    """
 68    カラム位置
 69
 70    Args:
 71        df (pd.DataFrame): チェックするデータ
 72        header (bool, optional): ヘッダを対象にする
 73        index (bool, optional): リストにIndexを含める. Defaults to False.
 74
 75    Returns:
 76        list[str]: colalignに指定するリスト
 77
 78    """
 79    fmt: list[str] = []  # global, right, center, left, decimal, None
 80    if df.empty:
 81        return fmt
 82
 83    field: list[str] = df.columns.tolist()
 84    if index:
 85        field.insert(0, str(df.index.name))
 86
 87    if header:  # ヘッダ(すべて左寄せ)
 88        fmt = ["left"] * len(field)
 89    else:
 90        for x in field:
 91            match x:
 92                case "日時" | "playtime":
 93                    fmt.append("left")
 94                case "プレイヤー名" | "name" | "team" | "player":
 95                    fmt.append("left")
 96                case "内容" | "和了役" | "matter":
 97                    fmt.append("left")
 98                case "段位" | "grade":
 99                    fmt.append("left")
100                case "順位分布" | "rank_distr" | "rank_distr4":
101                    fmt.append("left")
102                case "平均順位" | "rank_avg":
103                    fmt.append("center")
104                case _:
105                    fmt.append("right")
106
107    return fmt

カラム位置

Arguments:
  • df (pd.DataFrame): チェックするデータ
  • header (bool, optional): ヘッダを対象にする
  • index (bool, optional): リストにIndexを含める. Defaults to False.
Returns:

list[str]: colalignに指定するリスト

def name_replace(target: str, add_mark: bool = False, not_replace: bool = False) -> str:
110def name_replace(target: str, add_mark: bool = False, not_replace: bool = False) -> str:
111    """
112    表記ブレ修正(正規化)
113
114    Args:
115        target (str): 対象プレイヤー名
116        add_mark (bool, optional): ゲストマークを付与する. Defaults to False.
117        not_replace (bool, optional): ゲスト置換なし(強制/個人戦) Defaults to False.
118          - *True*: ゲストを置換しない
119          - *False*: ゲストを置換する
120
121    Returns:
122        str: 表記ブレ修正後のプレイヤー名
123
124    """
125    chk_pattern = [
126        target,  # 無加工
127        textutil.str_conv(target, textutil.ConversionType.HtoZ),  # 半角 -> 全角
128        textutil.str_conv(target, textutil.ConversionType.KtoH),  # カタ -> ひら
129        textutil.str_conv(target, textutil.ConversionType.HtoK),  # ひら -> カタ
130        honor_remove(target),  # 敬称削除
131        honor_remove(textutil.str_conv(target, textutil.ConversionType.HtoZ)),  # 敬称削除 + 半角 -> 全角
132        honor_remove(textutil.str_conv(target, textutil.ConversionType.KtoH)),  # 敬称削除 + カタ -> ひら
133        honor_remove(textutil.str_conv(target, textutil.ConversionType.HtoK)),  # 敬称削除 + ひら -> カタ
134    ]
135    chk_pattern = sorted(set(chk_pattern), key=chk_pattern.index)  # 順序を維持したまま重複排除
136
137    if g.params.individual or not_replace:
138        for name in chk_pattern:
139            if name in g.cfg.member.lists:  # メンバーリスト
140                return name
141            if name in g.cfg.member.all_lists:  # 別名を含むリスト
142                if ret_name := g.cfg.member.resolve_name(name):
143                    return ret_name
144    else:
145        for team in chk_pattern:
146            if team in g.cfg.team.lists:  # チームリスト
147                return team
148
149    # リストに見つからない場合
150    name = honor_remove(target)
151    if g.params.unregistered_replace and not not_replace:
152        name = g.cfg.member.guest_name
153    if name != g.cfg.member.guest_name and add_mark:
154        name = f"{name}({g.cfg.setting.guest_mark})"
155
156    return name

表記ブレ修正(正規化)

Arguments:
  • target (str): 対象プレイヤー名
  • add_mark (bool, optional): ゲストマークを付与する. Defaults to False.
  • not_replace (bool, optional): ゲスト置換なし(強制/個人戦) Defaults to False.
    • True: ゲストを置換しない
    • False: ゲストを置換する
Returns:

str: 表記ブレ修正後のプレイヤー名

def honor_remove(name: str) -> str:
159def honor_remove(name: str) -> str:
160    """
161    敬称削除
162
163    Args:
164        name (str): 対象の名前
165
166    Returns:
167        str: 敬称を削除した名前
168
169    """
170    honor = r"(くん|さん|ちゃん|クン|サン|チャン|君)$"
171    if re.match(rf".*{honor}", name):
172        if not re.match(rf".*(っ|ッ|ー){honor}", name):
173            name = re.sub(rf"{honor}", "", name)
174
175    return name

敬称削除

Arguments:
  • name (str): 対象の名前
Returns:

str: 敬称を削除した名前

def anonymous_mapping(name_list: list[str], initial: int = 0) -> dict[str, str]:
178def anonymous_mapping(name_list: list[str], initial: int = 0) -> dict[str, str]:
179    """
180    名前リストから変換用辞書を生成
181
182    Args:
183        name_list (list[str]): 名前リスト
184        initial (int, optional): インデックス初期値. Defaults to 0.
185
186    Returns:
187        dict[str, str]: マッピング用辞書
188
189    """
190    ret: dict[str, str] = {}
191
192    if g.params.individual:
193        prefix = "Player"
194        id_list = {x["name"]: x["id"] for x in g.cfg.member.info}
195    else:
196        prefix = "Team"
197        id_list = {x["team"]: x["id"] for x in g.cfg.team.info}
198
199    if len(name_list) == 1:
200        name = name_list[0]
201        if name in id_list:
202            idx = id_list[name]
203        else:
204            idx = int(random.random() * 100 + 100)
205        ret[name] = f"{prefix}_{idx + initial:03d}"
206    else:
207        random.shuffle(name_list)
208        for idx, name in enumerate(name_list):
209            ret[name] = f"{prefix}_{idx + initial:03d}"
210
211    return ret

名前リストから変換用辞書を生成

Arguments:
  • name_list (list[str]): 名前リスト
  • initial (int, optional): インデックス初期値. Defaults to 0.
Returns:

dict[str, str]: マッピング用辞書

def df_rename( df: pandas.DataFrame, options: libs.types.StyleOptions) -> pandas.DataFrame:
214def df_rename(df: pd.DataFrame, options: StyleOptions) -> pd.DataFrame:
215    """
216    カラム名をリネームする
217
218    Args:
219        df (pd.DataFrame): 対象データフレーム
220        options (StyleOptions): 変換モード
221
222    Returns:
223        pd.DataFrame: リネーム後のデータフレーム
224
225    """
226    rename_dict: dict[str, str] = {
227        #
228        "p1": "東家",
229        "p2": "南家",
230        "p3": "西家",
231        "p4": "北家",
232        "alias": "別名",
233        "members": "所属メンバー",
234        "last_update": "最終更新日",
235        "elapsed_day": "経過日数",
236        #
237        "playtime": "日時",
238        "rate": "レート",
239        "participation_rate": "ゲーム参加率",
240        "total_count": "集計ゲーム数",
241        "matter_count": "回数",
242        "ex_total": "ポイント合計",
243        "deposit": "供託",
244        "comment": "コメント",
245        "source": "入力元",
246        "rule_version": "ルール識別子",
247        "war_record": "戦績(勝-敗-分)",
248        #
249        "rpoint": "素点",
250        "rpoint_avg": "平均素点",
251        "balance_avg": "平均収支",
252        "point_dev": "得点偏差",
253        "rank_dev": "順位偏差",
254        "grade": "段位",
255        #
256        "rank1_rate-count": "1位率(回)",
257        "rank1_rate": "1位率",
258        "rank2_rate-count": "2位率(回)",
259        "rank2_rate": "2位率",
260        "rank3_rate-count": "3位率(回)",
261        "rank3_rate": "3位率",
262        "rank4_rate-count": "4位率(回)",
263        "rank4_rate": "4位率",
264        "top2_rate-count": "連対率(回)",
265        "top2_rate": "連対率",
266        "top2": "連対数",
267        "top3_rate-count": "ラス回避率(回)",
268        "top3_rate": "ラス回避率",
269        "top3": "ラス回避数",
270        "flying_rate-count": "トビ率(回)",
271        "flying_rate": "トビ率",
272        "flying_count": "トビ数",
273        "yakuman_rate-count": "役満和了率(回)",
274        # 収支
275        "avg_balance": "平均収支",
276        "top2_balance": "連対収支",
277        "lose2_balance": "逆連対収支",
278        "rank1_balance": "1位収支",
279        "rank2_balance": "2位収支",
280        "rank3_balance": "3位収支",
281        "rank4_balance": "4位収支",
282        # レコード
283        "top1_max": "連続トップ",
284        "top2_max": "連続連対",
285        "top3_max": "連続ラス回避",
286        "lose2_max": "連続トップなし",
287        "lose3_max": "連続逆連対",
288        "lose4_max": "連続ラス",
289        "point_max": "最大獲得ポイント",
290        "point_min": "最小獲得ポイント",
291        "rpoint_max": "最大素点",
292        "rpoint_min": "最小素点",
293        # 直接対決
294        "results": "対戦結果",
295        "win%": "勝率",
296        "my_point_sum": "獲得ポイント(自分)",
297        "my_point_avg": "平均ポイント(自分)",
298        "vs_point_sum": "獲得ポイント(相手)",
299        "vs_point_avg": "平均ポイント(相手)",
300        "my_rpoint_avg": "平均素点(自分)",
301        "my_rank_avg": "平均順位(自分)",
302        "my_rank_distr": "順位分布(自分)",
303        "vs_rpoint_avg": "平均素点(相手)",
304        "vs_rank_avg": "平均順位(相手)",
305        "vs_rank_distr": "順位分布(相手)",
306        #
307        "p1_name": "東家 名前",
308        "p2_name": "南家 名前",
309        "p3_name": "西家 名前",
310        "p4_name": "北家 名前",
311        "p1_yakuman": "東家 メモ",
312        "p2_yakuman": "南家 メモ",
313        "p3_yakuman": "西家 メモ",
314        "p4_yakuman": "北家 メモ",
315        "p1_remarks": "東家 メモ",
316        "p2_remarks": "南家 メモ",
317        "p3_remarks": "西家 メモ",
318        "p4_remarks": "北家 メモ",
319        "p1_rpoint": "東家 素点",
320        "p2_rpoint": "南家 素点",
321        "p3_rpoint": "西家 素点",
322        "p4_rpoint": "北家 素点",
323        "p1_rank": "東家 順位",
324        "p2_rank": "南家 順位",
325        "p3_rank": "西家 順位",
326        "p4_rank": "北家 順位",
327        "p1_point": "東家 ポイント",
328        "p2_point": "南家 ポイント",
329        "p3_point": "西家 ポイント",
330        "p4_point": "北家 ポイント",
331        "p1_str": "東家 入力素点",
332        "p2_str": "南家 入力素点",
333        "p3_str": "西家 入力素点",
334        "p4_str": "北家 入力素点",
335        # レポート - 上位成績
336        "collection": "集計月",
337        "name1": "1位(名前)",
338        "point1": "1位(ポイント)",
339        "name2": "2位(名前)",
340        "point2": "2位(ポイント)",
341        "name3": "3位(名前)",
342        "point3": "3位(ポイント)",
343        "name4": "4位(名前)",
344        "point4": "4位(ポイント)",
345        "name5": "5位(名前)",
346        "point5": "5位(ポイント)",
347        # メモ
348        "regulation": "卓外清算",
349        "remarks": "メモ",
350        #
351        "memo": "備考",
352    }
353
354    match options.rename_type:
355        case StyleOptions.RenameType.NONE:
356            return df
357        case StyleOptions.RenameType.NORMAL:
358            short = False
359        case StyleOptions.RenameType.SHORT:
360            short = True
361
362    for x in df.columns:
363        match x:
364            case "rank":
365                rename_dict[x] = "#" if short else "順位"
366            case "name" | "player":
367                rename_dict[x] = "名前" if short else "プレイヤー名"
368            case "team":
369                rename_dict[x] = "チーム" if short else "チーム名"
370            case "point":
371                rename_dict[x] = "ポイント" if short else "獲得ポイント"
372            case "seat":
373                rename_dict[x] = "席" if short else "座席"
374            case "count" | "game" | "game_count":
375                rename_dict[x] = "ゲーム数"
376            case "pt_total" | "total_point" | "point_sum" | "total_mix":
377                rename_dict[x] = "通算" if short else "通算ポイント"
378            case "pt_avg" | "avg_point" | "point_avg" | "avg_mix":
379                rename_dict[x] = "平均" if short else "平均ポイント"
380            case "ex_point":
381                rename_dict[x] = "ポイント" if short else "卓外ポイント"
382            case "rank_distr" | "rank_distr1" | "rank_distr2" | "rank_distr3" | "rank_distr4":
383                rename_dict[x] = "順位分布"
384            case "rank_avg":
385                rename_dict[x] = "平順" if short else "平均順位"
386            case "1st" | "rank1" | "1st_count" | "1st_mix":
387                rename_dict[x] = "1位数"
388            case "2nd" | "rank2" | "2nd_count" | "2nd_mix":
389                rename_dict[x] = "2位数"
390            case "3rd" | "rank3" | "3rd_count" | "3rd_mix":
391                rename_dict[x] = "3位数"
392            case "4th" | "rank4" | "4th_count" | "4th_mix":
393                rename_dict[x] = "4位数"
394            case "flying" | "flying_mix":
395                rename_dict[x] = "飛" if short else "トビ"
396            case "pt_diff":
397                rename_dict[x] = "差分"
398            case "diff_from_above":
399                rename_dict[x] = "順位差"
400            case "diff_from_top":
401                rename_dict[x] = "トップ差"
402            case "yakuman_mix":
403                rename_dict[x] = "役満和了"
404            case "yakuman_count" | "yakuman":
405                rename_dict[x] = "役満和了数"
406            case "yakuman_rate":
407                rename_dict[x] = "役満和了率"
408            case "win":
409                rename_dict[x] = "勝" if short else "勝ち"
410            case "lose":
411                rename_dict[x] = "負" if short else "負け"
412            case "draw":
413                rename_dict[x] = "分" if short else "引き分け"
414            case "matter":
415                match options.data_kind:
416                    case StyleOptions.DataKind.REMARKS_YAKUMAN:
417                        rename_dict[x] = "和了役"
418                    case StyleOptions.DataKind.REMARKS_REGULATION | StyleOptions.DataKind.REMARKS_OTHER:
419                        rename_dict[x] = "内容"
420                    case _:
421                        rename_dict[x] = "内容"
422
423    if not g.params.individual:
424        rename_dict.update(name="チーム" if short else "チーム名")
425
426    return df.rename(columns=rename_dict)

カラム名をリネームする

Arguments:
  • df (pd.DataFrame): 対象データフレーム
  • options (StyleOptions): 変換モード
Returns:

pd.DataFrame: リネーム後のデータフレーム

def df_drop(df: pandas.DataFrame, drop_items: list[str]) -> pandas.DataFrame:
429def df_drop(df: pd.DataFrame, drop_items: list[str]) -> pd.DataFrame:
430    """
431    非表示項目をドロップ
432
433    Args:
434        df (pd.DataFrame): ターゲット
435        drop_items (list): 非表示項目
436
437    Returns:
438        pd.DataFrame: 加工後
439
440    """
441    original = df.columns.to_list()
442    columns = df_rename(df, StyleOptions(rename_type=StyleOptions.RenameType.NORMAL)).columns.to_list()  # カラム名変換
443    position = [columns.index(item) for item in drop_items if item in columns]
444    df.drop(columns=[original[x] for x in position], inplace=True)
445
446    return df

非表示項目をドロップ

Arguments:
  • df (pd.DataFrame): ターゲット
  • drop_items (list): 非表示項目
Returns:

pd.DataFrame: 加工後

def group_strings(lines: list[str], limit: int = 3000) -> list[str]:
449def group_strings(lines: list[str], limit: int = 3000) -> list[str]:
450    """
451    指定文字数まで改行で連結
452
453    Args:
454        lines (list[str]): 連結対象
455        limit (int, optional): 制限値. Defaults to 3000.
456
457    Returns:
458        list[str]: 連結結果
459
460    """
461    result: list[str] = []
462    buffer: list[str] = []
463
464    for i, line in enumerate(lines):
465        is_last = i == len(lines) - 1  # 最終ブロック判定
466        max_char = limit * 1.5 if is_last else limit  # 1ブロックの最大値
467
468        # 仮に追加したときの文字列長を計算
469        temp = buffer + [line]
470        total_len = len("".join(temp))
471
472        if total_len <= max_char:
473            buffer.append(line)
474        else:
475            if buffer:
476                result.append("\n".join(buffer))
477            buffer = [line]
478
479    if buffer:
480        result.append("\n".join(buffer))
481
482    # 改行の集約
483    result = [str(x).replace("\n```\n\n```\n", "\n```\n```\n") for x in result]
484    result = [str(x).replace("\n\n\t", "\n\t") for x in result]
485
486    return result

指定文字数まで改行で連結

Arguments:
  • lines (list[str]): 連結対象
  • limit (int, optional): 制限値. Defaults to 3000.
Returns:

list[str]: 連結結果

def split_strings(msg: str, limit: int = 3000) -> list[str]:
489def split_strings(msg: str, limit: int = 3000) -> list[str]:
490    """
491    指定文字数で分割
492
493    Args:
494        msg (str): 分割対象
495        limit (int, optional): 分割文字数. Defaults to 3000.
496
497    Returns:
498        list[str]: 分割結果
499
500    """
501    result: list[str] = []
502    buffer: list[str] = []
503    codeblock: bool = False
504
505    for line in msg.splitlines(keepends=True):
506        # 仮に追加したときの文字列長を計算
507        temp = buffer + [line]
508        total_len = len("".join(temp))
509
510        if total_len < limit:
511            buffer.append(line)
512            if line != line.replace("```", ""):  # 1行でopen/closeされる想定ではない
513                codeblock = not (codeblock)
514        else:
515            if buffer:
516                if codeblock:  # codeblock open状態
517                    buffer.append("```\n")
518                    result.append("".join(buffer))
519                    buffer = [f"```\n{line}"]
520                else:
521                    result.append("".join(buffer))
522                    buffer = [line]
523
524    if result:
525        return result
526    return [msg]

指定文字数で分割

Arguments:
  • msg (str): 分割対象
  • limit (int, optional): 分割文字数. Defaults to 3000.
Returns:

list[str]: 分割結果