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]: マッピング用辞書
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]: 分割結果