D. Работа не волк

Рассмотрим объект «Программист», который задаётся именем, должностью и количеством отработанных часов. Каждая должность имеет собственный оклад (заработную плату за час работы). В нашей импровизированной компании существуют 3 должности:

  • Junior — с окладом 10 тугриков в час;
  • Middle — с окладом 15 тугриков в час;
  • Senior — с окладом 20 тугриков в час по умолчанию и +1 тугрик за каждое новое повышение.

Напишите класс Programmer, который инициализируется именем и должностью (отработка у нового работника равна нулю). Класс реализует следующие методы:

  • work(time) — отмечает новую отработку в количестве часов time;
  • rise() — повышает программиста;
  • info() — возвращает строку для бухгалтерии в формате: <имя> <количество отработанных часов>ч. <накопленная зарплата>тгр.

Примечание

Ваше решение должно содержать только классы и функции.
В решении не должно быть вызовов инициализации требуемых классов.

Пример

Ввод

programmer = Programmer('Васильев Иван', 'Junior')
programmer.work(750)
print(programmer.info())
programmer.rise()
programmer.work(500)
print(programmer.info())
programmer.rise()
programmer.work(250)
print(programmer.info())
programmer.rise()
programmer.work(250)
print(programmer.info())

Вывод

Васильев Иван 750ч. 7500тгр.
Васильев Иван 1250ч. 15000тгр.
Васильев Иван 1500ч. 20000тгр.
Васильев Иван 1750ч. 25250тгр.

Решение

Очень хорошая задача на проектирование и управление достаточно сложными для начинающих данными.

Мы начнем с примитивного решения, которое доведем до ума в несколько простых шагов.

Для начала определимся c набором атрибутов нашего класса. Из условия задачи следует что нам необходимо хранить имя (name), ставка оплаты (wage), отработанное время (work_time) и суммарный заработок (salary).
Все эти переменные должны быть привязаны к классу и поэтому их имя будет начинаться в self. Есть еще словарь сопоставления позиции и ставки. Его можно так же привязать в качестве атрибута, а можно оставить пока простой переменной, так как пока он нам потребуется только в процессе инициализации объекта.

Словарь заполняем согласно условиям, имя берем из входных данных, ставку берем из словаря сопоставляя ее с позицией на которую принимаем программиста. Переменные отработанного времени и выплат инициализируем нулем.

Теперь нужно продумать три метода – work() который занимается учетом времени и вычислением выплат, метод rise() который отвечает за логику повышения и установки надбавки и метод info() который выводит информацию о программисте.

Вычисление отработаного времени задача элементарная, просто прибавляем входную переменную время к отработанному времени. Так же поступаем и с зарплатой – увеличиваем ее на произведение ставки на дополнительное время. Таким образом work() содержит всего две операции.

С методом rise() чуть сложнее, но сейчас мы пойдем самым простым путем. Легко можно заметить, что ставки отличаются на 5 тугриков, а при достижении значения в 20 тугриков каждое повышение приносит один дополнительный тугрик. Таким образом нам можно обойтись проверкой на меньше ли ставка 20 тугриков и если да, то поднять ее на 5, а если нет, то всего на 1 тугрик.

Метод info() содержит всего одну операцию – формирование строки из имеющихся у нас параметров по шаблону из задания.

Код реализующий эту логику представлен в решении номер 1. Этого решения достаточно, чтобы пройти все тесты, но это решение имеет ряд недостатков о которых мы поговорим далее.

Одним из главных недостаков кода является то, что наша программа рассчитана только на одинаковое повышение ставок от должности к должности, что не позволяет нам реализовать схему когда джуниор, миддл и сеньор имеют ставки 10, 14 и 20 тугриков соответсвенно. Давайте устраним этот недостаток, а заодно избавимся от словаря внутри функции инициализации, так как в этом случае она будет создаваться заново для каждого программиста в штате, и если их будет 1000, то мы будем иметь 1000 табличек. А если она будет атрибутом, а не временной переменной, то мы будем постоянно хранить эти 1000 табличек в памяти. Это ведет к еще одному неудобству – если мы решим сменить ставки во время работы программы придется делать это для каждого программиста отдельно. Гораздо удобнее иметь глобальную в рамках класса переменную и тогда изменение таблички немедленно отразиться на всех без исключения программистах.
Делается это переносом переменной из функции __init__() в зону сразу за объявлением класса. Пусть вас не смущает отсутствие префикса self – глобальные данные в пределах класса автоматически получают атрибут, дающий возможность использовать эти переменные в любом экземпляре класса.

Вторым отличием нашего класса будет более продвинутая система повышения (rise()). Она будет проверять на какой позиции работает программист и присваивать ему следующую. Но если он достиг потолка, то будет просто начислять ему бонус, который будет добавляться к окладу. Для этого нам стоит предусмотреть соотвествующий атрибут. Еще одно изменение в данных – отказ от ставки в пользу словаря, возвращающего ставку по позиции программиста.

В остальном программа осталась без изменений.

Код реализующий эту логику представлен в решении номер 2. Это решение будет примерно соотвествовать тому, что от вас могли бы ожидать при проверке решения задания.

У этого решения несмотря на большую гибкость все еще есть недостатки.

Например, нам придется вручную править сетку ставок в дух местах – словаре и в методе rise().

