absfuyu 5.3.0__py3-none-any.whl → 5.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of absfuyu might be problematic. Click here for more details.

Files changed (73) hide show
  1. absfuyu/__init__.py +1 -1
  2. absfuyu/__main__.py +2 -2
  3. absfuyu/cli/__init__.py +2 -2
  4. absfuyu/cli/color.py +2 -2
  5. absfuyu/cli/config_group.py +2 -2
  6. absfuyu/cli/do_group.py +2 -2
  7. absfuyu/cli/game_group.py +2 -2
  8. absfuyu/cli/tool_group.py +2 -2
  9. absfuyu/config/__init__.py +2 -2
  10. absfuyu/core/__init__.py +4 -4
  11. absfuyu/core/baseclass.py +511 -153
  12. absfuyu/core/baseclass2.py +3 -3
  13. absfuyu/core/decorator.py +2 -2
  14. absfuyu/core/docstring.py +2 -2
  15. absfuyu/core/dummy_cli.py +2 -2
  16. absfuyu/core/dummy_func.py +2 -2
  17. absfuyu/dxt/__init__.py +2 -2
  18. absfuyu/dxt/dictext.py +17 -14
  19. absfuyu/dxt/dxt_support.py +2 -2
  20. absfuyu/dxt/intext.py +52 -34
  21. absfuyu/dxt/listext.py +81 -14
  22. absfuyu/dxt/strext.py +4 -4
  23. absfuyu/extra/__init__.py +2 -2
  24. absfuyu/extra/beautiful.py +2 -2
  25. absfuyu/extra/da/__init__.py +2 -2
  26. absfuyu/extra/da/dadf.py +59 -56
  27. absfuyu/extra/da/dadf_base.py +2 -2
  28. absfuyu/extra/da/df_func.py +2 -2
  29. absfuyu/extra/da/mplt.py +2 -2
  30. absfuyu/extra/data_analysis.py +2 -2
  31. absfuyu/fun/__init__.py +7 -2
  32. absfuyu/fun/rubik.py +442 -0
  33. absfuyu/fun/tarot.py +2 -2
  34. absfuyu/game/__init__.py +2 -2
  35. absfuyu/game/game_stat.py +2 -2
  36. absfuyu/game/sudoku.py +2 -2
  37. absfuyu/game/tictactoe.py +2 -2
  38. absfuyu/game/wordle.py +2 -2
  39. absfuyu/general/__init__.py +2 -2
  40. absfuyu/general/content.py +4 -4
  41. absfuyu/general/human.py +2 -2
  42. absfuyu/general/shape.py +2 -2
  43. absfuyu/logger.py +2 -2
  44. absfuyu/pkg_data/__init__.py +2 -2
  45. absfuyu/pkg_data/deprecated.py +2 -2
  46. absfuyu/sort.py +2 -2
  47. absfuyu/tools/__init__.py +2 -2
  48. absfuyu/tools/checksum.py +2 -2
  49. absfuyu/tools/converter.py +2 -2
  50. absfuyu/tools/generator.py +4 -4
  51. absfuyu/tools/inspector.py +346 -69
  52. absfuyu/tools/keygen.py +2 -2
  53. absfuyu/tools/obfuscator.py +4 -4
  54. absfuyu/tools/passwordlib.py +2 -2
  55. absfuyu/tools/shutdownizer.py +2 -2
  56. absfuyu/tools/web.py +2 -2
  57. absfuyu/typings.py +7 -2
  58. absfuyu/util/__init__.py +57 -3
  59. absfuyu/util/api.py +2 -2
  60. absfuyu/util/json_method.py +2 -2
  61. absfuyu/util/lunar.py +2 -2
  62. absfuyu/util/path.py +17 -4
  63. absfuyu/util/performance.py +2 -2
  64. absfuyu/util/shorten_number.py +2 -2
  65. absfuyu/util/text_table.py +33 -14
  66. absfuyu/util/zipped.py +2 -2
  67. absfuyu/version.py +3 -3
  68. {absfuyu-5.3.0.dist-info → absfuyu-5.5.0.dist-info}/METADATA +2 -2
  69. absfuyu-5.5.0.dist-info/RECORD +77 -0
  70. absfuyu-5.3.0.dist-info/RECORD +0 -76
  71. {absfuyu-5.3.0.dist-info → absfuyu-5.5.0.dist-info}/WHEEL +0 -0
  72. {absfuyu-5.3.0.dist-info → absfuyu-5.5.0.dist-info}/entry_points.txt +0 -0
  73. {absfuyu-5.3.0.dist-info → absfuyu-5.5.0.dist-info}/licenses/LICENSE +0 -0
absfuyu/extra/da/dadf.py CHANGED
@@ -3,8 +3,8 @@ Absfuyu: Data Analysis
3
3
  ----------------------
4
4
  Data Analyst DataFrame
5
5
 
6
- Version: 5.2.0
7
- Date updated: 15/03/2025 (dd/mm/yyyy)
6
+ Version: 5.5.0
7
+ Date updated: 23/04/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
@@ -37,7 +37,7 @@ try:
37
37
  except ImportError:
38
38
  from absfuyu.core.decorator import dummy_decorator as override
39
39
 
40
- from absfuyu.core.baseclass import ShowAllMethodsMixin
40
+ from absfuyu.core.baseclass import GetClassMembersMixin
41
41
  from absfuyu.core.docstring import deprecated, versionadded
42
42
  from absfuyu.extra.da.dadf_base import CityData
43
43
  from absfuyu.extra.da.dadf_base import DataAnalystDataFrameBase as DFBase
@@ -58,6 +58,7 @@ class DataAnalystDataFrameColumnMethodMixin(DFBase):
58
58
  - Drop columns
59
59
  - Drop rightmost column
60
60
  - Add blank column
61
+ - Split str column
61
62
  """
62
63
 
63
64
  def rearrange_rightmost_column(
@@ -200,6 +201,59 @@ class DataAnalystDataFrameColumnMethodMixin(DFBase):
200
201
  self[column_name] = [fill] * self.shape[0]
201
202
  return self
202
203
 
204
+ @versionadded("5.2.0") # No test cases
205
+ def split_str_column(
206
+ self,
207
+ col: str,
208
+ pattern: str = " ",
209
+ *,
210
+ n: int | None = None,
211
+ regex: bool = False,
212
+ ) -> Self:
213
+ """
214
+ Split column with dtype[str] into other columns.
215
+
216
+ Parameters
217
+ ----------
218
+ col : str
219
+ Column name
220
+
221
+ pattern : str, optional
222
+ Split pattern, by default ``" "``
223
+
224
+ n : int | None, optional
225
+ Split by how many times, by default ``None``
226
+
227
+ regex : bool, optional
228
+ Regex mode, by default ``False``
229
+
230
+ Returns
231
+ -------
232
+ Self
233
+ DataFrame
234
+
235
+
236
+ Example:
237
+ --------
238
+ >>> df = DADF(DADF.sample_df(5)[["text"]])
239
+ >>> df.split_str_column("text", "s"))
240
+ text text_0 text_1
241
+ 0 uwfzbsgj uwfzb gj
242
+ 1 lxlskayx lxl kayx
243
+ 2 fzgpzjtp fzgpzjtp None
244
+ 3 lxnytktz lxnytktz None
245
+ 4 onryaxtt onryaxtt None
246
+ """
247
+ if n is None:
248
+ pass
249
+ splited_data: pd.DataFrame = self[col].str.split(
250
+ pat=pattern, n=n, expand=True, regex=regex
251
+ )
252
+ num_of_splitted_cols = splited_data.shape[1]
253
+ new_col_names = [f"{col}_{x}" for x in range(num_of_splitted_cols)]
254
+ self[new_col_names] = splited_data
255
+ return self
256
+
203
257
 
204
258
  # Row method
205
259
  # ---------------------------------------------------------------------------
@@ -1035,7 +1089,7 @@ class DataAnalystDataFrameCityMixin(DFBase):
1035
1089
  # Main
1036
1090
  # ---------------------------------------------------------------------------
1037
1091
  class DADF(
1038
- ShowAllMethodsMixin,
1092
+ GetClassMembersMixin,
1039
1093
  DataAnalystDataFrameCityMixin,
1040
1094
  DataAnalystDataFrameDateMixin,
1041
1095
  DataAnalystDataFrameOtherMixin,
@@ -1136,55 +1190,4 @@ class DADF_WIP(DADF):
1136
1190
  W.I.P - No test cases written
1137
1191
  """
