2

I am working on a simple library that will help me work with prescription drugs. For instance, DrugBank contains a list of FDA approved drugs, and, importantly, how these drugs interact with one another. So, for instance, we might have drug A, which might then interact with drugs B and C in some way.

In a previous project doing something similar, my approach consisted of two steps: 1. I would load and construct the objects from the file, and store the interacting drug IDs into some list. Then 2., after constructing all the objects and putting them in a global dictionary, I would iterate over each object and replace the string IDs with a reference to the actual object.

I am writing my code in Python, but that's not really important, I just want to know what a good way to structure this might be.

So, in Python, some pseudocode might look something like this:

  1. Read and create the drug objects
class Drug:
    def __init__(self, id, name, interactions):
        self.id = id
        self.name = name
        self.interactions = self.interactions  # list[str]

so, I might construct my example drugs A, B, and C like this

drug_a = Drug("A", ["B", "C"])
drug_b = Drug("B", [])
drug_c = Drug("C", [])

and have them stored in some global dictionary e.g.

DRUGS = {"A": drug_a, "B": drug_b, "C": drug_c}
  1. Replace the drug ID strings in each Drug object with references to the actual drug objects
for drug in DRUGS.values():
    drug.interactions = [DRUGS[d_id] for d_id in drug.interactions]

This works well enough, but I'm wondering whether there is a better way, or an established design pattern for something like this. This two-step approach seems suboptimal and very much ad-hoc. But I can't really see a better way of doing things, so any insight would be interesting.

4
  • What you call "ad-hoc", I call a straightforward and simple solution with no need to make it more complicated by throwing some "pattern" at it. Can you tell us why you are unsatisfied with this approach, why you think it is "suboptimal" and what you would expect from a more optimal" solution?
    – Doc Brown
    Jan 22 at 16:38
  • I don't mean to imply there's anything wrong with this approach, it works completely fine. I am simply interested if there is an established way of doing this kind of thing. I came up with this approach on the fly, and I can't really see any drawbacks of it, but maybe I missed something? What I'm really looking for is maybe a confirmation that this is fine, or that there is an obviously better way of doing this that everyone does, but I couldn't find because I didn't know which keywords to search for. I am just interested in other ways of doing this that I missed.
    – Pavlin
    Jan 22 at 17:20
  • Take my word: your approach is fine. There are several variants possible, none of them "better" than the others. For example, you could use different arrays for the referenced IDs () and the references themselves, in case it turns out reusing the same array for both causes any trouble. Or you could keep only the referenced IDs in a member variable and determine the related objects at runtime from the DRUGS dictationary onyl when needed (instead of storing them in advance). All of these approaches have slightly different characteristics in performance, memory usage, and coupling ...
    – Doc Brown
    Jan 22 at 17:52
  • ... so just pick one which seems to fine for you and try out how it works.
    – Doc Brown
    Jan 22 at 17:53

2 Answers 2

1

I don't really follow why you would want to model this kind of data in this way. The most straight forward way to do with would be to create a set of interactions like this:

i0 = ('A', 'B')
i1 = ('A', 'C')

And then index them:

{
  'A': (i0, i1),
  'B': (i0),
  'C': (i1),
}

The variable declarations are unnecessary and just for demonstrative purposes.

In the real-world drug interactions are more than just a list of drugs that interact, there would be other data associated with an interaction such as what it does and/or contraindicated diagnoses. Interaction should therefore be its own type and which you would then index based on how you need to look up the interactions.

In short, I would not model interactions as a property of a Drug but flip it around and model drugs as one of the properties of an Interaction.

0

When I see this:

drug_b = Drug("B", [])

I see two problems. B is unaware that it has a drug interaction with A and the idea that A and B interact needs to be recorded twice.

This boils down to the use of the wrong data structure. You've essentially built a directed graph. But if drug A interacts with B then B interacts with A. So long as that's true you don't need this much control over a directed graph. What you really need is a undirected graph.

Consider:

edge("A", "B")

This captures the idea that they interact with each other in one line. Doesn't mater which comes first. It also doesn't force you to back this with any particular implementation which is a bonus. As one example you could back it with a dictionary that takes each edge as two key value pairs. "A", ["B"] and "B", ["A"]. The trick here is that the values are a list that you append to. This way any drug can be used to look up all of it's interacting drugs.

That means if you later say:

edge("A", "C")

No previous edge code needs to be updated. We’re exploiting the fact that while nodes connect to many nodes an edge always has exactly two nodes. Back this with a dictionary and it’ll look like this:

Key, Value
"A", ["B", "C"]
"B", ["A"]
"C", ["A"]

But you don't have to implement edge that way. There are others.

And you don't have to just stick to strings. You could have your drug objects. But you'd ensure they looked more like this:

drug_a = Drug("A", ["B", "C"])
drug_b = Drug("B", ["A"])
drug_c = Drug("C", ["A"])

If some one later added

edge("D", "C")

Then you could do something like:

drug_d = Drug("D", ["C"])
drug_c.interactions.append("D") 
2
  • I think for making a sensible asessment if the OP should really witch from an undirected graph representation to a directed graph, one needs to know more about the real use cases where the interactions will be used (including the use case "read the data from a file provided by a 3rd party").
    – Doc Brown
    Jan 23 at 8:47
  • @DocBrown better now? Jan 23 at 16:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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