BP101T · Semester 1 · B.Pharm

Unit III — Data Structures & File Handling

Basics of Python Programming for Pharmaceutical Sciences

This study page introduces Data Structures & File Handling in Basics of Python Programming for Pharmaceutical Sciences (BP101T), B.Pharm Semester 1. Read the full notes below, or download the unit PDF for offline study.

Topics in this unit

  • Lists, tuples, dictionaries, indexing, slicing, and basic string manipulation
  • NumPy array creation and arithmetic operations
  • Reading and writing CSV files
  • Structured healthcare datasets and small pharmaceutical-data access and manipulation practice

Full notes for this unit

Read the notes here, or download the PDF below for offline study. Plots and diagrams are in the PDF. All data in the examples is synthetic teaching data.

Lists Hold Batches

Index map - five tablet weights (mg)

Positions: 0 1 2 3 4 left to right; from the right: -5 ... -1. w[0] is first, w[-1] is last. Off-by-one is the classic viva trap - index 5 does not exist here.

batch_weights.py
# Batch weights in mg - synthetic teaching data
weights = [480, 495, 505, 512, 498]
print(weights[0]) # first tablet
print(weights[-1]) # last tablet
print(len(weights)) # batch size

Result: 480 | 498 | 5

Slicing cuts a sub-batch in one move

The slice rule w[start:stop:step]

start is included, stop is excluded, step defaults to 1. So w[1:4] takes positions 1, 2, 3 - three items, never the 4th. w[::-1] reverses the whole batch.

slice_batch.py
# Same batch: [480, 495, 505, 512, 498]
print(weights[1:4]) # middle three
print(weights[:2]) # first two
print(weights[::-1]) # reversed QC check

Result: [495, 505, 512] | [480, 495] | [498, 512, 505, 495, 480]

SlicePositions takenResult
w[1:4]1, 2, 3[495, 505, 512]
w[:2]0, 1[480, 495]
w[::-1]all, backwards[498, 512, 505, 495, 480]

Grow, shrink, sort and count the list

Four verbs that run a dispensary list

append(x) adds one item at the end +1 · remove(x) deletes by value -1 · sort() orders in place · len() counts. append vs extend: append adds one box, extend pours in many.

restock.py
# Restock program - synthetic teaching data
stock = [480, 495, 505]
stock.append(507) ; print(stock)
stock.remove(495) ; print(stock)
print(sorted([505, 480, 512]))

Result: [480, 495, 505, 507] | [480, 505, 507] | [480, 505, 512]

Tuples Seal the Record

Why immutable? The batch-record argument

A batch record (id, drug, mg) must survive audits untouched. Immutability is a tamper seal: the QC stamp, assay value and expiry cannot be silently overwritten the way a list can. Use tuples for facts, lists for work-in-progress.

batch_record.py
# Sealed batch record - synthetic teaching data
batch = ('B-1042', 'Paracetamol', 500, 25.0)
print(batch[1], len(batch))
batch[1] = 'Aspirin' # forbidden!

Result: Paracetamol 4 | TypeError: 'tuple' object does not support item assignment

Dicts Name Every Field

patient_dict.py
# Patient record + mini formulary - synthetic teaching data
rec = {'name': 'Asha', 'age': 34, 'drug': 'Metformin', 'dose_mg': 500}
print(rec['drug'], rec['dose_mg'])
formulary = {'PCM': 500, 'AMX': 250}
print(formulary['PCM'])

Result: Metformin 500 | 500

get, update and keys - the safe toolkit

Three methods, one warning

get(k, default) reads without crashing on missing keys · update({...}) adds or overwrites fields · keys() / values() / items() list the contents. Direct rec[missing] raises KeyError - in a dispensary, always prefer get.

dict_ops.py
# rec from previous page
print(rec.get('drug'))
print(rec.get('allergy', 'NKDA'))
rec.update({'stock': 120})
print(list(rec.keys()))

Result: Metformin | NKDA | ['name', 'age', 'drug', 'dose_mg', 'stock']

Strings Clean Drug Names

MethodWhat it doesExample result
strip()trims edge spaces' TAB ' -> 'TAB'
split(',')cuts on commas'a,500,cap' -> 3 parts
join()glues with '-''Amoxicillin-500mg'
upper()/title()case standard'PARACETAMOL'
clean_names.py
print(' paracetamol 500MG '.strip().title())
print('amoxi,500,cap'.split(','))
print('-'.join(['Amoxicillin', '500mg']))