1138
1192
 
1139
- @versionadded("5.2.0")
1140
- def split_str_column(
1141
- self,
1142
- col: str,
1143
- pattern: str = " ",
1144
- *,
1145
- n: int | None = None,
1146
- regex: bool = False,
1147
- ) -> Self:
1148
- """
1149
- Split column with dtype[str] into other columns.
1150
-
1151
- Parameters
1152
- ----------
1153
- col : str
1154
- Column name
1155
-
1156
- pattern : str, optional
1157
- Split pattern, by default ``" "``
1158
-
1159
- n : int | None, optional
1160
- Split by how many times, by default ``None``
1161
-
1162
- regex : bool, optional
1163
- Regex mode, by default ``False``
1164
-
1165
- Returns
1166
- -------
1167
- Self
1168
- DataFrame
1169
-
1170
-
1171
- Example:
1172
- --------
1173
- >>> df = DADF(DADF.sample_df(5)[["text"]])
1174
- >>> df.split_str_column("text", "s"))
1175
- text text_0 text_1
1176
- 0 uwfzbsgj uwfzb gj
1177
- 1 lxlskayx lxl kayx
1178
- 2 fzgpzjtp fzgpzjtp None
1179
- 3 lxnytktz lxnytktz None
1180
- 4 onryaxtt onryaxtt None
1181
- """
1182
- if n is None:
1183
- pass
1184
- splited_data: pd.DataFrame = self[col].str.split(
1185
- pat=pattern, n=n, expand=True, regex=regex
1186
- )
1187
- num_of_splitted_cols = splited_data.shape[1]
1188
- new_col_names = [f"{col}_{x}" for x in range(num_of_splitted_cols)]
1189
- self[new_col_names] = splited_data
1190
- return self
1193
+ pass
@@ -3,8 +3,8 @@ Absfuyu: Data Analysis
3
3
  ----------------------
4
4
  Data Analyst DataFrame - Base/Core
5
5
 
6
- Version: 5.2.0
7
- Date updated: 10/03/2025 (dd/mm/yyyy)
6
+ Version: 5.5.0
7
+ Date updated: 23/04/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
@@ -3,8 +3,8 @@ Absfuyu: Data Analysis
3
3
  ----------------------
4
4
  DF Function
5
5
 
6
- Version: 5.2.0
7
- Date updated: 10/03/2025 (dd/mm/yyyy)
6
+ Version: 5.5.0
7
+ Date updated: 23/04/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
absfuyu/extra/da/mplt.py CHANGED
@@ -3,8 +3,8 @@ Absfuyu: Data Analysis
3
3
  ----------------------
4
4
  Matplotlib Helper
5
5
 
6
- Version: 5.2.0
7
- Date updated: 10/03/2025 (dd/mm/yyyy)
6
+ Version: 5.5.0
7
+ Date updated: 23/04/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
@@ -4,8 +4,8 @@ Absfuyu: Data Analysis [W.I.P]
4
4
  Extension for ``pd.DataFrame``
5
5
  (deprecated)
6
6
 
