Интервью: класс анализатора наркотиков

введение таково:

Вы являетесь членом команды разработчиков биотехнологий, которая отвечает за создание системы для лаборантов, которая поможет им с анализом лекарств.

Ваша цель — создать приложение, которое позволит им вводить свои выводы в систему, предоставлять значимый анализ и проверять правильность отправленных данных.

задания:

Ваша цель в этой части — реализовать app.drug_analyzer.DrugAnalyzer учебный класс. Он будет отвечать за анализ данных, таких как данные, представленные ниже:

+-----------+-------------+------------------+-------------+
|   pill_id | pill_weight | active_substance | impurities  |
+-----------+-------------|------------------|-------------|
|    L01-10 | 1007.67     | 102.88           | 1.00100     |
|    L01-06 |  996.42     | 99.68            | 2.00087     |
|    G02-03 | 1111.95     | 125.04           | 3.00004     |
|    G03-06 |  989.01     | 119.00           | 4.00062     |
+-----------+-------------+-------------+-------------+-----

Инициализация класса может быть выполнена из списка списков Python (или ничего) и сохранена в переменной экземпляра с именем data, как показано в примере ниже:

>>> my_drug_data = [
...                 ['L01-10', 1007.67, 102.88, 1.00100],
...                 ['L01-06', 996.42, 99.68, 2.00087],
...                 ['G02-03', 1111.95, 125.04, 3.00100],
...                 ['G03-06', 989.01, 119.00, 4.00004]
... ]
>>> my_analyzer = DrugAnalyzer(my_drug_data)
>>> my_analyzer.data
[['L01-10', 1007.67, 102.88, 0.001], ['L01-06', 996.42, 99.68, 0.00087], > ['G02-03', 1111.95, 125.04, 0.00100], ['G03-06', 989.01, 119.00, 0.00004]]
>>> DrugAnalyzer().data
[]

У класса также должна быть возможность добавлять отдельные списки в объект. Добавление списка в DrugAnalyzer объект должен вернуть новый экземпляр этого объекта с дополнительным элементом. Добавление неправильного типа или списка неправильной длины должно вызвать ValueError. Пример правильного и неправильного вывода сложения показан ниже:

>>> my_new_analyzer = my_analyzer + ['G03-01', 789.01, 129.00, 0.00008]
>>> my_new_analyzer.data
[['L01-10', 1007.67, 102.88, 0.001], ['L01-06', 996.42, 99.68, 0.00087], > ['G02-03', 1111.95, 125.04, 0.00100], ['G03-06', 989.01, 119.00, 0.00004], ['G03-01', 789.01, 129.00, 0.00008]]
>>> my_new_analyzer = my_analyzer + ['G03-01', 129.00, 0.00008]
Traceback (the most recent call is displayed as the last one):
  File "<stdin>", line 1, in <module>
ValueError: Improper length of the added list.

Часть 2

Реализовать verify_series метод внутри app.drug_analyzer.DrugAnalyzer учебный класс.

Цель этого метода — получить список параметров и использовать их для проверки того, соответствуют ли таблетки, описанные в данных переменных экземпляра, заданным критериям. В результате он должен вернуть логическое значение.

Функция будет вызываться следующим образом:

verify_series(series_id = 'L01', act_subst_wgt = 100, act_subst_rate = 0.05, allowed_imp = 0.001)

Где:

  • в series_id представляет собой строку длиной 3 символа, которая присутствует в начале каждого pill_id, перед знаком -; Например, L01 это series_id в pill_id = L01-12.
  • в act_subst_wgt — ожидаемая масса (мг) содержания активного вещества в данной серии в одной таблетке.
  • в act_subst_rate — допустимая величина отклонения массы действующего вещества от ожидаемой. Например, для 100 мг допустимые значения будут от 95 до 105.
  • в allowed_imp допустимая норма нечистых веществ в pill_weight. Например, на 1000 мг pill_weight
    и 0,001 нормы, допустимое количество примесей — 1 мг.

Функция должна принимать все таблетки, входящие в состав L01 серии, просуммируйте их вес и вычислите, если количество active_substance, а также примеси соответствуют заданным показателям. Он должен вернуться True если оба условия соблюдены и False если какой-либо из них не выполняется.

В False Результат должен означать, что все переданные параметры верны, но либо количество активного вещества, либо количество примесей неправильное. В случае, если series_id вообще отсутствует в данных или в случае какого-либо неправильного параметра, функция должна выдать ValueError. Пожалуйста, подумайте, какой может быть крайний случай в таком сценарии.

Пример:

>>> my_drug_data = [
...                 ['L01-10', 1000.02, 102.88, 1.00100],
...                 ['L01-06', 999.90, 96.00, 2.00087],
...                 ['G02-03', 1000, 96.50, 3.00100],
...                 ['G03-06', 989.01, 119.00, 4.00004]
... ]
>>> my_analyzer = DrugAnalyzer(my_drug_data)
>>> my_analyzer.verify_series(series_id = 'L01', act_subst_wgt = 100, act_subst_rate = 0.05, allowed_imp = 0.001)
False
>>> // The overall active_substances weight would be 198.88, which is within the given rate of 0.05 for 200 mg (2 * act_subst_wgt).
>>> // However, the sum of impurities would be 3.00187, which is more than 0.001*1999.92 (allowed_imp_rate * (1000.02 + 999.90).
>>> my_analyzer.verify_series(series_id = 'L01', act_subst_wgt = 100, act_subst_rate = 0.05, allowed_imp = 0.0001)
True
>>> my_analyzer.verify_series(series_id = 'B03', act_subst_wgt = 100, act_subst_rate = 0.05, allowed_imp = 0.001)
Traceback (the most recent call is displayed as the last one):
  File "<stdin>", line 1, in <module>
ValueError: B03 series is not present within the dataset.

мой код прошел все тесты

После моей первой неудачи я попытался сделать код как можно более компактным и легким, используя понимание списков и исключая ненужные переменные, хотя это увеличило мою оценку всего на 5%. Может ли кто-нибудь сказать мне, что не так с моим кодом и как я могу написать лучший код?

мой последний код

(оценка: 58%)

  class DrugAnalyzer:
    def __init__(self, data):
        self.data = data

    def __add__(self, data):
        if len(data) == 4:
            if all(isinstance(i, float) for i in data[1:]) and isinstance(data[0], str):
                self.data = self.data + [data]
                return self
            else:
                raise ValueError('Improper type on list added')
        else:
            raise ValueError('improper length on list added')

    def verify_series(
        self,
        series_id: str,
        act_subst_wgt: float,
        act_subst_rate: float,
        allowed_imp: float,
    ) -> bool:
        pills = [pill for pill in self.data if series_id in pill[0]]
        if pills:
            return act_subst_wgt*len(pills)-(act_subst_wgt * len(pills) * act_subst_rate) < sum([i[2] for i in pills]) < act_subst_wgt*len(pills)+(act_subst_wgt * len(pills) * act_subst_rate) and sum([i[3] for i in pills]) < allowed_imp*sum([i[1] for i in pills])
        else:
            raise ValueError(f'There is no {series_id} series in database')

Предыдущая версия

(оценка: 55%)

class DrugAnalyzer():
    def __init__(self,data):
        self.data = data

    def __add__(self,data1):
        if len(data1) < 4:
            raise ValueError('improper lenght of list added')
        if type(data1[0]) == str:
            if type(data1[1]) and type(data1[2]) and type(data1[3]) == float:
                self.data = self.data + [data1]
                return self
            else:
                raise ValueError('Improper type on list added')
        else: raise ValueError('Improper type on list added')


    def verify_series(
        self,
        series_id: str,
        act_subst_wgt: float,
        act_subst_rate: float,
        allowed_imp: float,
    ) -> bool:
        serie = []
        for pill in self.data:
            if series_id in pill[0]:
                serie.append(pill)
                active = []
                imp = []
                weight = []
                print(serie)
                for pill in serie:
                    weight.append(pill[1])
                    active.append(pill[2])
                    imp.append(pill[3])

        if serie == 0:
            raise ValueError('the serie isnt in list')

        dif = (act_subst_wgt*len(serie))*act_subst_rate
        if  act_subst_wgt*len(serie)-dif < sum(active) < act_subst_wgt*len(serie)+dif and sum(imp) < allowed_imp*sum(weight):
            return True

        else:
            return False

2 ответа
2

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

def __add__(self, data):
    if len(data) != 4:
        raise ValueError('improper length on list added')
    if not all(isinstance(i, float) for i in data[1:]) or not isinstance(data[0], str):
        raise ValueError('Improper type on list added')
    self.data = self.data + [data]
    return self

Мне тоже непонятно, почему вы настаиваете на data[1:] существование float. Fraction будет работать отлично, как и многие другие типы, пока + и < определены.


series_id представляет собой строку длиной 3 символа, которая присутствует в начале каждого pill_id, перед - знак

Я не понимаю, как это требование реализовано.


        act_subst_wgt*len(pills)-(act_subst_wgt * len(pills) * act_subst_rate) < sum([i[2] for i in pills]) < act_subst_wgt*len(pills)+(act_subst_wgt * len(pills) * act_subst_rate) and sum([i[3] for i in pills]) < allowed_imp*sum([i[1] for i in pills])