Result: Paracetamol 500Mg | ['amoxi', '500', 'cap'] | Amoxicillin-500mg

NumPy Arrays

arrays_create.py
import numpy as np
doses = np.array([250, 500, 750])
print(doses, doses.mean(), doses.sum())
print(np.zeros(3).tolist(), np.arange(0, 10, 2).tolist())

Result: [250 500 750] 500.0 1500 | [0.0, 0.0, 0.0] [0, 2, 4, 6, 8]

Creation verbs to memorise

np.array([...]) from a list · np.zeros(3) / np.ones(4) placeholders · np.arange(0,10,2) steps like range · .mean() / .sum() / .max() summarise instantly. All outputs above are real executed results.

One line beats a loop - dose for five weights

Vectorisation in one picture

Loop version repeats the multiply five times in Python. Vectorised version writes w * 10 once and NumPy multiplies all five in compiled C - same answer, a fraction of the time on large wards.

dose_vector.py
import numpy as np
w = np.array([40, 50, 60, 70, 80])
doses = w * 10 # mg-per-kg rate
print(doses.tolist(), doses.mean(), doses.sum())
loop = [x * 10 for x in [40, 50, 60, 70, 80]]
print(loop)

Result: [400, 500, 600, 700, 800] 600.0 3000 | [400, 500, 600, 700, 800]

Writing patients.csv

write_patients.py
import csv
rows = [['pid','name','drug','dose_mg','stock'],
    ['P01','Asha','Metformin',500,120],
    ['P02','Ravi','Amoxicillin',250,8],
    ['P03','Meena','Paracetamol',500,45],
    ['P04','Kiran','Insulin',100,5]]
with open('patients.csv', 'w', newline='') as f:
    csv.writer(f).writerows(rows)

Result: patients.csv saved - 5 lines: header + 4 patients (P01-P04)

Reading back with DictReader - real file I/O

DictReader names every column

csv.DictReader(f) turns each row into a dict keyed by the header - row["drug"] instead of row[2]. Note: every value arrives as text, so wrap stock in int() before comparing.

read_patients.py
import csv
with open('patients.csv') as f:
    recs = list(csv.DictReader(f))
print(len(recs), [r['name'] for r in recs])
print([r['name'] for r in recs if int(r['stock']) < 10])

Result: 4 ['Asha', 'Ravi', 'Meena', 'Kiran'] | ['Ravi', 'Kiran']

Columns are fields, rows are patients

How to read any healthcare table

A column is one field measured the same way for everyone (drug, dose_mg). A row is one patient visit. The header row names the columns; everything below is data. Shape of our file: 4 rows x 5 columns.

pidnamedrugdose_mgstock
P01AshaMetformin500120
P02RaviAmoxicillin2508
P03MeenaParacetamol50045
P04KiranInsulin1005

Import + manipulate in two verbs

import: DictReader loads the file into a list of dicts · manipulate: filter int(stock) < 10, sort by dose, count by drug. Master those two and every small pharma dataset obeys you.

Formulary lookup + low-stock report

What the demo proves

A formulary dict answers dose lookups in O(1); a list-of-dicts scan flags every stock below 10. Lookup + filter + report - the three moves of dispensary software in a few lines.

master_demo.py
# formulary lookup + low-stock report - synthetic teaching data
form = {'Metformin': 500, 'Amoxicillin': 250, 'Insulin': 100}
print('Dose:', form.get('Insulin', 'unknown'))
low = [r['name'] for r in recs if int(r['stock']) < 10]
print('Reorder:', ', '.join(low))

Result: Dose: 100 | Reorder: Ravi, Kiran

IndexError vs KeyError - never confuse them

ErrorTriggerSafe habit
IndexErrorw[5] on 5-item listcheck len() first
KeyErrorrec["allergy"] missinguse get(k, default)
TypeErroredit a tuplerebuild, don't assign
safe_access.py
weights = [480, 495, 505, 512, 498]
try: weights[5]
except IndexError: print('Bad index: only 0-4 exist')
try: rec['allergy']
except KeyError: print('Use get(): NKDA assumed')

Result: Bad index: only 0-4 exist | Use get(): NKDA assumed

The chooser table - one glance, full marks

Featurelisttupledict
Brackets[](){ k: v }
Ordered?yesyesinsertion order
Mutable?yesNO - sealedyes (by key)
Access byindex w[0]index t[0]key rec["drug"]
Pharma usebatch weightsbatch recordpatient file