7
- Version: 5.2.0
8
- Date updated: 15/03/2025 (dd/mm/yyyy)
7
+ Version: 5.5.0
8
+ Date updated: 23/04/2025 (dd/mm/yyyy)
9
9
  """
10
10
 
11
11
  # Library
absfuyu/fun/__init__.py CHANGED
@@ -3,10 +3,15 @@ Absfuyu: Fun
3
3
  ------------
4
4
  Some fun or weird stuff
5
5
 
6
- Version: 5.2.0
7
- Date updated: 10/03/2025 (dd/mm/yyyy)
6
+ Version: 5.5.0
7
+ Date updated: 23/04/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
+ # Module level
11
+ # ---------------------------------------------------------------------------
12
+ __all__ = ["zodiac_sign", "happy_new_year", "human_year_to_dog_year"]
13
+
14
+
10
15
  # Library
11
16
  # ---------------------------------------------------------------------------
12
17
  from datetime import date
absfuyu/fun/rubik.py ADDED
@@ -0,0 +1,442 @@
1
+ """
2
+ Absfuyu: Fun
3
+ ------------
4
+ Rubik
5
+
6
+ Version: 5.5.0
7
+ Date updated: 23/04/2025 (dd/mm/yyyy)
8
+
9
+
10
+ Version added: 5.4.0
11
+ """
12
+
13
+ # Module level
14
+ # ---------------------------------------------------------------------------
15
+ __all__ = ["RubikNotations", "Rubik3x3"]
16
+
17
+
18
+ # Library
19
+ # ---------------------------------------------------------------------------
20
+ from typing import ClassVar, Self, cast, final
21
+
22
+ # Data
23
+ # ---------------------------------------------------------------------------
24
+ PLL_PIC = {
25
+ "Aa": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAICSURBVHhe7do9TsNAEIZhhwNQQEtJxf3PQkVJCwUXSPKFjFhZrD3e3/l7JUQiRFg9HlsOm9OyLOfrV7TRDen88/ukR6fH6x9Q/voP98fRRoHEyAXS99vr/VFZbiapBsoF0tP7x+17KZSbSaqBYt8C1J7X0iK0vdzeAnCBKDc3k3QmHAbyMkmlQNThScpdm3ILkDBJWHMxkJdJygEBL3fQ06qRsIDSo6SlaiTOkdBeMVI6PdahqibJC9RhpPU1yANU9TUJWYdqgoQsQzVDQlahmiIhi1DNkVAK9fWiH6rrvlsK9Pz5ByclWt/e2rr/qyQ95dIJa1XNG2ha29a6hrzBTY+S1mtUdySk/WI+BAlphhqGhLRCDUVCGqGGIyFtUFOQkCaoaUhIC9RUJKQBajoSkg4lAglJhhKDhKRCiUJCEqHEISFpUCKRkCQosUhICpRoJCQBSjwSmg2lAgnNhFKDhGZBqUJCM6DUIaEUasS+Xtd9t97V7uuJ2Xfr/cHS9JRLJ4wT/e7W75n4YOmIfT31SKj3xdwEEuoJZQYJ9YIyhYR6QJlDQq2hTCKhFKo2s0gIUOupyk3W1s9MI60jsDUGPc9Nn/o77pLX/29iskAW7rhLWoPkgCiXSIhg9oCQy9PtSG5Pt6MFEqNAYhRIjAKJUSAxCiRGgcQokBip3ncb07JcAGos+ItnN7h+AAAAAElFTkSuQmCC",
26
+ "Ab": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHuSURBVHhe7dtNTsMwEIbhlAOwgC1LVtz/LKxYsoUFFyj90o4URYkztmN7/l4JkQqlVE+dUVTDZZqm6+0rSjQjXf/uD1p0eb79AuXP//Q4jhIFEqNAYhRIjAKJUSAxCiRGgcQokBi5Rvr9eH8cpXOLREAcKJdIa5gjKHdIBPLy+TV/p1JQrpD2gOjxHpTpj0r2UKijnyPTH5WkLp/cTCItgVKrhJs5pLOBkCmkFkDIDFIrIGQCqSUQUo/089YWCKned1sCvX7nA9H5R+eqvZk84xKj50idr/ZmcglUsoJyU4fUekhvpQppBBBSgzQKCKlAGgmExCONBkKikSQAIbFIUoCQSCRJQEgckjQgJApJIhASgyQVCIlAkgyEhiNJB0JDkTQAoWFIWoDQECRNQKg7kjYg1BVJIxDqhqQVCHVB0gyEmiP12BdrXdN9t9p9sdYN33frdYnV7OvRa0y9vmb7bksgiSsot9ORtA/prU5FsgiETkOyCoROQbIMhLKRALJEsQ6EqlaSByBUjOQFCFXPJOtAqBppPaMsVo2kob03EVcB50ow/Q84FCGVjAa1fzOZG+GUjgX2SrI0d3JWlJuVtC73DXcxk6iS2eRqJdUMb1e3ACVAyM1KKgVCrmZSSa5mUk2BxGi+3O6H0XbT9A9hSvCXpdP6AwAAAABJRU5ErkJggg==",
27
+ "E": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAFrSURBVHhe7dkxbsIwGEBhwwEYysrI1PufhYmxKx16gUCBf4mK8xwH1VbeJ1UlqhT/ehLUJpuU0nD7UcY90vDzuHiHze62QOf33z5fK8NIgJEAIwFGAowEFEX6/jw+X/Vp7vw4UizQa6ia+VGk8Y17C1U7/2SkuOHH6Xz/HXoJtcT82UivFojr1kMtNX/R2e3Vojktnd3mzl/0322tjAQYCTASYCTASICRACMBRgKMBBQ9d7scHtv6/Rff1rdk7vye3SZ4doOMBBgJMBJgJMBIgJEAIwFGAowEGAkwEmAkwEiAkQAjAUYCjAQYCTASYCTASIDP3QCfu03wuRtkJMBIgJEAIwFGAowEGAkwEpCN9LtDjV3qWO5vrVhq/myk2L6PbxbXJdv7/7DU/Ojs9ldxvEADZ7fa+dFn0viGdIFW1M6PP7jjxr0FCjXzF31VMkcLb7ca+O22dkYCjAQYCTASYCTASEDRc7d1SukKxuWXT5bSG9QAAAAASUVORK5CYII=",
28
+ "F": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAF7SURBVHhe7dmxTcNAHEbxCwNQMAMVNQMwBUMyBQNQUzEDBQuEyPjK+N53EpL/zvtJKE5z+J590jk+tdbOlz9tWCKdf/6+/IfT/eUfFB//bj3WBiMBRgKMBBgJmIr0/fS4HtUzc+5xpMqBunQOUaQ++MPn1/JZUT/3JBSOdIRAXRoK7bjT27OarQuPd9xHuHuuIXOLnt1mltwen92SecTPbn3Qystv5kLHW4Bk8L1K5+BPJQPxcrtVRgKMBBgJMBJgJMBIgO/dADeTA24mISMBRgKMBBgJmIr08vqxHtUzc+5xpMqBunQOUaQ++Pvb8/JZUT/3JBSOdIRAXRoK7bjT27OarQuPd9xHuHuuIXOLnt1mltwen92SecTPbn3Qystv5kLHW4Bk8L1K5+BPJQPxcrtVRgKMBBgJMBJgJMBIgO/dADeTA24mISMBRgKMBBgJMBJgJMBIgJEAIwFGAowEGAkwEmAkwEiAkQAjAUYCjAQYCTAS4Hu3odZ+AfiyiXn4ccLwAAAAAElFTkSuQmCC",
29
+ "Ga": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAALwSURBVHhe7Zq9jtQwFEazPAAFtJRUiHJ7KKkpEI+IKKgpoadEVFvSQsEL7O43k6u9O9jJ/bNjJz7Saj3SjJM5ud+Nk8zVNE2393+DBU6Sbv+dX5Tg6un9Bjqf/8k8HiwwJAkYkgQcQtLfVy/nkY3DVJJHVJNnt7cffsyjx3z7fD2PHpDOT5Ke/bo5/ZeC+U2SNEfl+e+b6c8L3VF8//rTPHrMl58f59EDlvk1orpZAkBOSpAVbfSaiRtFDJFKxS0VNaCJsyVyokryNDwpkh6Ue48Ua08Ci5Jo4pKi+JdPVUuEKI8gkI1bSoxlI0txWBPEyb1XEjd8F7OgXNxy5iMrSiMIeCrKKoj4T1JOEL2OEKUVRHhEeVg9u+WkSbmMg1UQh8/x/et1Nm7efQeis1skEYIA/+ybd+mKimwN1SRFCSKWoscFefsRqCIpWhCREhUtCBSXxOMQKYhATyL4wYgSBIpKKlVBl/C5cXEcKQgUk1RLEEDE+AUw33YERSTxneRxKE2qR0VwWiedh2noXg3u20jgPaimIE70PoQuJlMRk1xbWaD9wsFLzR8V99DFZNROSeCn+RyR0QuRtJWgteqOEuWW1KogIkKUShI2wjfUuiDCK8pcSb0IIjyiTJJ6E0RYRbl6Uk+CCMs+q9ZJOftLG7auk6SCSq3DiOo33aSUqCCg7UVE9du3a2gFaeYnSZrINVdJpSqIIDnaimpGUmlBhEWUOm7WXLfKWvSabdy1kPamTRt3RMS0JwZt8960kmr1II7l7AY2kbSFIEIrCFSX1JsgUFXSloI8VJPUqyBQRVLPgkBxSfznwz0KAqd10nmYRvvcjcMFWT7fCsUWk7Uipl1Maim2mOSCeq4gIlxS7006RaikPQoCYZL2KgiESNqzIKCWBCFcyt4FAVclHUEQMEs6iiDg7kl7FwTcki571B5xSzoCzT3B1dLttdveWK0kL6OSDsKQJGBIEjAkCRiSBAxJAoYkAUOSgNXnboNpugNSNscojmPPkQAAAABJRU5ErkJggg==",
30
+ "Gb": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAALbSURBVHhe7ZqxbtwwDIadPECGZM3YqciYPR07dyj6iEWHzBmTvWORqWPWdugLXPJfTIAQTjZFUTLp6gMOp/MBsv2JpKXTnU3TdHh7DRY4Sjr8e//QgrOLtxME7/98bg8WGJIEDEkCqiT9/fhhbu0btaT/RRBQSSJBl8+/j+97p3gKED2CSge2eArQUtCXm+9zq4yrl/bRrI4k6YhIJnufvv6cW9P0+ON2br1D36XHCZeTSZJjFVVcEEg/E7njtUjuQ1W4rUSlN07Rwo/zCLIWRde/dh/qKUDtk41uOE2jXqJSMUuiXCxwc8JSTonT1KRUCAacjqWDr6pJW2IRUTkZSyWkWhI6PdVxK2pFQUYqiMh9ZxZJW4m6+2xTo5aolsTNR4qoEkwiae+izNJtK1FPD+1FmUkCe40oU0lgj6LMJQEu6s91fFFN9924oKWfNOgxzutLDXxaYNFn82UJTzkeYRwadR4JUnLLEh5JuX7p2nLXBbosS3gERa1RzSWB6MW8iyQQWVQ3SSCqqK6SgDdR2IBYE9ddEogWUZtIAlxUT7go6TbWZpKAB1ESNpUEeqce0qy0eG8uKaVnjZLiarfk/te34zuQpqJmt4SgAVk6l7vdkl6pV5py7tKttajSegTcSQKtRHFBJU84l5KAtSitIOBWErASVSMIuJYEakXVCgLuJQGtKAtBIIQkUCrKShAIIwlIRVkKAqEkgTVR1oJAOEmAi+K0EASa7rtJsdh3s95r44T6O+Ap0D8XVNIHpWsuMoG7Ba4GLoj/imBJaEm8BpGgknmUlLCS0iJdOo8qIaQkLogX6Vaiwklae8y3EBVK0pogIicKbY04t5IghEuRCiIsIypEJJUKIqxEuZekFURwUVrC1CSNIG0NSgkjKa1RPXGxdjtFTkgaUdr+AUXZUkqif7eSpAxJAnr0H6YmbcmQJGBIEjAkCRiSBAxJAoYkAUOSABf7br6ZpleZlqpZ7/HSYgAAAABJRU5ErkJggg==",
31
+ "Gc": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAL8SURBVHhe7Zq/jtQwEIdzPAAFtJRUiJIeSmoKxCOerqCmhJ4SUVHSQsELHPvL7uhGudiZv9k48SetNtFqbefzzKyd7M0wDPenV6fCKOn+3/kkg5unpw4ab//J5bhToUsS0CUJ6JIEdEkCuiQBXZKALknAoqS/r16OryPTI0nA4raEoujZz1/jeymq6PMpfVtyENySEEGlKNoLbklHKOpmSTx69i7KFUlHEaWWNK1BRxDlrklg76JCJIE9iwqTBPYqKlQS2KOocEmAi/rzon1Ri8/d6CKf/364cClckOX7W0G9wdXCU87aRo1dbHB5BLVao9IlgdaL+SqSQMuiVpMEWhW1qiRwTVHvPn6/HOlYXRJoTdRVJIFriPp692Z814pSr5OsIbtFSFqNwz8IkE54+opbuiLmKafpy7LiJjnNRdJaNUojiNhUumWLsggCqnQr5XCtU0s6aFJP0z7Grxa0pXTjZEWUVhDhkoROrR0vkZl6iKhSVszhkqTpyEKGKMuYTZJ49LQkio9VkwHmSGpNlFUQcC8mlzq3/LqV4IJoPJL2XYIift22HlEeQYRbEtiqqAhBIEQSuJaoElGCwFiTzofzaJ+7vX3/MLhvX3yDsxI9hpS7AHwWMciowj0HCitNJMYYGUEgbVvCB8dnNZtoQUSKJLB2jfrw+vZyFisIpEkCa4nibX/+8Um8PJCSKgnwwpkhiqczn5RIUemSQFZE8baoD806SsoqkkC0qDlBRLSo1SSBKFH8u6V1kGbJssQjSTBfsl/7TIpXVC2CpkBUhKxHkqjRqQw6j+jUKkojKJLZdCuJihBEaEV5BHmjv1iTpkIiBRElUTienhPWCPKIqhZuEpMhiFiKqAhBNH6rqKokkCmIKF18hCDCI2rxLsAcmo5wi0X7X26+DwPYapSwtA+kk592FyAaSJuK86DNDlMkaZDcqJ8yV5vAXMpJ26foVws6tb9JSRok7VsFAbTfRLpFYBFEHCKSPBwqkjx0SQK6JAFjTTofduYZhv+H7r9QKUdxcgAAAABJRU5ErkJggg==",
32
+ "Gd": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAALLSURBVHhe7dmxbtRAEMZxhweggJaSCqVMDyU1BeIREQU1ZdJTIipKWih4gSTfcSMNy+16ZnbWu2PvX0Lnu6AL/G68ts9Xy7LcP/6ZFToh3f/5+6RFV08ff0Hw939y3p4VmkiCJpKgQyD9fvXyvGXrMJNUA3UIpGfff5werVCmU4Da8e0doUna5BTg+U/5P4j37vrjecs/7Yfc9WTyzfuvp8fbTzenR4peR+nP0jQnk4TjPkmtdi0OwUtfz/09bRYgqohEb+wNlZsUer00WZZqgFAWKYXxgtIC0fNaKCsQurgmcfkUR/vL+JqRA7pUDi1NsyZZurgm5UaTnlsnSgM0Wv8hASM3LaWfleoFhA/U+qHyVo9utb1+2w/Iq6ZIPSeIskx+WjOkvQChJkgc6O5LbCDkjrSnCaJckfYIhNyQegH9etEWCLncd+OHea81iN6z9H4cyPqVjKTVr0polHOf0toEWS8b6H1zU9l6F6NEX5WUGmENajlBlBlpr4v0pUxIRwJCaqSjAaEiEkD4F/JHBELiSToqEBIh8WnaEojXCwip1qQjAiEVEnY5vtu1aoRdjKdC2iIONErVlyVraS5LOBCtg2u7eJe7Jbla72qj7WI89e7WAmpkICRG4mPvCTU6EFJNkjdUBCCk3t28oKIAITUSqoWKBIRMSMgKFQ0ImZGQFioiEKpCQlKoqECoGgmtQUUGQi5ISDJREYGQy303ntc9OMl9t61qcoHLJwn/yRb33aihLnA1ff724bz172RFzR2JJo9DlY56EXJFSo9i2vOoUXNDyh3m+cIbFcoFae08KPpEqZEAwlHWgKjIUFWTJAWiclDYHhnOjKQFoiJOVPWapAGiokFVI6VrlDQONXrVSJZGX4PSutyczAFZpmuLa7eh7uBaGgKptj0gdVmTojWRBE0kQRNJ0EQSNJEETSRBE0mQ+323/bUsD7Leq1kMq2/FAAAAAElFTkSuQmCC",
33
+ "H": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGJSURBVHhe7dlBboJAHEZx7AF6mu7bZU/QQ3oCl+2+p+kFrKizIYJvJlE+4L3EVNJE5ScY/syu67rj6WETnZGOf5eNR7R7Pb3Bwl//5frcJhIJJBJIJJBIIJFAUUgfX7/XZ1nFIBWgRKgIpCFMGtTsSAXke/92/ltKgpoVaQyobKdARc1uY2hTObuFJBJIJJBIIJFAIoFEAokEEgkkEihq3e398zKW/Bz4WPKMnN3u5OwGEwkkEkgkkEggkUAigaqRklYxamv97FVISwYqtewDRmq5Gk6rdakKjSVrOIJuRb5wNJasFaiP7lvVkdRyqiUOuDXvgwfc1nM5sZYvAv9wrwGqBaiv6hKg9sUTa9mHKqS+JUO1fvZqpC0mEkgkkEggkUCuu4Fcd7sTHku2nkggkUAigUQCiQQSCSQSSCTQrEj9FfbY7eCp/z27WZHK+DHEKNspd0EjZrdbRwwF2szsNgRJOYJKMT/cBSYNqC/qVklLmznd0hMJJBJIJJBIIJFAIoGi1t0y67p/nuaai10GTQQAAAAASUVORK5CYII=",
34
+ "Ja": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHlSURBVHhe7dkhT8NAGMbxjg+AAItEESQeJBpB+IgEgUaCRxLUJBYEX2Db07VJt2zrc2/fdnnvnl9COARt9+/ddllnVVUtVj9yQB1p8b/+Ywyz09UJgh//pBnLAYpEUCSCIhEUiaBIBEUimCL9XV02oxiGXm9ypGiBWkOuO2nH3Z7o7Hte/2b07YjvHj+b0ab3l5tmdBi747ZcO+D4dCTrnTj/mVe/F/v/9+H6uRltev16akb+Um8ytdzGXGKIMWaQXVJfT/JMskzXvuPvWnLeyw0sr4GeSdAe2HtWdQOxYSysNxmSPt28Q0UIBKbvk3BS9oT7loNXIGa5pVzvNhz/KF+6ec4gJtIQOL5pxz3EVEvM06SRIgaCySJFDQSTRIocCEaPdHsfOxDUn27rob9uoI+3mIFgtC3AVEss7BagGyjyDGq5R4r+Jr2La6QcA4FbpFwDgUuknAPB4Ei5B4JBkUoIBOZIpQQCKhKCdKOUFAiSZ1JpgSApUomBwPSeVFIgMEXafo/KnSlSaY7ytMRTlk9LIlIkgiIRFImgSARFIigSYdTnbrnQZrKHNpMkRSIoEkGRCIpEUCSCIhEUiaBIBEUiKBJBkQiKRFAkgiIRFImgSARFIigSQZEIikRQJIKeu/WqqiUCWr67jAaMcgAAAABJRU5ErkJggg==",
35
+ "Jb": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHJSURBVHhe7dsxTgMxEIXhDQeggJaSipoeSk6AOCMFNSX0lIiKkhYKLhAyiS0ZKzbPG5sdj98vIbyKBNIn72ayC6tpmtabL5Zpi7T+3h20aHW8+QWd//wjt2aZiAREJCAiAREJiEhARAIiEhCRgIZC+ro4d6uyhkHyQHOghkCKYUqhzCN5kJO39+13XwmUaaQUkD9GoYa6VZJCy8VbJWBEAiISEJGAiAREJCAiAQ313O3zbDcnnX7gc5LEYfKPOEyCEQmISEBEAiISEJGAiAREJCAiAZmfuK9vX9zqd0/3l26VjxM3mHkk2THorkk1xE5KnXJo5pFCoIfXO7cqyzRSCHTIKWf23W0fEO8nBdXaQT5zSLWBJFNILYAkM0itgCQTSC2BpO6Rrm7aAkldP3cLgZ4f2wBJ3c5Jc06xoeakEKjlDvJ1h9T6Ir2vrpCWAJK6QVoKSOoCaUkgST3S0kCSaiQNQJJaJC1AkgokAQlRNAFJ6nZSTSCZsP2UHZd7LU4VUu0dlPpHG3+MfjxReU2qeYqloEo+v6lEiq9RhxaDlABJKpFa5GFKgaSh/kR5TmYfKdWOSEBEAiISEJGAiAREJKCun7v9T9P0A4HGuqkeGiXTAAAAAElFTkSuQmCC",
36
+ "Na": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIqSURBVHhe7dsxTsQwEIXhLAeggJaSipoeSk7AITkBJfTUVJS0UHCBZR8kYojk7Njx2G8c/xIi0UogfbK9Sby7G4Zhf/jpLfSDtP/6PbFod3r4B87//sl43FuoIynqSIo6kqKOpKgjKepIilTXSZ9Xl+PR/85e38ajcLHXMbf3L8PTw/V4drzNXScBSP5mKQkJI0gzimKawzBBJSGFpl9qE8h8mrFARSHJ0ZMLKgQ0nTNARS3cE5IEOjbtUhZupF28aRbu+RpkMaKYS3532xJUMhLaCtQqJLQFqNVIqHWoLEioZahsSKhVqKxIqEWo7EhIQn1c+Icy3XeTQOfvf3BL3dz9XnE/P+ofl1hnvu8mp5wcYaHc3pasSY4gr2uUORLyvpgXQUKeoYohIa9QRZGQR6jiSMgbVBUk5AmqGhLyAlUVCXmAqo6EJBRjFEiIGYoGCbFOPSqkeSxQ1EiIAYoWiWnqUY8kFij66cYARY+EakO5QEI1odwgoVpQrpBQDSh3SEhCldjXM913Sylm3y1lXy8l8303648DyiknR1iuiuy7WVdiX889ErJezJtAQpZQzSAhK6imkJAFVHNIKDdUk0goJ1SzSCgEheMYuKaRUI4R1TwSWgtVHQm3IaGvay29FpuEiq06Uuh7bdO59h5uqdg1aB7FdAtB5QDKEdVTAImkBWri07cxTTAsI2iK7nlSbJsbSax1JEUdSVFHUtSRFHUkRXT7bnwNwzcsDAXYOLVy1wAAAABJRU5ErkJggg==",
37
+ "Nb": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIgSURBVHhe7dsxTsMwFMbxlAMwwMrIxMwOIyfgkJyAEXZmJkZWGLhA6UdjyURJ+uL42d9z3l+qSIoUpJ9sK8Htruu6/eHlzfSHtP85nmi0Oz/8AePXP+uPvZkcSZAjCXIkQY4kyJEEOZIguvuk+8e37uXptj87nfT63zfX/dH/Lt4/+qPx6O6TABT/ZIkGaQiTGwoj5tSomYoCaQgSpltuqKkpd6rqSAFiuA7lhoqBlo4oqoV7CmwuyfXHgMJ75hZujaZGEI6lI6pppDVTLK5ZpFxAqEmknECoOaTcQKgpJA0g1AySFhBqAkkTCJlH+rrSBUJU+253D8c77tdn2R13DHT5qQOEzD6WaE+xkNnHkhhIcwSFzCGVGkFxppBqACEzSLWAkAmkmkCIHqk2EKJGYgBCtEgsQIgSiQkIUU83BiBEi8QChGiQ2KZYHAVSDMRYdSR2IFQViXmKxVVDsgKEqiBZAkLFkawBoaJIFoFQMSSrQKgIkmUgpI5UYl9MO9V9t6X7Ykv33Uqltu+WMsW0Pg64JrV9txioxL6YdtmRrC/SY2VFahEIZUNqFQhlQWoZCK1Gah0IrULaAhASIwEkRtkKEEoaSVsCQouRtgaEktekXEB4FJn6utbc70qWjDRco1Kb+l5bOF/yHKdVMlLOhhBMQIjy07dICmT2vwCpBRiWERSiGkkpbW4kseZIghxJkCMJciRBjiRIdd+tjbruF/dWBdg2pAhsAAAAAElFTkSuQmCC",
38
+ "Ra": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHFSURBVHhe7do7UsMwFIVhhwVQsAYq6vRQsgIWyQoooadOxRoo2EDICfagMNg+V7ItXen8MwymIc6Xa+EHu67rjqcvNdEZ6fj188Ma7a5PL+D891/122oiIREJiUhIREIiEhKRkIiikD7vbvstH6XurxnJG9BQyn6bkIYXujl8nL97adjfWCj6ssTrBP2X5UOmL0tqAkLW92OeJOuhVtIFbsx7MF3gph7XuYv9kJFp4fYKlQKEzKcAsS+Uu5T91k23mUxrUssJiUhIREIiEhKRnrsR6RRgJp0CkAmJSEhEQiISEpGQiIRERJ0nPTy991uXvT7v+63xdJ7USFFImCBmimopCmns8Ks1E1I4PS1BmSepRajouwAh0NT61PRft5YmKhoJtQKVhIRagEpGQrVDLYKEaoZaDAnVCrUoEqoRanEkFELdP/qHWvW5Wwj09vIL563Vn7uFh1w4YUtVxf2kcIK8rlGrIyHvi/kmSMgz1GZIyCvUpkjII9TmSMgbVBYkNAaF7dLgsiEhLxOVFQl5gMqOhEKoEsuOVOIa9LciJqn09I+lM21ygVtDQiISEpGQiIREJCQiIREJiUhIRKs+d6ujrvsGdDW0Lec+UFYAAAAASUVORK5CYII=",
39
+ "Rb": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAG9SURBVHhe7dqxUcNAEIVhmQIIqIGImBxCKqBIKiCEnJiIGghowPgZ3czhsa13K521e/f+GQaRIPnzSj4hNsMwbHdf6kx7pO3P3w812lzvdhD891+N2+pMQiISEpGQiIREJCQiIRHNQvq+ux23YmQ9XjNSNKCU5bhNSGlHN59f++9RSsdbClV8WxJ1go7FvMnFtyUtASH29ZgniT3VPN7glrwG0w2u9bz2UumbjEwX7qhQFiBkXgKU7shLluPWH90mMl2TekxIREIiEhKRkIj03I1IS4CJtAQgExKRkIiERCQkIiERCYmom3XS4/PHuPW/t5f7cet4WieRdYOEiZmamlN1NUmnTrmpukHKgUonqgukOUCo+U+32UCtf7rNBUo1i7QUEGoSaUkg1BzS0kCoKaQaQKgZpFpAqAmkmkAoPNLDU10gFPq5Ww70/loHCIVdcdc+xVJhV9w5UM0JSoVDutQE5YVCWgMIhUFaCwiFQFoTCLlHWhsIuUICSI7iAQi5nSQvQMglkicg5Pqa5AEIuUY6vEatlWskL+kfSydq/pHSUgmJSEhEQiISEpGQiIREJCQiIRGFfu52mYbhFyeGtC1UL+rcAAAAAElFTkSuQmCC",
40
+ "T": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGRSURBVHhe7dkxTsNAEEBRhwNQQEtJRc0BOAWH5BQcgDoVJS0UXCDETqaxYu+fFUh49z8pii0kR/tjI0+8G4bhcHxpxRTp8H3a+Qu76+MHbPz4V+dtrTASYCTASICRACMB3UT6erg/b+V1ESkC1YZqPtI8TE2opiNFkJv9+/QesqGajbQUKPYzobqZ3ZailTi7QUYCjAQYCTASYCTASICRACMBRgK6ee72eXcaS24/cmPJyNmtwNkNMhJgJMBIgJEAIwFVkZ6e385b20bXkY7USqBA1pOKFAd8fXmc3rcu1lEKhSO1FiiQUGgsIadkK+YnAR5LWjt7liytMzXg1lxy/33ALa0pPeDGgVq5/OiXnr4FyJxFW0DW4+9JBenLrVdGAowEGAkwEmAkwOdugPdJBd4nQUYCjAQYCTASYCTASICRACMBzUYa77DjLntu7W+XNBspxo95jNjPjCfNz26XzphUoB5mt3mQ7IA76uIfd4SpCTTq5qeSWl1cbr/BSICRACMBRgKMBBgJ6Oa5W71h+AGr6qDhlFnaCQAAAABJRU5ErkJggg==",
41
+ "Ua": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHySURBVHhe7dnLUcMwFIVhhwKohj0sqYAiqYAl7KmGBpIcJxo0mTg6V7562eebYXA2RPNbko19mKbpeP6RB+ZIx7/LhxIOz+cvGPzvP12P5QFFIigSQZEIikRQJIIiERSJoEgERSIoEkGRCIpEUCSCIhEUiaBIBEUiKBJBkQiKRFAkgt67EfTeLUHv3UiKRFAkgiIRsiK9ffxej/bBHGlvgcAUKQT6/nyZf+8FfZ+0pRlkOcn0fdIel1jMPJOsS21Xd9whjjbuhL2GyvoHF5HYZdd6uS2dUMv4zfdJYN2XRkdHwhkZcZnhhK49qeaZNGKotWM2X91gpFDxWHNnVNbVDUYI5REIsq9uQerLW13d3AJ5XN16nFFegYKsSNBrKO9AkB0JegtVIhDMe9LlMN/r+//gfr78BmdRcgxZG/c9S2exxsYdB/KcQZC9cd/TaumVDBS4RYLaoUrtQbdcI0GtULUCgXskiAcdLwcvcaAaF4oikaDUjKo5g4JikcA7VItAUDQSxMthTahWgaB4JFg7o1oGgiqRIDdU60BQLRJYQ/UQCKpGgqVQOL79HLQMBNUjQWpG9RQImkSCVCjoIRC4PQVYknoKcBvIGqbGU4ZmMwlxlmZQb5pFGknz5bbWppfbSBSJoEgERSIoEsHlvdu2TdMJn8jmJ8fDhbIAAAAASUVORK5CYII=",
42
+ "Ub": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHySURBVHhe7drBUcMwFIRhhwKohjscqYAiUwFHuFMNDYSsEw2aTGzvk/QkOdpvhsE5EIk/kmNiDtM0nc5fsmKOdPq9PPBweD4PsPPnf7oeywpFIigSQZEIikRQJIIiERSJoEgERSIoEkGRCIpEUCSCIhEUiaBIBEUiKBJBkQiKRFAkgu67EXTfbYPuu5EUiaBIBEUiDBXp7ePnemQz3EpKCTVUpK/jy/zdGsp8nZS6ZHsUoq0Z/jqJfcGHvOIOcbSSFlgCBUNeAlgCgWm7Le3htUF72m6YvznQaNvNGijIioRBUwduCStqaVfckxXJMlAvUuacFClePXsKFc/VsgOSV9LeQqUGguyLya3Be3h3ywpU4t2t9xWVEyjIjgS9hioRCIpEgt5ClQoE8znpcljG6/v/5L4/8yaXqvQcXD4FiF9FTLLmibvkCgK3P0viycWvqrfSgQKXSFD7HOUVCNwiQa1QnoHANRLEJ06PUPF29ggE7pHAa0V5r6CgSiQoHapWIKgWCUqFin+2xrVY1UiQG6rmCgqqR4LUUC0CQZNIYA3VKhA0iwRLoXB8+zioHQiaRoKtFdU6EDSPBEu/fA+BoMv/BbhdUWuBUp7fwu1TgNIQ7d5WrGUXkVrrcrtZaLt1QpEIikRQJIIiEYrfd3s80/QHRtfoJ68+66IAAAAASUVORK5CYII=",
43
+ "V": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIWSURBVHhe7doxTgMxEIXhDQeggJaSipoeSk7AITlBSuipqShpoeACIS/sKkPErsbrGXvGnl+KtDQgPo29FmYzDMNu/4kWOiDtvn+/0Ghzvv8Bzr//2fgcLRRIjAKJUSAxCiRGgcQokBiZOCfdP76OT397frodn+YzdU76urn+99NDJpcbJogzRaVajXTx9n74aDS3/Gq1GkljqdHpsQSVjESnpxco9tttApmQKNDSslv79qFAS/tTibdb1hGAA5XzS3CgSiBlvd16WXrZR4AeoLKRUOtQIkioZSgxJNQqlCgSahFKHAlRqM8rXai7B30o1Xs3CnT5cYSTigK9bI9w0qn/PYkuOTphUtElRydMquzDJCc6QRp7FJ0grT1KHQl538yLICHPUMWQkFeookjII1RxJOQNqgoS8gRVDQl5gaqKhGpB4ZkLVx0JWZ8oE0jIMpQZJFQSKiVTSEgLKmUPOs0cEtKeqNRM/OvNXBSIwtHM37tpZ2WiTCMhC1DmkVBtKBdIqCaUGyRUC8oVEqoB5Q4JUSiNe73TVO/dtNO+15syfZjkRJccnTCpzB8mOWnf6yH3SEh7M28CCWlCNYOEtKCaQkIaUM0hIWmoJpGQJFSzSEgKqmkkNAeFZy5c80god6K6QEI5UN0gIQqVUjdIKXvQaV1N0trc/6mk+3s3KwUSo0BiFEiMAolRIDEKJEau793KNAw/I0AwLpLA1aEAAAAASUVORK5CYII=",
44
+ "Y": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIvSURBVHhe7do9TsNAEIZhhwNQQEtJRU0PJSdAnJGCmhJ6SkRFSQsFFwj5gldMItuZ3ZnFM+t5pYilCdKj8fqPVdd1680nmmiLtP7+/aVGq+PNH3D+/Uf9OpookBgFEqNAYhRIjAKJUSAxYl0nfV2c96vdTt7e+9V43OuY69uXfrXb0/1lvxourpOMVISECeJMUU6YmENTM1dFSGOHn7SxQ27uspDo9GhDUSBrE5U9STWgLAOh4qcAFGhqfzp09pECmT67aUyU9QlKFSMhCZQXICRCQiVQnoCQGAnlQHkDQipIiAPlEQipIaEpKK9ASBUJDUF5BkLqSIhCXd34BkJV37tRoOdHn0Co2ns3eog9vN5tf9IJ08rt8yQKRCeIex1lLXWkoU2ac3lgOVWkqbOYZyg1JM5p3iuUChIHKOURSoyUA5TyBpWFBBCKUgKU8gRVPEkSoJQXqCIkDaCUByjRniQFSlmHEiHt71GSLEOJkLSzCmXyH0spEIUbyu0NrjRrE2USCVmCMouErECZRkIWoMwjobmhXCChOaHcIKG5oFwhoTmg3CEhCvV5Vh+q6nu32lGg048/OO1M3pbkRA85OmFamb0tyYlOUK09yj0Sqr2ZN4GEakI1g4RqQTWFhGpANYeEtKGaREKaUM0ioTEorHPgmkZCGhPVPBKSQi0CCVGo3BaBlLsH7beYSZLk/inAYl9OWiuQGAUSo0BiFEiMAolRIDEKJEau37v9T133A/cUFsjTSnS4AAAAAElFTkSuQmCC",
45
+ "Z": "iVBORw0KGgoAAAANSUhEUgAAAEkAAABJCAYAAABxcwvcAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHcSURBVHhe7drNTcNAEMVxhwKohjscqYAiqYAj3KmGBkJe4pUW5MQT79d74/lLSCYHY/0ytldaDtM0HU8/0Y3OSMefyy8tOjye/oD4+R/m4+hGgWQokAwFkqFAMhRIhgLJEM066eXtez762+f703y0XKyTSKJBwsSsTc2oqCbp2i03OhqkHIhtoiiQmIHQ8LdbKZD7txv7BKWGIakAoSFISkCoO5IaEOqKpAiEuiGpAqEuSMpAqDnS86s2EGq675YDfX1oAqFmK+5et5jsijsHUp6gVHUk9Yf0UlWRPAKhakhegVAVJM9AqBjJOxC6CwkgOcoegNBd66SEAhArUI91jOX8+fXmrX25m9dJe5mgVNEzSQkI17r1eouQMFHXxpixrdda/HZTqeQRQfMPE1uznH8JKH22Bobzu0cqmSCE87u+3UqBUm6RagEhl0g1gZA7pNpAyBVSCyDkBqkVEHKB1BIIySP12Ndruu/Wul77erIr7ta3WEp2xZ0D9djXk0PqNUF5UkgjgJAM0iggJIE0EgjRI40GQlRIAMlRGIAQ7SSxACFKJCYgRP1MYgBC1Ej/n1GjokZiaRf7biW531KqVSAZCiRDgWQokAwFkqFAMhRIhgLJkPS+W5+m6RfQoucBXsykVgAAAABJRU5ErkJggg==",
46
+ }
47
+
48
+
49
+ # Class
50
+ # ---------------------------------------------------------------------------
51
+ class RubikNotation:
52
+ """
53
+ Rubik Notation
54
+
55
+ Parameters
56
+ ----------
57
+ notation : str
58
+ Notation (abbreviated name)
59
+
60
+ is_counter_clockwise : bool | None, optional
61
+ Is counter clockwise, by default ``False``
62
+
63
+ note : str | None, optional
64
+ Note, by default ``None``
65
+ """
66
+
67
+ _BASIC_NOTATION: ClassVar[dict[str, str]] = {
68
+ "L": "Left",
69
+ "R": "Right",
70
+ "U": "Up",
71
+ "D": "Down",
72
+ "F": "Front",
73
+ "B": "Back",
74
+ "M": "Middle",
75
+ "E": "Equator",
76
+ "S": "Side",
77
+ "X": "x axis",
78
+ "Y": "y axis",
79
+ "Z": "z axis",
80
+ }
81
+ _COUNTER_CLOCKWISE_SYMBOL: ClassVar[str] = "'"
82
+
83
+ def __init__(
84
+ self,
85
+ notation: str,
86
+ is_counter_clockwise: bool = False,
87
+ /,
88
+ *,
89
+ note: str | None = None,
90
+ ) -> None:
91
+ """
92
+ Rubik Notation
93
+
94
+ Parameters
95
+ ----------
96
+ notation : str
97
+ Notation (abbreviated name)
98
+
99
+ is_counter_clockwise : bool | None, optional
100
+ Is counter clockwise, by default ``False``
101
+
102
+ note : str | None, optional
103
+ Note, by default ``None``
104
+ """
105
+
106
+ self.notation = notation.upper().strip()
107
+ self.is_counter_clockwise = is_counter_clockwise
108
+
109
+ self.direction = (
110
+ "Counter Clockwise" if self.is_counter_clockwise else "Clockwise"
111
+ )
112
+
113
+ long_notation_name = self._BASIC_NOTATION.get(self.notation[0], self.notation)
114
+ self.full_notation = long_notation_name + (
115
+ f" ({self.direction})" if is_counter_clockwise else ""
116
+ )
117
+
118
+ self.note = None if note is None else note
119
+
120
+ def __str__(self) -> str:
121
+ # return f"{self.__class__.__name__}({self.notation})"
122
+ notation = (
123
+ f"{self.notation}{self._COUNTER_CLOCKWISE_SYMBOL}"
124
+ if self.is_counter_clockwise
125
+ else self.notation
126
+ )
127
+ return notation
128
+
129
+ def __repr__(self) -> str:
130
+ return self.__str__()
131
+
132
+ @classmethod
133
+ def from_str(cls, string: str) -> Self:
134
+ if cls._COUNTER_CLOCKWISE_SYMBOL in string:
135
+ return cls(string[:-1].upper(), True)
136
+ return cls(string)
137
+
138
+
139
+ @final
140
+ class RubikNotations:
141
+ """
142
+ Constant Rubik Notations
143
+ """
144
+
145
+ U = RubikNotation("U")
146
+ D = RubikNotation("D")
147
+ L = RubikNotation("L")
148
+ R = RubikNotation("R")
149
+ F = RubikNotation("F")
150
+ B = RubikNotation("B")
151
+
152
+ M = RubikNotation("M", note=f"Rotate according to {L} face")
153
+ E = RubikNotation("E", note=f"Rotate according to {D} face")
154
+ S = RubikNotation("S", note=f"Rotate according to {F} face")
155
+
156
+ U_REV = RubikNotation("U", True)
157
+ D_REV = RubikNotation("D", True)
158
+ L_REV = RubikNotation("L", True)
159
+ R_REV = RubikNotation("R", True)
160
+ F_REV = RubikNotation("F", True)
161
+ B_REV = RubikNotation("B", True)
162
+
163
+ M_REV = RubikNotation("M", True, note=f"Rotate according to {L} face")
164
+ E_REV = RubikNotation("E", True, note=f"Rotate according to {D} face")
165
+ S_REV = RubikNotation("S", True, note=f"Rotate according to {F} face")
166
+
167
+ X = RubikNotation("x", note=f"Rotate according to {R} face")
168
+ Y = RubikNotation("y", note=f"Rotate according to {U} face")
169
+ Z = RubikNotation("z", note=f"Rotate according to {F} face")
170
+
171
+ X_REV = RubikNotation("x", True, note=f"Rotate according to {R} face")
172
+ Y_REV = RubikNotation("y", True, note=f"Rotate according to {U} face")
173
+ Z_REV = RubikNotation("z", True, note=f"Rotate according to {F} face")
174
+
175
+
176
+ class RubikAlgorithm:
177
+ """
178
+ Rubik Algorithm
179
+
180
+ Parameters
181
+ ----------
182
+ case : str
183
+ Case name
184
+
185
+ solution : str | list[str] | list[RubikNotation]
186
+ Solution/Algorithm
187
+
188
+ other_solutions : None | list[list[str]] | list[list[RubikNotation]] | list[Self], optional
189
+ Other Solution/Algorithm, by default ``None``
190
+
191
+ note : str | None, optional
192
+ Note, by default ``None``
193
+
194
+ picture : str | None, optional
195
+ Picture of case (in base64 format), by default ``None``
196
+ """
197
+
198
+ def __init__(
199
+ self,
200
+ case: str,
201
+ solution: str | list[str] | list[RubikNotation],
202
+ other_solutions: (
203
+ None | list[list[str]] | list[list[RubikNotation]] | list[Self]
204
+ ) = None,
205
+ note: str | None = None,
206
+ picture: str | None = None,
207
+ ) -> None:
208
+ """
209
+ Rubik Algorithm
210
+
211
+ Parameters
212
+ ----------
213
+ case : str
214
+ Case name
215
+
216
+ solution : str | list[str] | list[RubikNotation]
217
+ Solution/Algorithm
218
+
219
+ other_solutions : None | list[list[str]] | list[list[RubikNotation]] | list[Self], optional
220
+ Other Solution/Algorithm, by default ``None``
221
+
222
+ note : str | None, optional
223
+ Note, by default ``None``
224
+
225
+ picture : str | None, optional
226
+ Picture of case (in base64 format), by default ``None``
227
+ """
228
+
229
+ self.case = case.strip()
230
+ self.solution = self._convert_solution(solution)
231
+
232
+ self.other_solutions = other_solutions
233
+ self.note = note
234
+ self.picture = picture
235
+
236
+ def __str__(self) -> str:
237
+ # return f"{self.__class__.__name__}({self.case})"
238
+ # return f"{self.solution}"
239
+ return f"{self.__class__.__name__}({self.case} - {self.solution})"
240
+
241
+ def __repr__(self) -> str:
242
+ return self.__str__()
243
+
244
+ def _convert_solution(
245
+ self, solution: str | list[str] | list[RubikNotation], /
246
+ ) -> list[RubikNotation]:
247
+ if isinstance(solution, str):
248
+ sols: list[str] = solution.replace("(", "").replace(")", "").split()
249
+ return [RubikNotation.from_str(x) for x in sols]
250
+
251
+ if isinstance(solution, list) and len(solution) > 0:
252
+ if isinstance(solution[0], RubikNotation):
253
+ return cast(list[RubikNotation], solution)
254
+ else:
255
+ return [RubikNotation.from_str(x) for x in solution] # type: ignore[arg-type]
256
+
257
+ raise ValueError("Wrong value type")
258
+
259
+
260
+ class Cross(RubikAlgorithm):
261
+ """Cross Algorithm"""
262
+
263
+ pass
264
+
265
+
266
+ class F2L(RubikAlgorithm):
267
+ """F2L Algorithm"""
268
+
269
+ pass
270
+
271
+
272
+ class OLL(RubikAlgorithm):
273
+ """OLL Algorithm"""
274
+
275
+ pass
276
+
277
+
278
+ class PLL(RubikAlgorithm):
279
+ """PLL Algorithm"""
280
+
281
+ pass
282
+
283
+
284
+ class PLLs:
285
+ """
286
+ Collection of PLLs
287
+ """
288
+
289
+ Aa = PLL(
290
+ "Aa",
291
+ "x (R' U R') D2 (R U' R') D2 R2",
292
+ other_solutions=[
293
+ PLL(
294
+ "Aa",
295
+ "(r U r' U') (r' F r2 U') r' U' (r U r' F')",
296
+ note="Headlight at Left (solve like T perm but is `r` instead of `R`)",
297
+ )
298
+ ],
299
+ note="Headlight at Back",
300
+ picture=PLL_PIC["Aa"],
301
+ )
302
+
303
+ Ab = PLL(
304
+ "Ab",
305
+ "x (R2 D2) (R U R') D2 (R U' R)",
306
+ note="Headlight at Right",
307
+ picture=PLL_PIC["Ab"],
308
+ )
309
+
310
+ E = PLL(
311
+ "E", "x' (R U' R' D) (R U R' D') (R U R' D) (R U' R' D')", picture=PLL_PIC["E"]
312
+ )
313
+
314
+ Ua = PLL(
315
+ "Ua",
316
+ "(R U' R U) R U (R U' R' U') R2",
317
+ note="Finished face at Back",
318
+ picture=PLL_PIC["Ua"],
319
+ )
320
+
321
+ Ub = PLL(
322
+ "Ub",
323
+ "R2 U (R U R' U') R' U' (R' U R')",
324
+ note="Finished face at Back",
325
+ picture=PLL_PIC["Ub"],
326
+ )
327
+
328
+ Z = PLL(
329
+ "Z",
330
+ "M2 U' M2 U' M' U2 M2 U2 M' U2",
331
+ other_solutions=[
332
+ PLL(
333
+ "Z",
334
+ "M' U' M2 U' M2 U' M' U2 M2 U",
335
+ note="Switch edge at Right and Front",
336
+ )
337
+ ],
338
+ note="Switch edge at Left and Front",
339
+ picture=PLL_PIC["Z"],
340
+ )
341
+
342
+ H = PLL("H", "(M2 U' M2) U2 (M2 U' M2)", picture=PLL_PIC["H"])
343
+
344
+ T = PLL(
345
+ "T",
346
+ "(R U R' U') (R' F R2 U') R' U' (R U R' F')",
347
+ note="Headlight at Left",
348
+ picture=PLL_PIC["T"],
349
+ )
350
+
351
+ F = PLL(
352
+ "F",
353
+ "R' U' F' (R U R' U') (R' F R2 U') (R' U' R U) (R' U R)",
354
+ note="Finished face at Left",
355
+ picture=PLL_PIC["F"],
356
+ )
357
+
358
+ Ja = PLL(
359
+ "Ja",
360
+ "(R' U L' U2) (R U' R' U2 R) L U'",
361
+ note="Finished face at Front",
362
+ picture=PLL_PIC["Ja"],
363
+ )
364
+
365
+ Jb = PLL(
366
+ "Jb",
367
+ "(R U R' F') (R U R' U') R' F R2 U' R' U'",
368
+ note="Finished face at Left",
369
+ picture=PLL_PIC["Jb"],
370
+ )
371
+
372
+ Ra = PLL(
373
+ "Ra",
374
+ "(R U R' F') (R U2' R' U2') (R' F R U) (R U2 R' U')",
375
+ note="Headlight at Left",
376
+ picture=PLL_PIC["Ra"],
377
+ )
378
+
379
+ Rb = PLL(
380
+ "Rb",
381
+ "(R' U2 R U2') R' F (R U R' U') R' F' R2 U'",
382
+ note="Headlight at Front",
383
+ picture=PLL_PIC["Rb"],
384
+ )
385
+
386
+ V = PLL(
387
+ "V",
388
+ "(R' U R' d') (R' F' R2 U') (R' U R' F) R F",
389
+ note="Block at Left Front",
390
+ picture=PLL_PIC["V"],
391
+ )
392
+
393
+ Y = PLL(
394
+ "Y",
395
+ "F (R U' R' U') (R U R' F') (R U R' U') (R' F R F')",
396
+ note="2 pieces have same color at Front and Right",
397
+ picture=PLL_PIC["Y"],
398
+ )
399
+
400
+ Na = PLL(
401
+ "Na",
402
+ "(R U R' U) (R U R' F') (R U R' U') (R' F R2 U') R' U2 (R U' R')",
403
+ note="Edges need to swap at Left and Right",
404
+ picture=PLL_PIC["Na"],
405
+ )
406
+
407
+ Nb = PLL(
408
+ "Nb",
409
+ "z R' U' R D' R2 U R' D U' R D' R2 U R' D z'",
410
+ note="Edges need to swap at Left and Right",
411
+ picture=PLL_PIC["Nb"],
412
+ )
413
+
414
+ Ga = PLL("Ga", "R2 u R' U R' U' R u' R2 y' R' U R", picture=PLL_PIC["Ga"])
415
+
416
+ Gb = PLL(
417
+ "Gb",
418
+ "(R' U' R) y R2 u (R' U R U' R) u' R2",
419
+ other_solutions=[
420
+ PLL("Gb", "R' U' R U D' R2 U R' U R U' R U' R2 D", note="Headlight at Left")
421
+ ],
422
+ note="Headlight at Left",
423
+ picture=PLL_PIC["Gb"],
424
+ )
425
+
426
+ Gc = PLL(
427
+ "Gc",
428
+ "R2 F2 R U2 R U2 R' F (R U R' U') R' F R2",
429
+ note="Headlight at Right",
430
+ picture=PLL_PIC["Gc"],
431
+ )
432
+
433
+ Gd = PLL(
434
+ "Gd",
435
+ "R U R' U' D R2 U' R U' R' U R' U R2 D'",
436
+ note="Headlight at Left",
437
+ picture=PLL_PIC["Gd"],
438
+ )
439
+
440
+
441
+ class Rubik3x3:
442
+ PLL = PLLs()