совершенно нечитаемо.

  • Ваше второе замечание о float можно улучшить, сказав использовать numbers.Real вместо этого в isinstance чек об оплате.

    — Пейлонрайз

Я не знаю, как рассчитываются эти оценки, поэтому не могу сказать, что могло бы улучшить ваш результат. Ваш код выглядит как нормальная реализация спецификации с два три детали «неправильные».

  1. В спецификации не сказано, что операция добавления должна изменять self, это фактически означает, что операция должна нет модифицировать self. «Добавление списка к объекту DrugAnalyzer должно вернуть новый экземпляр этого объекта с дополнительным элементом». Это согласуется с тем, как мы обычно думаем о +; 5+1 возвращается 6, но это не так изменять 5.
  2. if series_id in pill[0] будет истинным для 'L01' и 'G02-L01'; ты хочешь startswith. (Обратите внимание, что это метод большую строку.)
  3. Вы не учли пустой вызов конструктора.

Другие вещи:

  • Вы используете подсказки типа; Замечательно! Убедитесь, что вы подтверждаете их с помощью MyPy, и используйте их для всего.
  • пикодестиль поможет сохранить стандартное форматирование. Это довольно строго; Я не всегда им пользуюсь. Но ваша линия-23 действительно плохая!
  • Я не могу себе представить, чтобы проверка Только происходит при добавлении к существующему объекту; он должен быть централизованным. Может быть, он может быть централизован в своем собственном классе? Таким образом у вас может быть согласованный «список объектов строк» ​​вместо менее надежного «списка списков». Однако потребуется дополнительное оборудование для преобразования … В любом случае, я бы использовал классы данных за это.
  • Сам код валидации, на мой взгляд, можно немного подправить.
  • В спецификации говорится «… функция должна выдать ошибку ValueError. Подумайте, какой может быть крайний случай в таком сценарии».
    Может, они имеют в виду отрицательные числа или что-то в этом роде; Я НЕ ЗНАЮ? Кто бы ни создавал этот API, плохо справляется, но это не в ваших руках!
  • Многие функции (например, sum) может принимать голые генераторы вместо списка.
  • Я думаю, что для проверки неравенств вы должны технически использовать версии «или равно», но я очень надеюсь, что это не имеет значения!

IDK, насколько полезной будет эта версия для вас. Если вы запустите его, вы обнаружите, что утверждения в конце не проходят; они основаны на примерах вывода оболочки в вашем вопросе, которые, кажется, имеют неправильные значения в последнем столбце вывода? Это может быть действительно важная деталь, которую вы упустили, но у меня нет времени. Сожалею!

import collections.abc as c  # these are _classes_, not _types_.
from dataclasses import dataclass
from numbers import Real  # again, a class, for typing we'll use float.
from typing import Any, Iterable, Sequence, List  # these are _types_.
#    You can just use List for everything; I like to split hairs.


@dataclass(frozen=True)  # I think they should be frozen by default.
class PillRow:
    pill_id: str
    series: str
    weight: float
    active: float
    impure: float

    @staticmethod
    def from_data(data: Any) -> "PillRow":
        if isinstance(data, PillRow):
            return data
        try:
            assert isinstance(data, c.Sized) and isinstance(data, c.Iterable), ("Could not parse data.", data)
            assert 4 == len(data), f"Four columns needed; found {len(data)}."
            pill_id, pill_weight, active_substance, impurities = data
            assert isinstance(pill_id, str), ("Column One must be the pill id.", pill_id)
            series_id = pill_id.split('-')[0]
            assert 3 == len(series_id), ("Could not parse series id from the pill id.", pill_id)
            assert isinstance(pill_weight, Real), ("Invalid pill weight.", pill_weight)
            assert isinstance(active_substance, Real), ("Invalid active-substance weight.", active_substance)
            assert isinstance(impurities, Real), ("Invalid impurities weight.", impurities)
            return PillRow(pill_id=pill_id,
                           series=series_id,
                           weight=float(pill_weight),
                           active=float(active_substance),
                           impure=float(impurities))
        except AssertionError as ae:
            raise ValueError(ae)

    def to_list(self) -> list:
        return [self.pill_id, self.weight, self.active, self.impure]