Давайте попробуем избавиться от этого недостатка. Решение предоставленное ниже не выходит за рамки изученного материала, но тем не менее, достаточно сложно для начинающих, потому что использует приемы, которые ранее не были показаны в теоретическом материале и не использовались в решениях.

Итак давайте подумаем как было бы удобнее всего оформить логику повышений и назначения ставок. Очевидно, что ставки все еще удобно хранить в словаре, но было бы здорово, если бы нам не надо было заботиться о том, какую позицию займет наш программист после повышения. Очевидно что для этого нам надо иметь список позиций отсортированный по размеру ставки. Это решение дает нам еще одно преимущество – мы можем добавлять ставки в словарь в любом порядке, сортировка расставит все по своим местам.
Итак вводим дополнительный список, который содержит “табель о рангах” от низшей позиции к высшей. Таким образом, повышение возможно до тех пор, пока текущая позиция не равна последнему элементу этого списка.

Реализация этой логики программы представлена в решении 3.

Посмотреть код

Решение

Python
# simple

class Programmer:

    def __init__(self, name, position) -> None:
        rank = {
            'Junior': 10,
            'Middle': 15,
            'Senior': 20
        }
        self.name = name
        self.wage = rank[position]
        self.work_time = 0
        self.salary = 0

    def work(self, time):
        self.work_time += time
        self.salary += self.wage * time

    def info(self):
        return f'{self.name} {self.work_time}ч. {self.salary}тгр.'

    def rise(self):
        if self.wage < 20:
            self.wage += 5
        else:
            self.wage += 1

Решение

Python
class Programmer:
    __rank = {
        'Junior': 10,
        'Middle': 15,
        'Senior': 20,
    }

    def __init__(self, name, position):
        self.name = name
        self.position = position
        self.bonus = 0
        self.work_time = 0
        self.salary = 0

    def work(self, time):
        self.work_time += time
        self.salary += (self.__rank[self.position] + self.bonus) * time

    def info(self):
        return f'{self.name} {self.work_time}ч. {self.salary}тгр.'

    def rise(self):
        match self.position:
            case 'Junior':
                self.position = 'Middle'
            case 'Middle':
                self.position = 'Senior'
            case 'Senior':
                self.bonus += 1

Решение

Python
class Programmer:
    __wage = {
        'Junior': 10,
        'Middle': 15,
        'Senior': 20,
    }

    __ranks = list(dict(sorted(__wage.items(), key=lambda item: item[1])).keys())  # noqa

    def __init__(self, name, position) -> None:
        self.__name = name
        self.__position = position
        self.__bonus = 0
        self.__work_time = 0
        self.__salary = 0

    def work(self, time):
        self.__work_time += time
        self.__salary += (self.__wage[self.__position] + self.__bonus) * time

    def info(self):
        return f'{self.__name} {self.__work_time}ч. {self.__salary}тгр.'

    def rise(self):
        if self.__position != self.__ranks[-1]:
            index = self.__ranks.index(self.__position)
            self.__position = self.__ranks[index + 1]
        else:
            self.__bonus += 1
Подписаться
Уведомить о
guest
11 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
nmalkam
23.01.2024 06:42

скажите, пожалуйста,
index = list(self.__ranks).index(self.__position)
можно использовать без list()??
index = self.__ranks.index(self.__position)

nmalkam
Ответить на  Сергей Клочко
23.01.2024 11:30

Вам большое спасибо за знания))

nmalkam
23.01.2024 06:54

__ranks = list(dict(sorted(__wage.items(), key=lambda item: item[1])).keys()) # noqa
По другому получается никак не получить список ключей?
либо циклом записаться в список

Алексей
Алексей
22.06.2024 20:37

Здравствуйте, Сергей. 

Подскажите, зачем __rank во 2-ом решении и self.__name = name в 3-ем. В материалах этого нет. Где поискать теорию?

Алексей
Алексей
Ответить на  Сергей Клочко
23.06.2024 12:47

Сергей, здравствуйте.
Благодарю за оперативный ответ.
Это как privateprotected в C#? Только в виде соглашения, а не прямого запрета. Просто переменная будет теперь _Programmer__name?
Если все переменные без двойного подчеркивания, код тоже проходит проверки! Т.е., они поставлены, чтобы напрямую не менять переменные, только через методы?
Просто, __ появилось в переменных в __init__ только в 3-ем решении, почему этого не было в предыдущих, я так и не понял. Это просто правильнее? Про __rank во 2-ом решении объяснение дается: “Гораздо удобнее иметь глобальную в рамках класса переменную и тогда изменение таблички немедленно отразиться на всех без исключения программистах”.

Алексей
Алексей
Ответить на  Сергей Клочко
23.06.2024 15:28

Сергей, большое спасибо за обстоятельный ответ. Да, я тоже нашел информацию про сеттеры и геттеры.
Жаль, что зная о вашем ресурсе, проверял до раздела 5.1. ООП по ресурсу https://github.com/Pavellver/Yandex_handbook_answers/
Там только ответы, никаких пояснений нет. Полистаю предыдущие темы, сразу наткнулся на “читерский” способ спрямления вложенных списков.
Ряд задач пропустил, т.к. сам не решил и решение там не понял.
Спасибо.