3
\$\begingroup\$

What do I need help with?

I'm doing the challenge below, in which I need to increase code performance. It's currently at 63% and I need to increase it to at least 71% however I can't optimize more than the current one.
How can it be optimized?

Introduction

You are a member of a biotechnology programming team that is responsible for creating a system for lab technicians, which will assist them with drug analysis.

Your goal is to create the application that will let them input their findings into the system, provide a meaningful analysis and verify the correctness of the data that they have sent.

Prerequisites

To complete this task, use Python 3.

Task Details

Note: Please do NOT modify any tests unless specifically told to do so.

Part 1

Your goal in this part is to implement the app.drug_analyzer.DrugAnalyzer class. It will be responsible for analyzing data like the data presented below:

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
  • The initialization of the class can be done from Python's list of lists (or nothing) and stored in the instance variable called data as per example below:
>> 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
[]
  • The class should also have an option to add single lists into the object. Adding a list to the DrugAnalyzer object should return a new instance of this object with an additional element. Adding improper type or a list with improper length should raise a ValueError. An example of a correct and wrong addition output is shown below:
>>> 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.

Part 2

Implement the verify_series method inside the app.drug_analyzer.DrugAnalyzer class.

The goal of this method is to receive a list of parameters and use them to verify if the pills described inside the instance variable data matches the given criteria. It should return a Boolean value as a result.

The function would be called as follows:

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

Where:

  • the series_id is a 3 characters long string that is present at the beginning of every pill_id, before the - sign, for example, L01 is the series_id in pill_id = L01-12.
  • the act_subst_wgt is the expected weight (mg) of the active substance content in the given series in one pill.
  • the act_subst_rate is the allowed rate of difference in the active substance weight from the expected one. For example, for 100 mg, the accepted values would be between 95 and 105.
  • the allowed_imp is the allowed rate of impure substances in the pill_weight. For example, for 1000 mg pill_weight and 0.001 rate, the allowed amount of impurities is 1 mg.

The function should take all pills that are part of the L01 series, sum their weight and calculate if the amount of active_substance, as well as impurities, match the given rates. It should return True if both conditions are met and False if any of them is not met.

The False result should mean that all the passed parameters are proper, but either the active_substance amount or the impurities amount is improper.
In case of a series_id that is not present in the data at all or in case of any improper parameter, the function should throw a ValueError.
Please think what could be the possible edge case in such a scenario.

Example:

>>> 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.

My Code:

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

    def __add__(self, other):
        if len(self.data[0]) != len(other):
            raise ValueError('Improper length of the added list.')

        if type(other[0]) is not str or type(other[1]) is not float or type(other[2]) is not float or type(other[3]) is not float:
            raise ValueError('Wrong type.')

        total_data = self.data
        total_data.append(other)
        return DrugAnalyzer(total_data)

    def verify_series(
        self,
        series_id: str,
        act_subst_wgt: float,
        act_subst_rate: float,
        allowed_imp: float,
    ) -> bool:
    
        lists = list(filter(lambda k: series_id in k[0], self.data))
        pills_weight = sum([liste[1] for liste in lists])
        actives_substances = sum([liste[2] for liste in lists])
        impurities = sum([liste[3] for liste in lists])

        if (len(lists) * act_subst_wgt * (1 + act_subst_rate)) > actives_substances > (len(lists) * act_subst_wgt * (1 - act_subst_rate)):
            if impurities < (allowed_imp * pills_weight):
                return True
        return False
\$\endgroup\$
4
  • 1
    \$\begingroup\$ This fails but should succeed: my_analyzer = DrugAnalyzer(); my_new_analyzer = my_analyzer + ['G03-01', 789.01, 129.00, 0.00008] \$\endgroup\$
    – AJNeufeld
    Commented Oct 14, 2021 at 16:35
  • 1
    \$\begingroup\$ This returns a value, but should raise an exception: DrugAnalyzer().verify_series('L01', 0, 0, 0) \$\endgroup\$
    – AJNeufeld
    Commented Oct 14, 2021 at 16:38
  • \$\begingroup\$ Does the problem specify any limits on the size of various inputs. That is, how many different pill_id's, series_id's, additions, or calls to verify_series? \$\endgroup\$
    – RootTwo
    Commented Oct 14, 2021 at 18:28
  • \$\begingroup\$ Welcome to Code Review@SE. Brainly's recruitment? For content not originated by you, please tell who did and where you encountered it - hyperlinks welcome. \$\endgroup\$
    – greybeard
    Commented Oct 17, 2021 at 11:07

2 Answers 2

6
\$\begingroup\$
  • __init__ has a mutable default argument. The default argument is not reset between calls to a function, so multiple analyzers that start out empty will have the same list, which could be a problem if one of them changes:

    >>> a1 = DrugAnalyzer()
    >>> a1.data.append(['G03-01', 789.01, 129.00, 0.00008])
    >>> a2 = DrugAnalyzer()
    >>> a2.data
    [['G03-01', 789.01, 129.0, 8e-05]]
    

    One option to avoid that could be explicitly copying the argument's content to a new list, perhaps like

    def __init__(self, drug_data=None):
        self.data = []
        if drug_data:
            self.data[:] = drug_data
    
  • It feels weird that __add__ verifies the shape of its input, but __init__ doesn't. Especially since the pre-existing data is used to decide whether a list is valid to add with __add__ - what if someone did DrugAnalyzer([[]])? Then you could only add 0-length lists to that analyzer, and a 0-length list will never pass the type check!

  • By the way, the reason I used a1.data.append instead of += earlier? __add__ does not work on empty DrugAnalyzers - it tries to get self.data[0], which won't work if self.data is empty

  • __add__ modifies self.data. Try:

    >>> a1 = DrugAnalyzer([['G03-01', 789.01, 129.00, 0.00008]])
    >>> a2 = a1 + ['G02-03', 1111.95, 125.04, 3.00100]
    >>> a1.data
    [['G03-01', 789.01, 129.0, 8e-05], ['G02-03', 1111.95, 125.04, 3.001]]
    

    This is because both the old and new analyzer share the same list. You probably want to explicitly create a new list instead - total_data = self.data + [other] could be one way to do that

  • The if in verify_series is annoyingly long. I would consider at least calculating len(lists) * act_subst_weight) in advance and going if target_weight * (1 + act_subst_rate) > active_substances > (1 - act_subst_rate). Or perhaps something like actual_rate = actives_substances / act_subst_weight followed by if (1 + act_subst_rate) > actual_rate > (1 - act_subst_rate)

  • verify_series does not throw a ValueError for non-existing pill series' as the requirements demand, but instead returns False

  • verify_series may mis-identify pills as belonging to the wrong pill series - if I understand the requirements correctly, G03-06 should only belong to the G03 series, not G0, 06 or 3-0, but this implementation will count it as being part of all those. Something like series_id == k[0].split('-')[0] or k[0].startswith(series_id + "-") might be more robust

\$\endgroup\$
1
  • \$\begingroup\$ Wow, very good, it helped a lot. \$\endgroup\$ Commented Nov 9, 2021 at 15:32
-1
\$\begingroup\$

You should consider validating all entries and throwing errors for invalid data. Once all inputs are tested.

\$\endgroup\$
2
  • 2
    \$\begingroup\$ For a programming-challenge, it is implied that all data is valid. It is a controlled environment and one of the few situations in which case input-validation is a waste of resources. \$\endgroup\$
    – Mast
    Commented Apr 2, 2022 at 10:09
  • \$\begingroup\$ It is not implied. This is one of the tests. \$\endgroup\$ Commented Apr 3, 2022 at 11:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.