class DrugAnalyzer:
    def __init__(self, *datas: Iterable[Sequence]):  # taking *args will make __add__ easier to write.
        self._data = [PillRow.from_data(pill)
                      for data in datas
                      for pill in data]
        self.data = [pill.to_list() for pill in self._data]

    def __add__(self, data):
        return DataAnalyzer(self._data, [data])  # that's what all the above work was for!

    def verify_series(self,
                      series_id: str,
                      act_subst_wgt: float,
                      act_subst_rate: float,
                      allowed_imp: float) -> bool:
        pills = [p for p in self._data if p.series == series_id]
        if not pills:
            raise ValueError(f'There is no {series_id} series in database')
        else:
            num_pills = len(pills)
            target_active = act_subst_wgt * num_pills
            margin_active = target_active * act_subst_rate
            actual_active = sum(p.active for p in pills)
            max_impure = allowed_imp * sum(p.weight for p in pills)
            actual_impure = sum(p.impure for p in pills)
            return (abs(target_active - actual_active) <= margin_active) and (actual_impure <= max_impure)


if __name__ == "__main__":
    # tests constructors
    my_drug_data = [
            ['L01-10', 1007.67, 102.88, 1.00100],
            ['L01-06', 996.42, 99.68, 2.00087],
            ['G02-03', 1111.95, 125.04, 3.00100],
            ['G03-06', 989.01, 119.00, 4.00004]
    ]
    my_analyzer = DrugAnalyzer(my_drug_data)
    assert tuple(map(tuple, my_analyzer.data)) == (
            ('L01-10', 1007.67, 102.88, 0.001),
            ('L01-06', 996.42, 99.68, 0.00087),
            ('G02-03', 1111.95, 125.04, 0.00100),
            ('G03-06', 989.01, 119.00, 0.00004)
        ), tuple(map(tuple, my_analyzer.data))
    assert tuple(DrugAnalyzer().data) == (), DrugAnalyzer().data

    # tests plus
    my_new_analyzer = my_analyzer + ['G03-01', 789.01, 129.00, 0.00008]
    assert tuple(map(tuple, my_new_analyzer.data)) == (
            ('L01-10', 1007.67, 102.88, 0.001),
            ('L01-06', 996.42, 99.68, 0.00087),
            ('G02-03', 1111.95, 125.04, 0.00100),
            ('G03-06', 989.01, 119.00, 0.00004),
            ('G03-01', 789.01, 129.00, 0.00008)
        ), my_new_analyzer.data
    thrown = False
    try:
        my_new_analyzer = my_analyzer + ['G03-01', 129.00, 0.00008]
    except ValueError:
        thrown = True
    assert thrown, my_new_analyzer

    # tests verify
    my_drug_data = [['L01-10', 1000.02, 102.88, 1.00100],
                    ['L01-06', 999.90, 96.00, 2.00087],
                    ['G02-03', 1000, 96.50, 3.00100],
                    ['G03-06', 989.01, 119.00, 4.00004]]
    my_analyzer = DrugAnalyzer(my_drug_data)
    assert not my_analyzer.verify_series(series_id='L01',
                                         act_subst_wgt=100,
                                         act_subst_rate=0.05,
                                         allowed_imp=0.001)
    assert my_analyzer.verify_series(series_id='L01',
                                     act_subst_wgt=100,
                                     act_subst_rate=0.05,
                                     allowed_imp=0.0001)
    thrown = False
    try:
        my_analyzer.verify_series(series_id='B03',
                                  act_subst_wgt=100,
                                  act_subst_rate=0.05,
                                  allowed_imp=0.001)
    except ValueError:
        thrown = True
    assert thrown
```

  • Утверждение, улавливание утверждения и повторное повышение как ValueError разбивать исходную стопку … не здорово? Лучше бы вам заменить утверждения отдельными повышениями вашего собственного типа ошибки. Даже если вы сохраните свой текущий паттерн утверждение-ререйз, вы, возможно, захотите raise ValueError(str(ae)) from ae чтобы сохранить исходную стопку.

    — Райндериен

  • Это нет здорово. Спецификация запрашивает конкретный тип ошибки, и я действительно думаю, что нужно иметь отдельный if...raise... по каждой причине слишком многословен. Утверждение / ловля / повторный рейз плохо пахнет, но дает понять суть. С использованием raise...from сохраняет некоторую дополнительную информацию, что хорошо, но в данном случае это действительно затрудняет чтение сообщений об ошибках, поэтому я чувствую себя противоречивым. В идеале / интуитивно понятно, что предложение об ошибке assert... может быть фактическим исключением, которое вы хотите вызвать, но python этого не делает 🙁

    — ShapeOfMatter


  • Я полагаю, что всегда есть опасность, что кто-то запустит это с -O установлен флаг …

    — ShapeOfMatter

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *