-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsociety_hierarchy.py
1122 lines (947 loc) · 41.1 KB
/
society_hierarchy.py
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
"""Assignment 2: Society Hierarchy (all tasks)
CSC148, Fall 2024
This code is provided solely for the personal and private use of students
taking the CSC148 course at the University of Toronto. Copying for purposes
other than this use is expressly prohibited. All forms of distribution of this
code, whether as given or with any changes, are expressly prohibited.
Authors: Sadia Sharmin, Diane Horton, Dina Sabie, Sophia Huynh, and
Jonathan Calver.
All of the files in this directory and all subdirectories are:
Copyright (c) 2024 Sadia Sharmin, Diane Horton, Dina Sabie, Sophia Huynh, and
Jonathan Calver
=== Module description ===
This module contains all of the classes necessary to model the entities in a
society's hierarchy.
REMINDER: You must NOT use list.sort() or sorted() in your code. Instead, use
the merge() function we provide for you below.
"""
from __future__ import annotations
from typing import Optional, TextIO, Any
def merge(lst1: list, lst2: list) -> list:
"""Return a sorted list with the elements in <lst1> and <lst2>.
Preconditions:
- <lst1>> is sorted and <lst2> is sorted.
- All of the elements of <lst1> and <lst2> are of the same type, and they
are comparable (i.e. their type implements __lt__).
>>> merge([1, 2, 5], [3, 4, 6])
[1, 2, 3, 4, 5, 6]
"""
i1 = 0
i2 = 0
new_list = []
while i1 < len(lst1) and i2 < len(lst2):
if lst1[i1] < lst2[i2]:
new_list.append(lst1[i1])
i1 += 1
else:
new_list.append(lst2[i2])
i2 += 1
new_list.extend(lst1[i1:])
new_list.extend(lst2[i2:])
return new_list
###########################################################################
# Task 1: Citizen and Society
###########################################################################
class Citizen:
"""A Citizen: a citizen in a Society.
=== Public Attributes ===
cid:
The ID number of this citizen.
manufacturer:
The manufacturer of this Citizen.
model_year:
The model year of this Citizen.
job:
The name of this Citizen's job within the Society.
rating:
The rating of this Citizen.
=== Private Attributes ===
_superior:
The superior of this Citizen in the society, or None if this Citizen
does not have a superior.
_subordinates:
A list of this Citizen's direct subordinates (that is, Citizens that
work directly under this Citizen).
=== Representation Invariants ===
- self.cid > 0
- 0 <= self.rating <= 100
- self._subordinates is in ascending order by the subordinates' IDs
- self._superior is None or self in self._superior._subordinates
- all(sub._superior is self for sub in self._subordinates)
"""
cid: int
manufacturer: str
model_year: int
job: str
rating: int
_superior: Optional[Citizen]
_subordinates: list[Citizen]
def __init__(self, cid: int, manufacturer: str, model_year: int,
job: str, rating: int) -> None:
"""Initialize this Citizen with the ID <cid>, manufacturer
<manufacturer>, model year <model_year>, job <job>, and rating <rating>.
A Citizen initially has no superior and no subordinates.
>>> c1 = Citizen(1, "Starky Industries", 3042, "Labourer", 50)
>>> c1.cid
1
>>> c1.rating
50
"""
self.cid = cid
self.manufacturer = manufacturer
self.model_year = model_year
self.job = job
self.rating = rating
self._superior = None
self._subordinates = []
def __lt__(self, other: Any) -> bool:
"""Return True if <other> is a Citizen and this Citizen's cid is less
than <other>'s cid.
If other is not a Citizen, raise a TypeError.
>>> c1 = Citizen(1, "Starky Industries", 3042, "Labourer", 50)
>>> c2 = Citizen(2, "Hookins National Lab", 3042, "Manager", 30)
>>> c1 < c2
True
"""
if not isinstance(other, Citizen):
raise TypeError
return self.cid < other.cid
def __str__(self) -> str:
"""Return a string representation of the tree rooted at this Citizen.
"""
return self._str_indented().strip()
def _str_indented(self, depth: int = 0) -> str:
"""Return an indented string representation of this tree.
The indentation level is specified by the <depth> parameter.
"""
me = f'{str(self.cid)} (rating = {self.rating})'
if isinstance(self, DistrictLeader):
me += f' --> District Leader for {self._district_name}'
s = ' ' * depth + me + '\n'
for subordinate in self.get_direct_subordinates():
# Note that the 'depth' argument to the recursive call is
# modified.
s += subordinate._str_indented(depth + 1)
return s
def get_superior(self) -> Optional[Citizen]:
"""Return the superior of this Citizen or None if no superior exists.
>>> c1 = Citizen(1, "Starky Industries", 3024, "Labourer", 50)
>>> c1.get_superior() is None
True
>>> c2 = Citizen(2, "Hookins National Lab", 3024, "Manager", 30)
>>> c1.become_subordinate_to(c2)
>>> c1.get_superior().cid
2
"""
return self._superior
def set_superior(self, new_superior: Optional[Citizen]) -> None:
"""Update the superior of this Citizen to <new_superior>
>>> c1 = Citizen(1, "Starky Industries", 3024, "Labourer", 50)
>>> c2 = Citizen(2, "Hookins National Lab", 3024, "Manager", 30)
>>> c1.set_superior(c2)
>>> c1.get_superior().cid
2
"""
self._superior = new_superior
def get_direct_subordinates(self) -> list[Citizen]:
"""Return a new list containing the direct subordinates of this Citizen.
>>> c1 = Citizen(1, "Starky Industries", 3024, "Labourer", 50)
>>> c2 = Citizen(2, "Hookins National Lab", 3024, "Manager", 30)
>>> c3 = Citizen(3, "S.T.A.R.R.Y Lab", 3010, "Commander", 60)
>>> c1.become_subordinate_to(c2)
>>> c2.become_subordinate_to(c3)
>>> c3.get_direct_subordinates()[0].cid
2
"""
return self._subordinates[:]
###########################################################################
# Task 1.1 (Helper methods)
#
# While not called by the client code, these methods may be helpful to
# you and will be tested. You can (and should) call them in the other
# methods that you implement when appropriate.
###########################################################################
def add_subordinate(self, subordinate: Citizen) -> None:
"""Add <subordinate> to this Citizen's list of direct subordinates,
keeping the list of subordinates in ascending order by their ID.
Update the new subordinate's superior to be this Citizen.
Precondition: The <subordinate> has no existing superior
>>> c1 = Citizen(1, "Starky Industries", 3024, "Labourer", 50)
>>> c2 = Citizen(2, "Hookins National Lab", 3024, "Manager", 30)
>>> c2.add_subordinate(c1)
>>> c2.get_direct_subordinates()[0].cid
1
>>> c1.get_superior() is c2
True
"""
i = 0
while i < len(self._subordinates) and self._subordinates[i].cid < subordinate.cid:
i += 1
self._subordinates.insert(i, subordinate)
subordinate._superior = self
def remove_subordinate(self, cid: int) -> None:
"""Remove the direct subordinate with the ID <cid> from this Citizen's
list of subordinates.
Furthermore, remove that (former) subordinate from the hierarchy by
setting its superior to None.
Precondition: This Citizen has a direct subordinate with ID <cid>.
>>> c1 = Citizen(1, "Starky Industries", 3024, "Labourer", 50)
>>> c2 = Citizen(2, "Hookins National Lab", 3024, "Manager", 30)
>>> c1.become_subordinate_to(c2)
>>> c2.get_direct_subordinates()[0].cid
1
>>> c2.remove_subordinate(1)
>>> c2.get_direct_subordinates()
[]
>>> c1.get_superior() is None
True
"""
for subordinate in self._subordinates:
if subordinate.cid == cid:
self._subordinates.remove(subordinate)
subordinate._superior = None
break
def become_subordinate_to(self, superior: Optional[Citizen]) -> None:
"""Make this Citizen a direct subordinate of <superior>.
If this Citizen already had a superior, remove this Citizen from the
old superior's list of subordinates.
If <superior> is None, just set this Citizen's superior to None.
>>> c1 = Citizen(1, "Starky Industries", 3024, "Labourer", 50)
>>> c2 = Citizen(2, "Hookins National Lab", 3024, "Manager", 30)
>>> c1.become_subordinate_to(c2)
>>> c1.get_superior().cid
2
>>> c2.get_direct_subordinates()[0].cid
1
>>> c1.become_subordinate_to(None)
>>> c1.get_superior() is None
True
>>> c2.get_direct_subordinates()
[]
"""
if self._superior:
self._superior.remove_subordinate(self.cid)
if superior:
superior.add_subordinate(self)
else:
self._superior = None
def get_citizen(self, cid: int) -> Optional[Citizen]:
"""Check this Citizen and its subordinates (both direct and indirect)
to find and return the Citizen that has the ID <cid>.
If neither this Citizen nor any of its subordinates (both direct and
indirect) have the ID <cid>, return None.
>>> c1 = Citizen(1, "Starky Industries", 3024, "Labourer", 50)
>>> c2 = Citizen(2, "Hookins National Lab", 3024, "Manager", 30)
>>> c3 = Citizen(3, "S.T.A.R.R.Y Lab", 3010, "Commander", 60)
>>> c1.become_subordinate_to(c2)
>>> c2.become_subordinate_to(c3)
>>> c3.get_citizen(1) is c1
True
>>> c2.get_citizen(3) is None
True
"""
# Note: This method must call itself recursively
if self.cid == cid:
return self
else:
for subordinate in self._subordinates:
result = subordinate.get_citizen(cid)
if result is not None:
return result
return None
###########################################################################
# Task 1.2
###########################################################################
def get_all_subordinates(self) -> list[Citizen]:
"""Return a new list of all of the subordinates (both direct and
indirect) of this Citizen in order of ascending IDs.
>>> c1 = Citizen(1, "Starky Industries", 3024, "Labourer", 50)
>>> c2 = Citizen(2, "Hookins National Lab", 3024, "Manager", 30)
>>> c3 = Citizen(3, "S.T.A.R.R.Y Lab", 3010, "Commander", 60)
>>> c1.become_subordinate_to(c2)
>>> c2.become_subordinate_to(c3)
>>> c3.get_all_subordinates()[0].cid
1
>>> c3.get_all_subordinates()[1].cid
2
"""
# Note: This method must call itself recursively
# Hints:
# - Recall that each Citizen's subordinates list is sorted in ascending
# order.
# - Use the merge helper function.
if not self._subordinates:
return []
else:
result = self._subordinates[:]
for subordinate in self._subordinates:
indirect_subordinates = subordinate.get_all_subordinates()
result = merge(result, indirect_subordinates)
return result
def get_society_head(self) -> Citizen:
"""Return the head of the Society (i.e. the top-most superior Citizen,
a.k.a. the root of the hierarchy).
the head of the society is a citizen who has no superior.
>>> c1 = Citizen(1, "Starky Industries", 3024, "Labourer", 50)
>>> c2 = Citizen(2, "Hookins National Lab", 3024, "Manager", 30)
>>> c3 = Citizen(3, "S.T.A.R.R.Y Lab", 3010, "Commander", 60)
>>> c1.become_subordinate_to(c2)
>>> c2.become_subordinate_to(c3)
>>> c1.get_society_head().cid
3
"""
# Note: This method must call itself recursively
if not self._superior:
return self
else:
return self._superior.get_society_head()
def get_closest_common_superior(self, cid: int) -> Citizen:
"""Return the closest common superior that this Citizen and the
Citizen with ID <cid> share.
If this Citizen is the superior of <cid>, return this Citizen.
Similaraly, if a citizen with cid <cid> is the superior of this
Citizen, return that citizen with cid <cid>.
Precondition: A Citizen with <cid> exists in the hierarchy, to which
self belongs.
>>> c1 = Citizen(1, "Starky Industries", 3024, "Labourer", 50)
>>> c2 = Citizen(2, "Hookins National Lab", 3024, "Manager", 30)
>>> c3 = Citizen(3, "S.T.A.R.R.Y Lab", 3010, "Commander", 60)
>>> c4 = Citizen(4, "Starky Industries", 3022, "Manager", 55)
>>> c5 = Citizen(5, "Hookins National Lab", 3023, "Engineer", 50)
>>> c2.become_subordinate_to(c1)
>>> c4.become_subordinate_to(c1)
>>> c3.become_subordinate_to(c2)
>>> c5.become_subordinate_to(c2)
>>> c3.get_closest_common_superior(1) == c1
True
>>> c3.get_closest_common_superior(3) == c3
True
>>> c3.get_closest_common_superior(4) == c1
True
>>> c3.get_closest_common_superior(5) == c2
True
"""
# Note: This method must call itself recursively
if self.cid == cid:
return self
# if the citizen with cid is a subordinate of self
if self.get_citizen(cid):
return self
# if the citizen with cid is a superior of self
if self._superior is not None:
return self._superior.get_closest_common_superior(cid)
###########################################################################
# Task 2.2
###########################################################################
def get_district_name(self) -> str:
"""Return the immediate district that the Citizen belongs to (or
leads).
If the Citizen is not part of any districts, return an empty string.
>>> c1 = Citizen(1, "Starky Industries", 3024, "Labourer", 50)
>>> c2 = DistrictLeader(2, "Hookins National Lab", 3024, "Manager", \
30, "District A")
>>> c1.get_district_name()
''
>>> c1.become_subordinate_to(c2)
>>> c1.get_district_name()
'District A'
"""
# Note: This method must call itself recursively
if self.get_superior() is None:
return ""
return self.get_superior().get_district_name()
def rename_district(self, district_name: str) -> None:
"""Rename the immediate district which this Citizen is a part of to
<district_name>.
If the Citizen is not part of a district, do nothing.
>>> c1 = Citizen(1, "Starky Industries", 3024, "Labourer", 50)
>>> c2 = DistrictLeader(2, "Hookins National Lab", 3024, "Manager", \
30, "District A")
>>> c1.become_subordinate_to(c2)
>>> c1.rename_district('District B')
>>> c1.get_district_name()
'District B'
>>> c2.get_district_name()
'District B'
"""
superior = self.get_superior()
if superior is not None:
superior.rename_district(district_name)
###########################################################################
# Task 3.2 Helper Method
###########################################################################
def get_highest_rated_subordinate(self) -> Citizen:
"""Return the direct subordinate of this Citizen with the highest
rating.
Precondition: This Citizen has at least one subordinate.
>>> c1 = Citizen(1, "Starky Industries", 3024, "Labourer", 50)
>>> c2 = DistrictLeader(2, "Hookins National Lab", 3024, "Manager", 30,
... "District A")
>>> c3 = DistrictLeader(3, "S.T.A.R.R.Y Lab", 3000, "Commander", 60,
... "District X")
>>> c1.become_subordinate_to(c2)
>>> c2.become_subordinate_to(c3)
>>> c3.get_highest_rated_subordinate().manufacturer
'Hookins National Lab'
>>> c1.become_subordinate_to(c3)
>>> c3.get_highest_rated_subordinate().manufacturer
'Starky Industries'
"""
# Hint: This can be used as a helper function for `delete_citizen`
direct_subordinates = self.get_direct_subordinates()
highest_rated = direct_subordinates[0]
for subordinate in direct_subordinates:
if subordinate.rating > highest_rated.rating:
highest_rated = subordinate
return highest_rated
class Society:
"""A society containing citizens in a hierarchy.
=== Private Attributes ===
_head:
The root of the hierarchy, which we call the "head" of the Society.
If _head is None, this indicates that this Society is empty (there are
no citizens in this Society).
=== Representation Invariants ===
- No two Citizens in this Society have the same cid.
- self._head._superior is None
"""
_head: Optional[Citizen]
def __init__(self, head: Optional[Citizen] = None) -> None:
"""Initialize this Society with the head <head>.
>>> o = Society()
>>> o.get_head() is None
True
"""
self._head = head
def __str__(self) -> str:
"""Return a string representation of this Society's tree.
For each node, its item is printed before any of its descendants'
items. The output is nicely indented.
You may find this method helpful for debugging.
"""
return str(self._head)
###########################################################################
# You may use the methods below as helper methods if needed.
###########################################################################
def get_head(self) -> Optional[Citizen]:
"""Return the head of this Society.
"""
return self._head
def set_head(self, new_head: Citizen) -> None:
"""Set the head of this Society to <new_head>.
"""
self._head = new_head
###########################################################################
# Task 1.3
###########################################################################
def get_citizen(self, cid: int) -> Optional[Citizen]:
"""Return the Citizen in this Society who has the ID <cid>. If no such
Citizen exists, return None.
>>> o = Society()
>>> c1 = Citizen(1, "Starky Industries", 3024, "Labourer", 50)
>>> o.add_citizen(c1)
>>> o.get_citizen(1) is c1
True
>>> o.get_citizen(2) is None
True
"""
result = None
for citizen in self.get_all_citizens():
if citizen.cid == cid:
result = citizen
return result
def add_citizen(self, citizen: Citizen, superior_id: int = None) -> None:
"""Add <citizen> to this Society as a subordinate of the Citizen with
ID <superior_id>.
If no <superior_id> is provided, make <citizen> the new head of this
Society, with the original head becoming the one and only subordinate
of <citizen>.
Preconditions:
- citizen.get_superior() is None
- len(citizen.get_direct_subordinates()) == 0
- if <superior_id> is not None, then the Society contains a Citizen with
ID <superior_id>.
- Society does not already contain any Citizen with the same ID as
<citizen>.
>>> o = Society()
>>> c1 = Citizen(1, "Starky Industries", 3024, "Labourer", 50)
>>> c2 = Citizen(2, "Some Lab", 3024, "Lawyer", 30)
>>> o.add_citizen(c2)
>>> o.get_head() is c2
True
>>> o.add_citizen(c1, 2)
>>> o.get_head() is c2
True
>>> o.get_citizen(1) is c1
True
>>> c1.get_superior() is c2
True
"""
if superior_id is None:
if self._head:
citizen.add_subordinate(self._head)
self._head = citizen
else:
self.get_citizen(superior_id).add_subordinate(citizen)
def get_all_citizens(self) -> list[Citizen]:
"""Return a list of all citizens, in order of increasing cid.
>>> o = Society()
>>> c1 = Citizen(1, "Starky Industries", 3024, "Manager", 50)
>>> c2 = Citizen(2, "Hookins National Lab", 3024, "Manager", 65)
>>> c3 = Citizen(3, "Starky Industries", 3024, "Labourer", 50)
>>> c4 = Citizen(4, "S.T.A.R.R.Y Lab", 3024, "Manager", 30)
>>> c5 = Citizen(5, "Hookins National Lab", 3024, "Labourer", 50)
>>> c6 = Citizen(6, "S.T.A.R.R.Y Lab", 3024, "Lawyer", 30)
>>> o.add_citizen(c4, None)
>>> o.add_citizen(c2, 4)
>>> o.add_citizen(c6, 2)
>>> o.add_citizen(c1, 4)
>>> o.add_citizen(c3, 1)
>>> o.add_citizen(c5, 1)
>>> o.get_all_citizens() == [c1, c2, c3, c4, c5, c6]
True
"""
if not self._head:
return []
else:
subordinates = self._head.get_all_subordinates()
return merge([self._head], subordinates)
def get_citizens_with_job(self, job: str) -> list[Citizen]:
"""Return a list of all citizens with the job <job>, in order of
increasing cid.
>>> o = Society()
>>> c1 = Citizen(1, "Starky Industries", 3024, "Manager", 50)
>>> c2 = Citizen(2, "Hookins National Lab", 3024, "Manager", 65)
>>> c3 = Citizen(3, "Starky Industries", 3024, "Labourer", 50)
>>> c4 = Citizen(4, "S.T.A.R.R.Y Lab", 3024, "Manager", 30)
>>> c5 = Citizen(5, "Hookins National Lab", 3024, "Labourer", 50)
>>> c6 = Citizen(6, "S.T.A.R.R.Y Lab", 3024, "Lawyer", 30)
>>> o.add_citizen(c4, None)
>>> o.add_citizen(c2, 4)
>>> o.add_citizen(c6, 2)
>>> o.add_citizen(c1, 4)
>>> o.add_citizen(c3, 1)
>>> o.add_citizen(c5, 1)
>>> o.get_citizens_with_job('Manager') == [c1, c2, c4]
True
"""
all_citizens = self.get_all_citizens()
all_citizens_with_job = []
for citizen in all_citizens:
if citizen.job == job:
all_citizens_with_job.append(citizen)
return all_citizens_with_job
###########################################################################
# Task 2.3
###########################################################################
def change_citizen_type(self, cid: int,
district_name: Optional[str] = None) -> Citizen:
"""Change the type of the Citizen with the given <cid>
If the Citizen is currently a DistrictLeader, change them to become a
regular Citizen (with no district name). If they are currently a regular
Citizen, change them to become DistrictLeader for <district_name>.
Note that this requires creating a new object of type either Citizen
or DistrictLeader.
The new Citizen/DistrictLeader should keep the same placement in the
hierarchy (that is, the same superior and subordinates) that the
original Citizen had, as well as the same ID, manufacturer, model year,
job, and rating.
Return the newly created Citizen/DistrictLeader.
The original citizen that's being replaced should no longer be in the
hierarchy (it should not be anyone's subordinate nor superior).
Precondition:
- <cid> exists in this society.
- If <cid> is the id of a DistrictLeader, <district_name> must be None
>>> o = Society()
>>> c1 = DistrictLeader(
... 1, "Starky Industries", 3024, "Manager", 50, "Area 52")
>>> c2 = Citizen(2, "Hookins National Lab", 3024, "Manager", 65)
>>> c3 = Citizen(3, "Starky Industries", 3024, "Labourer", 50)
>>> c4 = Citizen(4, "S.T.A.R.R.Y Lab", 3024, "Manager", 30)
>>> c5 = Citizen(5, "Hookins National Lab", 3024, "Labourer", 50)
>>> c6 = Citizen(6, "S.T.A.R.R.Y Lab", 3024, "Lawyer", 30)
>>> o.add_citizen(c1, None)
>>> o.add_citizen(c2, 1)
>>> o.add_citizen(c3, 1)
>>> o.add_citizen(c4, 1)
>>> o.add_citizen(c5, 3)
>>> o.add_citizen(c6, 3)
>>> original_subordinates = c1.get_direct_subordinates()
>>> new_c1 = o.change_citizen_type(1)
>>> o.get_head() is new_c1
True
>>> len(c1.get_direct_subordinates())
0
>>> new_c1.get_direct_subordinates() == original_subordinates
True
>>> new_c3 = o.change_citizen_type(3, "Finance")
>>> new_c3.get_district_name() == "Finance"
True
"""
target = self.get_citizen(cid)
if target:
if isinstance(target, DistrictLeader):
new_citizen = Citizen(cid, target.manufacturer, target.model_year, target.job, target.rating)
else:
new_citizen = DistrictLeader(cid, target.manufacturer, target.model_year, target.job, target.rating,
district_name)
for subordinate in target.get_direct_subordinates():
new_citizen.add_subordinate(subordinate)
target.remove_subordinate(subordinate.cid)
superior = target.get_superior()
if superior is not None:
superior.remove_subordinate(target.cid)
superior.add_subordinate(new_citizen)
if target is self.get_head():
self.set_head(new_citizen)
return new_citizen
###########################################################################
# Task 3.1
###########################################################################
def _swap_up(self, citizen: Citizen) -> Citizen:
"""Swap <citizen> with their superior in this Society (they should
swap their job, and their position in the tree, but otherwise keep
all the same attribute data they currently have).
If the superior is a DistrictLeader, the citizens being swapped should
also switch their citizen type (i.e. the DistrictLeader becomes a
regular Citizen and vice versa).
Return the Citizen after it has been swapped up ONCE in the Society.
Precondition:
- <citizen> has a superior (i.e., it is not the head of this Society),
and is not a DistrictLeader.
>>> o = Society()
>>> c1 = Citizen(1, "Starky Industries", 3024, "Manager", 50)
>>> c2 = DistrictLeader(
... 2, "Hookins National Lab", 3024, "Manager", 65, "Area 52")
>>> c3 = Citizen(3, "Starky Industries", 3024, "Labourer", 50)
>>> c4 = Citizen(4, "S.T.A.R.R.Y Lab", 3024, "Manager", 30)
>>> c5 = Citizen(5, "Hookins National Lab", 3024, "Labourer", 50)
>>> c6 = Citizen(6, "S.T.A.R.R.Y Lab", 3024, "Lawyer", 30)
>>> o.add_citizen(c1, None)
>>> o.add_citizen(c2, 1)
>>> o.add_citizen(c3, 1)
>>> o.add_citizen(c4, 2)
>>> o.add_citizen(c5, 2)
>>> o.add_citizen(c6, 4)
>>> swapped_c4 = o._swap_up(c4)
>>> swapped_c2 = o.get_citizen(2)
>>> swapped_c2.get_direct_subordinates() == [c6]
True
>>> isinstance(swapped_c2, DistrictLeader)
False
>>> swapped_c2 in swapped_c4.get_direct_subordinates()
True
>>> isinstance(swapped_c4, DistrictLeader)
True
"""
superior = citizen.get_superior()
if isinstance(superior, DistrictLeader):
# swapped in wrong order!!!
citizen = self.change_citizen_type(citizen.cid, superior.get_district_name())
superior = self.change_citizen_type(superior.cid)
# copy of subordinates
superior_subordinates = superior.get_direct_subordinates()
citizen_subordinates = citizen.get_direct_subordinates()
for subordinate in superior_subordinates:
# idea is to
# remove all superior subordinates
# add all superior subordinates to citizen except itself
if (subordinate.cid != citizen.cid):
citizen.add_subordinate(subordinate)
superior.remove_subordinate(subordinate.cid)
for subordinate in citizen_subordinates:
if subordinate not in superior.get_all_subordinates():
# print("adding subordinate" + str(subordinate.cid))
superior.add_subordinate(subordinate)
citizen.remove_subordinate(subordinate.cid)
if superior.get_superior() is not None:
superior.get_superior().add_subordinate(citizen)
superior.get_superior().remove_subordinate(superior.cid)
citizen.add_subordinate(superior)
superior.remove_subordinate(citizen.cid)
superior.job, citizen.job = citizen.job, superior.job
return citizen
def promote_citizen(self, cid: int) -> None:
"""Promote the Citizen with cid <cid> until they either:
- have a superior with a rating greater than or equal to them or,
- become DistrictLeader for their district.
See the Assignment 2 handout for further details.
Precondition:
- There is a Citizen with the cid <cid> in this Society.
- The Citizen with cid <cid> is not a DistrictLeader.
>>> c6 = DistrictLeader(6, "Star", 3036, "CFO", 20, "Area 52")
>>> c5 = DistrictLeader(
... 5, "S.T.A.R.R.Y Lab", 3024, "Manager", 50, "Finance")
>>> c7 = Citizen(7, "Hookins", 3071, "Labourer", 60)
>>> c11 = Citizen(11, "Starky", 3036, "Repairer", 90)
>>> c13 = Citizen(13, "STARRY", 3098, "Eng", 86)
>>> s = Society()
>>> s.add_citizen(c6)
>>> s.add_citizen(c5, 6)
>>> s.add_citizen(c7, 5)
>>> s.add_citizen(c11, 7)
>>> s.add_citizen(c13, 7)
>>> s.promote_citizen(11)
>>> promoted = s.get_citizen(11)
>>> c5 = s.get_citizen(5)
>>> isinstance(promoted, DistrictLeader)
True
>>> c5 in promoted.get_direct_subordinates()
True
"""
citizen = self.get_citizen(cid)
# if immediate superior has lower rating, begin promotion
if citizen.get_superior().rating < citizen.rating:
last_swappable_superior = citizen.get_superior()
while last_swappable_superior.cid < citizen.cid:
# reached the top of the district
if isinstance(last_swappable_superior, DistrictLeader):
break
last_swappable_superior = last_swappable_superior.get_superior()
subordinates = last_swappable_superior.get_all_subordinates()
while citizen in subordinates:
citizen = self._swap_up(citizen)
# else, do nothing
else:
return None
###########################################################################
# Task 3.2
###########################################################################
def delete_citizen(self, cid: int) -> None:
"""Remove the Citizen with ID <cid> from this Society.
If this Citizen has subordinates, their subordinates become subordinates
of this Citizen's superior.
If this Citizen is the head of the Society, their most highly rated
direct subordinate becomes the new head. If they did not have any
subordinates, the society becomes empty (the society head becomes None).
Precondition: There is a Citizen with the cid <cid> in this Society.
>>> c1 = DistrictLeader(1, "Star", 3036, "CFO", 20, "Area 52")
>>> c2 = DistrictLeader(
... 2, "S.T.A.R.R.Y Lab", 3024, "Manager", 50, "Finance")
>>> c3 = Citizen(3, "Hookins", 3071, "Labourer", 60)
>>> c4 = Citizen(4, "Starky", 3036, "Repairer", 90)
>>> c5 = Citizen(5, "STARRY", 3098, "Eng", 86)
>>> s = Society()
>>> s.add_citizen(c1, None)
>>> s.add_citizen(c2, 1)
>>> s.add_citizen(c3, 1)
>>> s.add_citizen(c4, 2)
>>> s.add_citizen(c5, 2)
>>> len(c1.get_direct_subordinates())
2
>>> s.delete_citizen(2)
>>> len(c1.get_direct_subordinates())
3
>>> all(c in c1.get_direct_subordinates() for c in [c4, c5, c3])
True
"""
citizen = self.get_citizen(cid)
superior = citizen.get_superior()
subordinates = citizen.get_direct_subordinates()
superior.remove_subordinate(citizen.cid)
for subordinate in subordinates:
superior.add_subordinate(subordinate)
if citizen is self.get_head():
if not citizen.get_direct_subordinates():
self._head = None
else:
highest_rated_subordinate = citizen.get_highest_rated_subordinate()
subordinates = citizen.get_direct_subordinates()
for subordinate in subordinates:
if subordinate.cid != highest_rated_subordinate.cid:
highest_rated_subordinate.add_subordinate(subordinate)
citizen.remove_subordinate(subordinate.cid)
highest_rated_subordinate.set_superior(None)
self.set_head(highest_rated_subordinate)
###############################################################################
# Task 2: DistrictLeader
###############################################################################
class DistrictLeader(Citizen):
"""The leader of a district in a society.
=== Private Attributes ===
_district_name:
The name of the district that this DistrictLeader is the leader of.
=== Inherited Public Attributes ===
cid:
The ID number of this citizen.
manufacturer:
The manufacturer of this Citizen.
model_year:
The model year of this Citizen.
job:
The name of this Citizen's job within the Society.
rating:
The rating of this Citizen.
=== Inherited Private Attributes ===
_superior:
The superior of this Citizen in the society, or None if this Citizen
does not have a superior.
_subordinates:
A list of this Citizen's direct subordinates (that is, Citizens that
work directly under this Citizen).
=== Representation Invariants ===
- All Citizen RIs are inherited.
- len(self._district_name) >= 1
"""
_district_name: str
###########################################################################
# Task 2.1
###########################################################################
def __init__(self, cid: int, manufacturer: str, model_year: int,
job: str, rating: int, district: str) -> None:
"""Initialize this DistrictLeader with the ID <cid>, manufacturer
<manufacturer>, model year <model_year>, job <job>, rating <rating>, and
district name <district>.
Preconsitions:
- len(distict) >= 0
>>> c2 = DistrictLeader(2, "Some Lab", 3024, "Lawyer", 30, "District A")
>>> c2.manufacturer
'Some Lab'
>>> c2.get_district_name()
'District A'
"""
Citizen.__init__(self, cid, manufacturer, model_year, job, rating)
self._district_name = district
def get_district_citizens(self) -> list[Citizen]:
"""Return a list of all citizens in this DistrictLeader's district, in
increasing order of cid.
Include the cid of this DistrictLeader in the list.
>>> c1 = DistrictLeader(
... 1, "Hookins National Lab", 3024, "Commander", 65, "District A"
... )
>>> c2 = Citizen(2, "Hookins National Lab", 3024, "Lawyer", 30)
>>> c3 = Citizen(3, "S.T.A.R.R.Y Lab", 3010, "Labourer", 55)
>>> c2.become_subordinate_to(c1)
>>> c3.become_subordinate_to(c1)
>>> c1.get_district_citizens() == [c1, c2, c3]
True
"""
citizen_list = self.get_all_subordinates()
return merge(citizen_list, [self])
###########################################################################
# Task 2.2
###########################################################################
def get_district_name(self) -> str:
"""Return the name of the district that this DistrictLeader leads.
"""
return self._district_name
def rename_district(self, district_name: str) -> None:
"""Rename this district leader's district to the given <district_name>.
"""
self._district_name = district_name
###########################################################################
# ALL PROVIDED FUNCTIONS BELOW ARE COMPLETE, DO NOT CHANGE
###########################################################################
def create_society_from_file(file: TextIO) -> Society:
"""Return the Society represented by the information in file.
>>> o = create_society_from_file(open('citizens.csv'))
>>> o.get_head().manufacturer
'Hookins National Lab'
>>> len(o.get_head().get_all_subordinates())
11
"""
head = None
people = {}
for line in file:
info: list[Any] = line.strip().split(',')