Chooser rule in one line

Changing pile? list · Sealed fact? tuple · Named fields? dict. Audit = tuple, queue = list, record = dict - write that sentence and the 5-marker is yours.

Three drills examiners repeat

Drill 1 - negative-index slicing drill

neg_slice.py
# tail drills - synthetic teaching data
w = [480, 495, 505, 512, 498]
print(w[-3:]) # last three
print(w[:-1]) # drop last
print(w[-4:-1]) # middle three

Result: [505, 512, 498] | [480, 495, 505, 512] | [495, 505, 512]

Drill 2 - dict .get() with default vs KeyError

dict_get.py
# safe lookup - synthetic teaching data
rec = {'drug': 'Metformin', 'dose_mg': 500}
print(rec.get('drug')) # safe hit
print(rec.get('allergy', 'NKDA')) # safe miss
print(rec['allergy']) # crashes: KeyError

Result: Metformin | NKDA | KeyError: 'allergy'

Drill 3 - batch totals: comprehension vs loop

batch_totals.py
# double the stock - synthetic teaching data
stocks = [120, 8, 45, 5]
loop = []
for s in stocks:
    loop.append(s * 2)
comp = [s * 2 for s in stocks]
print(loop, comp, loop == comp)

Result: [240, 16, 90, 10] | [240, 16, 90, 10] | True

Trace first

Drill 1: -3: takes the tail - Drill 2: get never crashes - Drill 3: same list, one line beats four. Synthetic teaching data.

Exam Points

  • Indexing: first is w[0], last is w[-1] - off-by-one costs the mark.
  • Slicing: w[1:4] takes 1,2,3 - stop excluded, never the 4th.
  • Tuple: immutable - batch record; edit raises TypeError.
  • Dict: get(k, default) avoids KeyError; direct rec[k] crashes.
  • Strings: methods return new strings - strip / split / join / upper; capture the result.
  • NumPy: w * 10 vectorises five doses at once; .mean() / .sum() summarise.
  • CSV: writerows saves, DictReader reads dicts; values arrive as text - int() first.
  • Program: formulary lookup + stock < 10 filter + join reorder line - practise tracing it.

Five questions - answer before you turn the page

  • Q1. weights has 5 items - why does weights[5] raise IndexError?
  • Q2. What does weights[1:4] return, and why only three items?
  • Q3. Batch record must survive audit - tuple or list, and why?
  • Q4. rec['allergy'] is missing - which call avoids KeyError?
  • Q5. How does w * 10 in NumPy differ from a five-round loop?

Answers (cover, then recall)

  • A1. Valid indexes are 0-4; 5 is one past the end.
  • A2. [495, 505, 512] - stop index 4 is excluded.
  • A3. Tuple - immutable, tamper-sealed for audit.
  • A4. rec.get('allergy', 'NKDA') - default, no crash.
  • A5. Same values; vectorised runs once in C, loop repeats in Python.

Unit III in one line

Unit 3 teaches you how to store pharmacy data (list / tuple / dict), compute it (NumPy) and keep it (CSV) - so batch, dose and stock work runs itself.

Pinpoints - Must Remember

HookRecall in one line
0-based indexfirst item is w[0]; 5 items live at 0-4
Negative indexw[-1] is last; w[-3:] takes the tail
Slice stop-outw[1:4] takes 1,2,3 - stop excluded
get() defaultrec.get(k, default) never raises KeyError
Tuple immutablesealed batch record; edit raises TypeError
append vs extendappend adds one box, extend pours many
len() guardcheck len() before w[i]; beats IndexError
CSV reader rowsDictReader gives dicts; values are text - int() first
NumPy vectorizew * 10 hits all doses at once in C
Loop vs comp[s*2 for s in stocks] equals the 4-line loop

3 exam predictions

  • Trace w[-3:] and w[1:4] on a 5-item batch.
  • 2. Missing key: get vs rec[k] - which crashes?
  • 3. Rewrite the dose vector w * 10 plus low-stock filter stock < 10 from memory.

Mnemonic - ZERO GET SEAL

Zero-based, tail with minus-one; GET averts KeyError, SEAL the tuple, vectorise the rest. Say ZERO-GET-SEAL once and all ten hooks follow.

Download notes

Save the PDF for offline reading. On some browsers, the file opens first; use the PDF viewer’s download button to save it.

For education and exam preparation only, not medical advice or current regulatory guidance.

Other units in this subject

Back to BP101T notes