-
Notifications
You must be signed in to change notification settings - Fork 0
/
レベルアップ問題集Python3 クラス・構造体メニュー
1167 lines (963 loc) · 50.5 KB
/
レベルアップ問題集Python3 クラス・構造体メニュー
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<構造体の更新>
step1 構造体の作成
# utf-8
N = int(input())
for _ in range(N):
n, o, b, s = input().split()
print("User{")
print("nickname : " + n)
print("old : " + o)
print("birth : " + b)
print("state : " + s)
print("}")
'''
生徒の情報を受け取って、各言語の文字列の出力を用いて、指定された形式で出力を行ってください。
今回の問題のように、「ある人や物に関する情報をまとめて管理したい」という考え方が構造体を使う上で大切な考え方です。
n 行にわたる入力を for 文を用いて受け取ります。
for 文の変数 _ ですが、これは「この変数は明示的に使わない」ということを意味する変数名です。
しかし、慣習的な書き方であるので、「必ずこう書かなくてはならない」ということではありません。
n, o, b, s = input().split() は input().split() で入力値を半角スペースで区切ってリストを生成して、アンパックで左辺の変数にそれぞれ代入しています。
【 アンパックについて 】
右辺に置かれたタプルやリストなどのオブジェクトの要素の数だけ左辺に変数を置くと、オブジェクトの要素順に左辺の変数に代入される
'''
step2 構造体の検索
# utf-8
class Student:
def __init__(self, name, old, birth, state):
self.name = name
self.old = old
self.birth = birth
self.state = state
n = int(input())
roster = [None] * n
for i in range(n):
name, old, birth, state = input().split()
roster[i] = Student(name, old, birth, state)
k = input()
for student in roster:
if student.old == k:
print(student.name)
break
'''
問題文の指示通り生徒のデータを構造体でまとめて管理しましょう。構造体に関する文法は言語によって異なるので、自分の使っている言語の構造体に関する文法を参考にしましょう。
1 人の生徒に関する情報をまとめた構造体 student をクラス全員分作成し、管理する必要があるため、構造体の配列を用いてクラス全員のデータを管理しましょう。
作成した構造体は変数ではなく型として扱われます。イメージとしては整数型・文字列型...といったものと同じです。
全ての生徒の情報を受け取ったのち、各生徒の年齢が K と一致するかどうかを調べ、一致した場合名前を出力すれば良いです。
「クラス」を用いることで、生徒の情報を管理します。
【 流れ・考え方 】
[None] * n では、要素数が n であるリストを生成しています。
入力値は input().split() でリストを生成して、左辺の変数に代入しています (アンパックについては前問の解説で説明しました)。
リスト roster の要素が Student のインスタンスになるように代入します。
k と各インスタンスの old が一致するか判定して、一致したインスタンスの name を出力します。
【 クラスの基本的事項 】
ここでは、Python3 でクラスをどのように使うかを説明します。
【 クラス定義 】
クラスは class クラス名: として定義します。
「クラスの中身」 はインデントを 1 つ下げて書きます。
Python3 では、「クラスの中身」のことを「属性」と言います。
属性には以下の 2 種類があります。
プロパティ
クラス内で定義される変数のこと
使い方は変数と大きく変わらない。
メソッド
クラス内で定義される関数のこと
定義の構文は関数と大きく変わらない
【 コンストラクタ 】
コンストラクタはメソッドの 1 種です。
コンストラクタの主な役割は、クラスのプロパティを用意することです。
コンストラクタのメソッド名は __init__ と決まっています。
クラスを実際に使うときに、「インスタンス化」 (次の節で説明) という作業を行うのですが、その際に呼び出されるメソッドです。
【 インスタンス 】
インスタンスとは、「実体」のような意味を持ちます。
実際にクラスを使うときに「インスタンス化」という作業を行い、インスタンスを生成します。
【 インスタンス化 】
インスタンス化はクラスを実際に使うときに行う作業のことです。
インスタンス化は インスタンス名 = クラス(引数) のように行います。
【 インスタンスの利用 】
インスタンスは、生成時に指定した インスタンス名 を使って取り扱います。
クラスで定義されている属性を利用する際、主にインスタンスから利用します。
【 属性へのアクセス 】
インスタンス名.属性名 のようにしてアクセスします。
プロパティ
インスタンス名.プロパティ名 のように書くことで、プロパティ名に対応する値を取得することができます。
メソッド
インスタンス名.メソッド名 のように書くことで、メソッド名に対応するメソッドを利用することができます。
【 self について 】
コンストラクタなどに出てくる self とは何か、について説明します。
self は簡単に言えば、「どのインスタンスの属性か」を特定するために使われるものです。
【 もう少し詳しい説明 】
クラスからはいくつものインスタンスを生成することができますが、すべてのインスタンスにおいて同名の属性が定義できます。
この状況下で単純に name としたときに、どのインスタンスの name かが分かりません。
そこで、インスタンス名.name とアクセスして「どのインスタンスの name か」という問題を解決します。
同様にクラス内でも self.name とすることで、問題を解決しています。
'''
step3 構造体の整列
# utf-8
class Student:
def __init__(self, name, old, birth, state):
self.name = name
self.old = old
self.birth = birth
self.state = state
n = int(input())
roster = [None] * n
for i in range(n):
name, old, birth, state = input().split()
roster[i] = Student(name, old, birth, state)
for i in range(n):
for j in range(i+1, n):
if roster[i].old > roster[j].old:
roster[i], roster[j] = roster[j], roster[i]
for student in roster:
print(student.name, student.old, student.birth, student.state)
'''
問題文の指示通り生徒のデータを構造体でまとめて管理しましょう。構造体に関する文法は言語によって異なるので、自分の使っている言語の構造体に関する文法を参考にしましょう。
1 人の生徒に関する情報をまとめた構造体 student をクラス全員分作成し、管理する必要があるため、構造体の配列を用いてクラス全員のデータを管理しましょう。
作成した構造体は変数ではなく型として扱われます。イメージとしては整数型・文字列型...といったものと同じです。
全ての生徒の情報を受け取ったのち、各生徒を表す構造体の年齢を参照して昇順に並び替えるような処理を実装すれば良いです。
※ソートを自分で行う
クラスで生徒の情報を管理します。
クラスについては「構造体の検索」の解説で詳しく説明しているので、興味のある方はご覧ください。
ここでは、ソートの仕方を説明します。
今回はある範囲内での最小値を見つけ、ソート済みのリストの末尾に追加するソート方法を取っています。
【 ソートの説明 】
まず、n 回繰り返す for 文(0 ≦ i < n)を記述します。
上記の for 文 内で新たに for 文(i < j < n) を書き、2 重ループにします。
i < j < n の範囲で最小値を見つけ出して、インデックス i の要素とすることを繰り返してソートを完了させます。
-- イメージ --
[(ソート済み) (インデックス i) | (ここから最小値を見つけて (インデックス i) の要素とする)]
| が、だんだん右に進む
<別解>
class Student:
def __init__(self, name, old, birth, state):
self.name = name
self.old = old
self.birth = birth
self.state = state
n = int(input())
roster = [None] * n
for i in range(n):
name, old, birth, state = input().split()
roster[i] = Student(name, int(old), birth, state)
sorted_by_old = sorted(roster, key=lambda x: x.old)
for student in sorted_by_old:
print(student.name, student.old, student.birth, student.state)
ラムダ式を用いる方法です。
ソート自体は sorted 関数を使います。
sorted 関数は、引数で key= を指定することで、「なにを基準に並べ替えるか」を決めることができます。
key=lambda x: x.old とすることで、「roster が保持している Student クラスの old 」を基準に並べ替えるように指定できます。
【 lambda とはなにか 】
lambda は名前をつけないで関数を生成するために使われます。
書き方は lambda 引数: 返り値 になります。
今回の lambda x: x.old は以下の関数を利用することと同等です。
def func(x):
return x.old
x.old で Student クラスのインスタンスの年齢を取得できるので、key で年齢を指定することができます。
'''
final 構造体の更新
# utf-8
# utf-8
class Student:
def __init__(self, name, old, birth, state):
self.name = name
self.old = old
self.birth = birth
self.state = state
def change_name(self, name):
self.name = name
n, k = [int(x) for x in input().split()]
roster = [None] * n
for i in range(n):
name, old, birth, state = input().split()
roster[i] = Student(name, old, birth, state)
for i in range(k):
index, new_name = input().split()
roster[int(index) - 1].change_name(new_name)
for student in roster:
print(student.name, student.old, student.birth, student.state)
'''
全生徒の情報をまとめた構造体 student の配列を作成し、 changeName に引数として更新後の名前と更新する生徒の構造体を渡して、対象の生徒の名前 = 更新後の名前といった具合に更新を行えば良いです。
これらの処理を行う上で注意しなければならないのが、「値渡し」と「参照渡し」です。宣言した変数がローカルなものである(グローバル変数でない)場合、関数に変数を渡すと、それは値渡しとなります。構造体の宣言はグローバルなものですが、構造体を元として宣言した変数はローカルなものとなります。
値渡しの場合、引数として受け取った変数を更新しても、引数として渡した元の変数は変化しません。そのため、 changeName に構造体を参照渡しする必要があります。参照渡しの使用は言語によって異なるので、自分の使っている言語の文法に従ってください。
クラスを定義して、生徒の情報を管理します。
クラスについては「構造体の検索」の解説で詳しく説明しているので、興味のある方はご覧ください。
クラス内でメソッドを定義する方法は、普段、関数を定義する方法と変わりません。
リストのインデックスは 0 から始まることに注意してください。
'''
<静的メンバ>
step1 クラスの作成
# utf-8
class Employee:
def __init__(self, number, name):
self.number = number
self.name = name
def getnum(self):
return self.number
def getname(self):
return self.name
n = int(input())
roster = []
for _ in range(n):
values = input().split()
query = values[0]
if query == "make":
number = int(values[1])
name = values[2]
roster.append(Employee(number, name))
elif query == "getnum":
index = int(values[1]) - 1
number = roster[index].getnum()
print(number)
else:
index = int(values[1]) - 1
name = roster[index].getname()
print(name)
'''
今回の問題で挙げたクラスとは、構造体にその構造体内の変数を扱う関数を付属したものです。これを用いることで、構造体よりもより大きなものをひとまとめにすることができるようになります。
今回の問題を構造体で解こうとした場合、社員の構造体 employee と、関数 getnum , getname を作成する必要があり、構造体と関数の間の値のやり取りが多くなってしまいます。
クラス内では関数と値の複雑なやり取りをする必要がないため、社員クラスの中に getnum と getname を実装することで、構造体と関数を別に作るよりも簡潔なコードになります。
クラスの作成や宣言の文法は言語によって異なるので、お使いの言語の文法に従ってください。
入力に応じてクラスの関数を呼び出したり、クラスの実体(オブジェクト)を作成することでこの問題を解くことができます。
【 解答の流れ・考え方 】
クラスを用いて従業員の情報を管理します。
クラスについては「構造体の検索」の解説で詳しく説明しているので、興味のある方はご覧ください。
入力に対する応答の箇所は文字列の分割を利用するなどして、必要な情報を抽出する必要があります。
以下の 2 点に注意が必要です。
n は与えられる社員の情報の数ではない
つまり、インスタンスを格納するリストは [None] * n のように初期化することができないので、append メソッドを利用して要素を追加する必要がある
リストのインデックスは 0 から始まる
つまり、i 番目の要素のインデックスは i-1 になる
【 ゲッターを定義せずにプロパティを取得する getattr 】
getnum のようなメソッドをゲッターと呼ぶことがあります。
Python3 では、getattr 関数を用いると、このゲッターを定義せずにプロパティを取得することができます。
使い方は getattr(インスタンス, プロパティ名) です。
具体例として number = roster[index].getnum() を getattr を用いて書き換えることを考えます。
まず、インスタンス は roster[index] です。
次に、プロパティ名 は number です。プロパティ名は文字列で指定することに注意してください。
以上のことに注意して、getattr メソッドを用いて number = roster[index].getnum() の右辺を書き換えると getattr(roster[index], "number") となります。
これで、getnum メソッドを定義しなくてもプロパティ number を取得できます。
name = roster[index].getname() に関しても同様にできるので、やってみてください。
'''
step2 コンストラクタ
# utf-8
# utf-8
class Employee:
def __init__(self, number, name):
self.number = number
self.name = name
def getnum(self):
return self.number
def getname(self):
return self.name
n = int(input())
roster = []
for _ in range(n):
values = input().split()
query = values[0]
if query == "make":
number = int(values[1])
name = values[2]
roster.append(Employee(number, name))
elif query == "getnum":
index = int(values[1]) - 1
number = roster[index].getnum()
print(number)
else:
index = int(values[1]) - 1
name = roster[index].getname()
print(name)
'''
問題の指示通りクラスを作成します。クラスにはコンストラクタというものが存在します。コンストラクタとは、クラスの実体(オブジェクト)を作成するときに行われる処理をクラス内で宣言できる機能のことです。
コンストラクタの設定方法も言語間で差があるので、お使いの言語の仕様に従ってください。
今回は、「クラスのメンバ変数の値を設定する」という操作をオブジェクト作成時に行いたいので、クラスの各メンバ変数に設定したい値を引数として、代入を行うコンストラクタを実装すれば良いです。
クラスを用いて従業員の情報を管理します。
クラスについては「構造体の検索」の解説で詳しく説明しているので、興味のある方はご覧ください。
入力に対する応答の箇所は文字列の分割を利用するなどして、必要な情報を抽出する必要があります。
以下の 2 点に注意が必要です。
n は与えられる社員の情報の数ではない
つまり、インスタンスを格納するリストを [None] * n のように初期化することができないので、append メソッドを利用して要素を追加する必要がある
リストのインデックスは 0 から始まる
つまり、i 番目の要素のインデックスは i-1 になる
'''
step3 クラスのメンバの更新
# utf-8
class Employee:
def __init__(self, number, name):
self.number = number
self.name = name
def getnum(self):
return self.number
def getname(self):
return self.name
def change_num(self, number):
self.number = number
def change_name(self, name):
self.name = name
n = int(input())
roster = []
for _ in range(n):
values = input().split()
query = values[0]
if query == "make":
number = int(values[1])
name = values[2]
roster.append(Employee(number, name))
else:
index = int(values[1]) - 1
if query == "getnum":
number = roster[index].getnum()
print(number)
elif query == "getname":
name = roster[index].getname()
print(name)
elif query == "change_num":
new_num = int(values[2])
roster[index].change_num(new_num)
else:
new_name = values[2]
roster[index].change_name(new_name)
'''
クラス内の変数を操作するような関数を宣言する場合は、同じクラスの中に関数を作成することで、余計な値の受け渡しの手間を省くことができます。
今回の問題の場合、change_num と change_name を class employee の外で関数として定義した場合は、クラス内の変数 number , name をクラスの外に渡し、更新した値をクラスに返すといった処理を行う必要がありますが、
class employee の中で定義した場合は、 number , name を普通の変数として用いることができるので、変数に値を代入するだけで更新をおこなうことができるため、コードが簡潔になります。
【 解答の流れ・考え方 】
クラスを用いて従業員の情報を管理します。
クラスについては「構造体の検索」の解説で詳しく説明しているので、興味のある方はご覧ください。
入力に対する応答の箇所は文字列の分割を利用するなどして、必要な情報を抽出する必要があります。
以下の 2 点に注意が必要です。
n は与えられる社員の情報の数ではない
つまり、インスタンスを格納するリストを [None] * n のように初期化することができないので、append メソッドを利用して要素を追加する必要がある
リストのインデックスは 0 から始まる
つまり、i 番目の要素のインデックスは i-1 になる
プロパティを変更するための構文は、変数への代入とよく似ており、self.プロパティ名 = 新しい値 のように書きます。
【 セッターを定義せずにプロパティを更新する setattr 】
change_num のようなメソッドをセッターと呼ぶことがあります。
Python3 では、setattr 関数を用いると、このセッターを定義せずにプロパティを更新することができます。
使い方は setattr(インスタンス, プロパティ名, 新しい値) です。
具体例として roster[index].change_num(new_num) を setattr を用いて書き換えることを考えます。
まず、インスタンス は roster[index] です。
次に、プロパティ名 は number です。プロパティ名は文字列で指定することに注意してください。
さらに、新しい値 は new_num です。
以上のことに注意して、setattr メソッドを用いて roster[index].change_num(new_num) を書き換えると setattr(roster[index], "number", new_num) となります。
これで、setnum メソッドを定義しなくてもプロパティ number を更新できます。
roster[index].change_name(new_name) に関しても同様にできるので、やってみてください。
'''
step4 クラスの継承
# utf-8
class Customer:
def __init__(self):
self.sum = 0
def take_food(self, price):
self.sum += price
def take_softdrink(self, price):
self.sum += price
def take_alcohol(self, price):
# 何もしない (却下)
pass
def get_sum(self):
return self.sum
class Adult(Customer):
def __init__(self):
super().__init__()
self.alcohol = False
def take_food(self, price):
if self.alcohol:
self.sum += price - 200
else:
self.sum += price
def take_alcohol(self, price):
self.sum += price
self.alcohol = True
n, k = [int(x) for x in input().split()]
customers = [None] * n
for i in range(n):
age = int(input())
if age >= 20:
customers[i] = Adult()
else:
customers[i] = Customer()
for _ in range(k):
values = input().split()
index = int(values[0]) - 1
order = values[1]
price = int(values[2])
if order == "food":
customers[index].take_food(price)
elif order == "softdrink":
customers[index].take_softdrink(price)
else:
customers[index].take_alcohol(price)
for customer in customers:
print(customer.get_sum())
'''
問題文にもある通り、paiza 国における未成年と成人済のお客さんを次のようなクラスに見立てることで、注文に応じてクラスの関数を呼び出すことで全ての処理ができるということはなんとなくわかると思います。
未成年
変数
そのお客さんの会計金額
関数
ソフトドリンクの注文
値段を値段を引数として受け取り、会計金額にその値段を足す
食事の注文
値段を値段を引数として受け取り、会計金額にその値段を足す
お酒の注文
何もしない
成年済
変数
そのお客さんの会計金額
お酒を頼んだかどうかを保持する変数
関数
ソフトドリンクの注文
値段を引数として受け取り、会計金額にその値段を足す
食事の注文
値段を引数として受け取り、会計金額にその値段を足す
もし既にお酒を頼んでいたら、会計を -200 円する
お酒の注文
値段を引数として受け取り、会計金額にその値段を足す
お酒を頼んだことを変数に覚えておく
この 2 つのクラスをそれぞれ実装しても良いのですが、よく見ると 2 つのクラスには似た箇所が多いことがわかります。
かつ、成年済のクラスには未成年のクラスの機能が全て備わっていることがわかります。
このように、1 つのクラス(未成年)の機能を包括している新しいクラス(成年済)を作成するときに役立つのが継承という機能です。継承を行うことで、作成する新しいクラスには、継承元と異なる部分や追加する部分のみを書けばよくなるので、コードの無駄を省き、バグの可能性を減らすことができます。
継承するための記述は言語によって異なるので、お使いの言語の仕様に従うようにしてください。
クラスを用いてお客さんの情報を管理します。
クラスについては「構造体の検索」の解説で詳しく説明しているので、興味のある方はご覧ください。
【 解答の流れ・考え方 】
方針のように、飲酒不可能なお客さんのクラスをまず作成して、そのクラスを継承する形で、飲酒可能なお客さんのクラスを作成します。
20 歳未満のお客さんがアルコールを注文したときの注文の取り消し方は以下の方法が挙げられます。
20 歳未満のお客さんのクラス内で処理を実行しない
この方法を実装するために、「何もしないメソッド」を定義する必要があります。
この「なにもしないメソッド」は「なにもしない」ことを表す pass 文を用いて書きます。
【 クラス追加説明 (継承) 】
ここでは、「構造体の検索」の解説での内容に加えて「継承」についての説明をします。
【 継承 】
【 継承とは何か 】
継承とは端的に言えば、既存のクラスの機能を拡張することです。
今回の場合では、飲酒できない客に「飲酒できる」という機能を拡張する目的で継承します。
継承元のクラスを「親クラス」や「スーパークラス」と言います。
継承したクラスを「子クラス」や「サブクラス」と言います。
【 継承でできること 】
継承すると、サブクラスはスーパークラスの属性をすべて使うことができ、なおかつ、属性を追加することができます。
また、スーパークラスのメソッドの処理内容をサブクラスにおいて上書きして使うことができます。これをオーバーライドと言います。
※ スーパークラスのメソッドが変更されるわけではなく、サブクラスでスーパークラスと同名のメソッドをサブクラス独自のメソッドとして定義できる
できることをまとめると
スーパークラスのプロパティを使える
スーパークラスのメソッドを使える
スーパークラスのメソッドの処理内容を上書きできる
サブクラス独自のプロパティを用意できる
サブクラス独自のメソッドを用意できる
【 継承の仕方 】
クラス定義の際に、以下のようにします。
class サブクラス名(スーパークラス名):
上記のように書くことで スーパークラス を継承した サブクラス が定義できます。
【 スーパークラスのメソッドの呼び出し 】
スーパークラスのメソッドの呼び出し方法を紹介します。
サブクラスからスーパークラスのメソッドを呼び出すときは super() を使います。
【 どのようなときに使うのか 】
たとえば、今回の場合は、サブクラス Adult でも変数 sum を使うので、初期化を行いたいと考えます。
変数 sum の初期化の処理はスーパークラスのコンストラクタで書いているので、サブクラスでも同じ処理を書いたら二度手間です。
そこで、サブクラスからスーパークラスのコンストラクタを呼び出せたら便利だと気付きます。
よって、サブクラスのコンストラクタ内で super().__init__() としてスーパークラスのコンストラクタを呼び出しています。
今回は、処理の内容が少ないので、ありがたみが感じづらいですが、処理内容が多いときは、ありがたみが実感できるかと思います。
'''
step5 デフォルト引数
# utf-8
class Customer:
def __init__(self):
self.sum = 0
def take_food(self, price):
self.sum += price
def take_softdrink(self, price):
self.sum += price
def take_alcohol(self, price=500):
# 何もしない (却下)
pass
def get_sum(self):
return self.sum
class Adult(Customer):
def __init__(self):
super().__init__()
self.alcohol = False
def take_food(self, price):
if self.alcohol:
self.sum += price - 200
else:
self.sum += price
def take_alcohol(self, price=500):
self.sum += price
self.alcohol = True
n, k = [int(x) for x in input().split()]
customers = [None] * n
for i in range(n):
age = int(input())
if age >= 20:
customers[i] = Adult()
else:
customers[i] = Customer()
for _ in range(k):
values = input().split()
index = int(values[0]) - 1
order = values[1]
if order == "0":
customers[index].take_alcohol()
else:
price = int(values[2])
if order == "food":
customers[index].take_food(price)
elif order == "softdrink":
customers[index].take_softdrink(price)
else:
customers[index].take_alcohol(price)
for customer in customers:
print(customer.get_sum())
'''
未成年のお客さんを表すクラスを宣言したのち、そのクラスを継承して成年済のお客さんのクラスを宣言します。
クラス内の関数の引数にはデフォルト値が設定できます。デフォルト値を設定する変数を先に記述する必要があるので気をつけてください。
クラスを用いてお客さんの情報を管理します。
クラスについては「構造体の検索」の解説で詳しく説明しているので、興味のある方はご覧ください。
継承については前問の「クラスの継承」で詳しく説明しているので、興味のある方はご覧ください。
【 流れ・考え方 】
今回は、ビールの値段の 500 円を take_alcohol メソッドの仮引数 price のデフォルト値に設定します。
また、Python では、入力を行単位で受け取るので、前問と同様に注文の入力値を処理しようとするとエラーが発生します。
なぜかというと、ビールの注文は お客さんの番号 0 で入力されますが、それ以外の注文は お客さんの番号 注文の種類 値段 と入力されるため、input().split() で生成されるリストの要素数が常に一定ではないからです。
そこで、注文がビールだったときの処理をしてから、他の注文の処理をすることで、エラーの発生を回避します。
【 デフォルト引数について 】
デフォルト引数について少し説明します。
【 関数定義のモデル 】
下記のモデルを前提として説明します。
def func(仮引数_1, ... , 仮引数_n):
【 デフォルト引数とは 】
関数は仮引数にデフォルト値を設定しておくことができます。
関数呼び出し時に特に指定がないと、仮引数にはデフォルト値が設定されます。
指定があれば、デフォルト値ではなく、その値が設定されます。
【 デフォルト値の設定の方法 】
仮引数_d_i=デフォルト値 のようにします。
関数定義のモデルは以下のようになります。
def func(仮引数_1, ... , 仮引数_n, 仮引数_d_1=デフォルト値, ... , 仮引数_d_m=デフォルト値):
注意点
デフォルト値が設定されていない仮引数の前に、デフォルト値が設定された仮引数を置くことはできません。
たとえば、以下の場合はエラーが発生します。
def func(仮引数_d_m_1=デフォルト値, 仮引数_1, ... , 仮引数_n):
'''
final 静的メンバ
# utf-8
# utf-8
class Customer:
n = 0
def __init__(self):
self.sum = 0
def take_food(self, price):
self.sum += price
def take_softdrink(self, price):
self.sum += price
def take_alcohol(self, price=500):
# 何もしない (却下)
pass
def accounting(self):
Customer.n += 1
print(self.sum)
class Adult(Customer):
def __init__(self):
super().__init__()
self.alcohol = False
def take_food(self, price):
if self.alcohol:
self.sum += price - 200
else:
self.sum += price
def take_alcohol(self, price=500):
self.sum += price
self.alcohol = True
n, k = [int(x) for x in input().split()]
customers = [None] * n
for i in range(n):
age = int(input())
if age >= 20:
customers[i] = Adult()
else:
customers[i] = Customer()
for _ in range(k):
values = input().split()
index = int(values[0]) - 1
order = values[1]
if order == "0":
customers[index].take_alcohol()
elif order == "A":
customers[index].accounting()
else:
price = int(values[2])
if order == "food":
customers[index].take_food(price)
elif order == "softdrink":
customers[index].take_softdrink(price)
else:
customers[index].take_alcohol(price)
print(Customer.n)
'''
未成年のお客さんのクラス・成年済のお客さんのクラスに会計をする関数を追加し、会計ごとに金額を出力し、退店者数を +1 する必要があります。
退店者数はグローバル変数で管理することもできますが、お客さんに関する情報なので、クラスの中で管理するのが望ましいです。そこでクラスの静的メンバを用いることができます。
静的メンバとは、オブジェクト間を超えて共有される変数や関数のことです。これを用いることで、お客さん一人一人が退店した人数を覚えておくよりも、効率的に退店した人数を記憶しておくことができます。
クラスを用いてお客さんの情報を管理します。
クラスについては「構造体の検索」の解説で詳しく説明しているので、興味のある方はご覧ください。
【 流れ・考え方 】
今回やることは大きく以下の 2 点です。
退店者を管理する
会計を処理するメソッドを定義する
【 退店者管理 】
方針に書いてあるように、退店したお客さんの数をインスタンス間を超えて管理できると便利です。
Python では、クラス変数を用いて解決します。
【 会計を処理するメソッド 】
このメソッドの役割は主に以下の 2 点です。
退店を管理する変数の値を更新する
退店するお客さんが支払う料金を出力する
更新については、クラス変数の値を更新することで解決できます。
出力はお客さんが注文した商品の値段の合計値を保持する変数の値を出力すればよいです。
【 クラス変数について 】
クラス変数について少し説明します。
【 定義の仕方 】
クラス変数はメソッド定義と同じインデントレベルで変数定義することで、使うことができます。
【 アクセスの仕方 】
アクセスの仕方はいくつかありますが、一般的な方法を紹介します。
クラス名.クラス変数名 とすると、アクセスできます。
'''
<ロボットの暴走>
step1 出口のない迷路
# utf-8
class Point:
def __init__(self, keyword, path_a, path_b):
self.keyword = keyword
self.path_a = path_a
self.path_b = path_b
def get_keyword(self):
return self.keyword
def get_path(self, direction):
if direction == 1:
return self.path_a
else:
return self.path_b
n, k, s = [int(x) for x in input().split()]
maze = [None] * n
for i in range(n):
values = input().split()
keyword = values[0]
path_a = int(values[1]) - 1
path_b = int(values[2]) - 1
maze[i] = Point(keyword, path_a, path_b)
now_point = maze[s-1]
ans = now_point.get_keyword()
for _ in range(k):
direction = int(input())
to = now_point.get_path(direction)
now_point = maze[to]
ans += now_point.get_keyword()
print(ans)
'''
複雑な問題を解く際には、できるだけ情報を整理することが大切となります。
今回の問題では、移動や情報の起点は迷路の中の地点となっており、情報がまとめられそうです。 また、特に各地点が変化を起こすことは無いので、クラスと構造体のうち、地点を表すには構造体が適していると考えられます。
入力と各地点の情報を参考にしながら、指示通り文字を集めていくことでこの問題を解くことができます。
クラスを用いて、ポイントを管理します。
クラスについては「構造体の検索」の解説で詳しく説明しているので、興味のある方はご覧ください。
クラス Point は以下の要素を持ちます。
キーワード
道_1 の行き先
道_2 の行き先
k 行にわたってどの道を選択するか与えられるので、クラス Point を利用して、次の行き先を決定します。
現在地は常に変数 now_point が持っているので、呪文は now_point.get_keyword() を用いて作成します。
上記の内容を流れでまとめると、以下のようになります。
どちらの道を選択するか (m_i)、が入力によって与えられる
ポイントnow_point の 道_m_i の行き先の番号 (p_i) を get_pathメソッド で確認する
移動する (now_point に maze[p_i] を代入する)
now_point のキーワードを呪文に追加する
'''
step2 RPG
# utf-8
# utf-8
class Hero:
def __init__(self, lv, hp, at, df, sp, cl, ft):
self.lv = lv
self.hp = hp
self.at = at
self.df = df
self.sp = sp
self.cl = cl
self.ft = ft
def levelup(self, hp, at, df, sp, cl, ft):
self.lv += 1
self.hp += hp
self.at += at
self.df += df
self.sp += sp
self.cl += cl
self.ft += ft
def muscle_training(self, hp, at):
self.hp += hp
self.at += at
def running(self, df, sp):
self.df += df
self.sp += sp
def study(self, cl):
self.cl += cl
def pray(self, ft):
self.ft += ft
def print(self):
print(self.lv, self.hp, self.at, self.df, self.sp, self.cl, self.ft)
n, k = [int(x) for x in input().split()]
heroes = [None] * n
for i in range(n):
lv, hp, at, df, sp, cl, ft = [int(x) for x in input().split()]
heroes[i] = Hero(lv, hp, at, df, sp, cl, ft)
for _ in range(k):
values = input().split()
index = int(values.pop(0)) - 1
event = values.pop(0)
if event == "levelup":
hp, at, df, sp, cl, ft = [int(x) for x in values]
heroes[index].levelup(hp, at, df, sp, cl, ft)
elif event == "muscle_training":
hp, at = [int(x) for x in values]
heroes[index].muscle_training(hp, at)
elif event == "running":
df, sp = [int(x) for x in values]
heroes[index].running(df, sp)
elif event == "study":
cl = int(values[0])
heroes[index].study(cl)
else:
ft = int(values[0])
heroes[index].pray(ft)
for hero in heroes:
hero.print()
'''
各勇者についての情報とそれに関する操作が多く、情報をたくさんの配列で管理したり、各操作を関数で実装したりするのは大変です。そこで、あるまとまりについての情報とその操作を管理するのに適しているクラスの出番です。
勇者一人一人をオブジェクトに見立てることで、ステータスをメンバ変数、イベントをメンバ関数として実装し、入力に応じてそれらを呼び出すだけで処理できるようになります。
問題文での指示に加えて、Hero クラスに print メソッドを追加しました。
以下で print メソッドについて説明します。
この print メソッドは勇者のステータスを出力するために実装しました。
呼び出されると、レベル 体力 攻撃力 防御力 素早さ 賢さ 運 の形式で出力します。
'''
step3 格闘ゲーム
# utf-8
class Player:
def __init__(self, hp, f_1, a_1, f_2, a_2, f_3, a_3):
self.hp = hp
self.f = [f_1, f_2, f_3]
self.a = [a_1, a_2, a_3]
self.alive = True
def enhance(self):
for i in range(3):
if self.f[i] == 0 and self.a[i] == 0:
continue
self.f[i] = max(self.f[i] - 3, 1)
self.a[i] += 5
def calc_hp(self, damage):
self.hp -= damage
if self.hp <= 0:
self.alive = False
def get_status(self, i):
return (self.f[i], self.a[i])
def get_alive(self):
return self.alive
n, k = [int(x) for x in input().split()]
players = [None] * n
for i in range(n):
hp, f_1, a_1, f_2, a_2, f_3, a_3 = [int(x) for x in input().split()]
players[i] = Player(hp, f_1, a_1, f_2, a_2, f_3, a_3)
for _ in range(k):
values = [int(x) for x in input().split()]
p_1 = values[0] - 1
t_1 = values[1] - 1
p_2 = values[2] - 1
t_2 = values[3] - 1
f_1, a_1 = players[p_1].get_status(t_1)
f_2, a_2 = players[p_2].get_status(t_2)
if not players[p_1].get_alive() or not players[p_2].get_alive():
continue
if f_1 == 0 and a_1 == 0 and f_2 == 0 and a_2 == 0:
players[p_1].enhance()
players[p_2].enhance()
elif f_1 == 0 and a_1 == 0:
players[p_1].enhance()
players[p_1].calc_hp(a_2)
elif f_2 == 0 and a_2 == 0:
players[p_2].enhance()
players[p_2].calc_hp(a_1)
else:
if f_1 > f_2:
players[p_1].calc_hp(a_2)
elif f_1 < f_2:
players[p_2].calc_hp(a_1)
else:
pass
ans = 0
for player in players:
if player.get_alive():
ans += 1
print(ans)
'''
今回の問題では攻撃時の条件分岐が多く、どこからとっつけば良いかわからなくなってしまいます。そういったときは、情報を整理することが大切です。
今回の問題では、各プレイヤーは独立した情報を持ち、それらを操作する共通の強化技を持つので、クラスで管理することができます。
各攻撃については、クラスで整理した情報を用いて地道に条件分岐を行いましょう。強化技はクラス内の関数とすることで処理の流れをわかりやすくすることができます。
クラスを用いて、プレイヤーを管理します。
クラスについては「構造体の検索」の解説で詳しく説明しているので、興味のある方はご覧ください。
【 今回実装したクラスに関して 】
今回は以下の 4 つのプロパティを持つと扱いやすいです。
体力を表す整数
3 つの攻撃フレームをまとめるリスト
3 つの攻撃力をまとめるリスト
生きているかを表すブール値 (True か False)
メソッドは以下の 5 つのメソッドを実装しました。
初期値を設定するコンストラクタ
強化系の技を実現するメソッド
体力を計算するメソッド
指定されたインデックスの攻撃に関する情報を (攻撃フレーム, 攻撃力) のタプルの形式で返すメソッド
生きているかを返すメソッド
強化系の技を使ったとき、強化系の技の情報は変更されない点に注意が必要です。
【 入力を受け取って処理を実行する部分について 】
入力値を受け取って Player クラスのインスタンスを生成してリストで管理する箇所は前問などと同じ構造です。
実際に攻撃し合う入力に関する処理を以下でまとめます。
まずは、Player クラスに用意したメソッドなどを用いて、入力値から必要な情報を用意します。
上で言う必要な情報とは以下の通りです。
プレイヤー_i VS プレイヤー_j の p_i と p_j
何の技を使うかの f_x, a_x と f_y, a_y
上の情報が出揃ったら、両者が生き残っているか確認します。どちらも生き残っている場合、攻撃のターンを以下のように場合分けします。
両者ともに強化系の技を使うか
True だったら、両者ダメージ計算をせずに、ただ強化するメソッドを呼び出す
p_i だけ強化系の技を使うか
True だったら、p_i は強化系の技をし、p_j から攻撃力 a_y の技を受ける
p_j だけ強化系の技を使うか
True だったら、p_j は強化系の技をし、p_i から攻撃力 a_x の技を受ける
両方とも攻撃技を使うか
入力値の条件から、強化系の技に関する判定がすべて偽だった場合、ここで絶対に処理されなくてはなりません。
ここでは以下のようにさらに場合分けされます。
f_x > f_y の場合、p_i が 攻撃力a_y の技を受ける
f_x < f_y の場合、p_j が 攻撃力a_x の技を受ける
f_x == f_y の場合、何もしない
補足
48 行目の条件式は
if not (players[p_1].get_alive() and players[p_2].get_alive()):