Back to List

Primer Library Deduplicator: Consolidating three concepts into a single tool.

This outlines the step-by-step creation of a Python tool designed to efficiently remove duplicates from large primer or oligo libraries in O(n) time. The tool leverages a combination of hash sets, dictionaries, and regular expressions to achieve this.

Intermediate
|
75min
|
Verified (2026-07)
PrimerUpload.Remove duplicates.Hash set.Reverse phase chromatography.Rank normalization.
Progress0/8 (0%)

Primer Library Deduplicator โ€” Combining 3 Concepts to Create a Single Tool

After Completing This Topic

You will be able to combine the three concepts โ€“ hash tables, dictionaries, and regular expressions โ€“ that you learned individually from the textbook (DryBench) and create a practical tool that can find duplicates in a primer library of tens of thousands of entries almost instantly. This is more than just "removing duplicates"; you will gain a deeper understanding of why naive methods are slow and how each concept contributes to a specific part of the tool.

This article is an educational, general example. It is not based on a specific library from a research lab, but rather uses the "problem of increasing oligonucleotide inventory" as a topic that recurs in molecular biology.


"Oh, isn't this what we ordered last time?" โ€” The Problems We Face

When working in the lab, performing PCR, cloning, qPCR, and sequencing, we constantly order primers (short DNA oligonucleotides, usually 18-25 nt). Initially, a single Excel sheet seemed sufficient, containing information like name, sequence, purpose, order date, and Tm.

However, after a year or two, this sheet grows to thousands or even tens of thousands of rows. When multiple people use it, problems arise:

  • Mr. Kim registers a sequence as hGAPDH_F, and later, Ms. Lee registers the exact same sequence with a different name, GAPDH_forward.
  • Some use ATGCGT... in uppercase, while others use atgcgt... in lowercase.
  • When copying and pasting, spaces or invisible tabs are added to the beginning or end of the sequence.
  • Some sequences are entered as the reverse complement, and it turns out to be the same as an existing primer.
  • Typos result in sequences like ATGXGT, containing characters other than ACGT.

What is the result? The same oligonucleotide is ordered two or three times, wasting money, and when experimenting, we get confused, wondering, "Is this primer the correct one?"

So, we naively try to "clean up the duplicates" by writing code that compares each sequence to every other sequence.

python
# Naive approach โ€” compare all pairs (intentionally slow version)
def find_duplicates_naive(sequences):
duplicates = []
for i in range(len(sequences)):
for j in range(i + 1, len(sequences)):
if sequences[i] == sequences[j]:
duplicates.append((i, j))
return duplicates

With 100 samples, it's quick. But with 10,000? The number of comparisons is approximately 10000 ร— 10000 / 2 = 50 million. It takes tens of seconds to minutes on a laptop. With 100,000, it takes 100 times longer. Even after finishing a cup of coffee, it's not done.

Instead of stopping here, let's ask some questions. Why is it slow? And what tools are needed to make it instantaneous? This article will provide the answers by building the solution with code.

Let's See the Finished Product First (Run the Black Box First)

Starting with a blank editor can be disorienting. So, let's run the final tool we're going to build first, and then open it up and examine its internals. (This is the "top-down" learning approach used in places like fast.ai โ€“ play a complete game first, then learn the rules.)

Here's how we use the finished dedup_primers.py:

python
report = dedup_primer_library("primers.csv")
print(report.summary())

This will produce a report like this:

text
=== Primer Library Deduplication Report ===
Total entries    : 12,480
Valid entries    : 12,451 (29 entries with format errors excluded)
Unique sequences : 9,832
Exact duplicates : 2,619 (same sequence, different names)
Reverse complement duplicates : 174 (sequences that are the reverse complement of each other)
Processing time  : 0.31 seconds
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
Top 3 Most Frequent Sequences
  1) ATGGCACCACAGTCCATGCC  ร—7   (GAPDH_F, hGAPDH_forward, ...)
  2) GTCGACCTGCAGGCATGCAA  ร—5
  3) CCTGCAGGTCGACTCTAGAG  ร—4

There are two key takeaways. First, 0.31 seconds. The naive version took several minutes, but this finishes in an instant. Second, it doesn't just indicate "duplicate/not duplicate," but also tells us which sequences are duplicated, how many times, and with what names. This entire article is about how to achieve these two things.

What Components Does This Tool Consist Of? (Component Breakdown)

The "Deduplication Engine" we will assemble is essentially a machine composed of three interconnected textbook concepts. Like a model kit, let's label where each component comes from.

text
Deduplication Engine
   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   โ”‚                                                                                โ”‚
   โ”‚   [Input] Read CSV File โ”€โ”€โ”€โ”€ Component: File I/O                               โ”‚  โ† Provided as a finished product (tool)
   โ”‚              โ”‚                                                                 โ”‚
   โ”‚              โ–ผ                                                                 โ”‚
   โ”‚   [Purify] Sequence Normalization/Validation โ”€โ”€ Component: Regular Expressions โ”‚  โ† You will create this yourself โ˜…
   โ”‚              โ”‚                                                                 โ”‚
   โ”‚              โ–ผ                                                                 โ”‚
   โ”‚   [Judge] Immediately Determine Duplicates โ”€โ”€โ”€โ”€ Component: Hash Set            โ”‚  โ† You will create this yourself โ˜…
   โ”‚              โ”‚                                                                 โ”‚
   โ”‚              โ–ผ                                                                 โ”‚
   โ”‚   [Aggregate] Group Names by Sequence โ”€โ”€ Component: Dictionary                 โ”‚  โ† You will create this yourself โ˜…
   โ”‚              โ”‚                                                                 โ”‚
   โ”‚              โ–ผ                                                                 โ”‚
   โ”‚   [Output] Report                                                              โ”‚
   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
ComponentWhere You Learned It (DryBench)What It Does in This Tool
File I/Ostring-and-file-ioReads names and sequences from the CSV
Regular Expressionsregex-pattern-matchingValidates and cleans the sequences to ensure they consist only of ACGT
Hash Sethash-tableDetermines if a sequence has been seen before in O(1) time
Dictionarylist-and-dictionaryGroups the names associated with each sequence

๐Ÿ“Œ If you are unfamiliar with these concepts (link at the top) These three are the core components of this tutorial. If they are unfamiliar, warm up with a 30-second review in the sidebar (right-hand mini-card) before proceeding. We will revisit them as needed.

Now, let's build them one by one, from top to bottom. The file reading component is already complete and provided as a tool, so you only need to focus on the three components: purification, judgment, and aggregation. Just three, right? The limit of new concepts a person can grasp at once is three, so we deliberately divided it that way.

Step 0: Understanding the Data

Before writing any code, let's examine the raw data. When the primer sheet is exported as a CSV, it looks something like this:

csv
name,sequence,purpose,date
GAPDH_F,ATGGCACCACAGTCCATGCC,qPCR,2025-01-12
GAPDH_R,GGCATGGACTGTGGTCATGAG,qPCR,2025-01-12
hGAPDH_forward, atggcaccacagtccatgcc ,qPCR,2025-06-03
cloning_F,GTCGACCTGCAGGCATGCAA,cloning,2025-02-20
bad_entry,ATGXGTNN,cloning,2025-03-01

Even with a quick glance, we can spot some issues.

  • GAPDH_F and hGAPDH_forward have the same sequence but different names. Moreover, the latter has leading and trailing whitespace and is in lowercase.
  • The sequence ATGXGTNN for bad_entry is not a valid DNA sequence because it contains the character X (likely a typo or abbreviation).

Therefore, to accurately count "duplicates," we need to normalize the sequences (make them consistent) and filter out invalid sequences before comparing them. Otherwise, we might mistakenly treat ATGGCACCACAGTCCATGCC and atggcaccacagtccatgcc as distinct sequences and miss the duplication.

This is the key insight: Half of the challenge in removing duplicates isn't about "comparing well," but rather about "making sure the data is in a comparable format" before the comparison.


Step 1: Loading the File (This part is provided as a completed solution)

Loading the file is not the learning objective of this tutorial. Your mental workspace is valuable, so we will provide this "boilerplate" code as a completed solution and move on. (It's the same as what you learned in the string-and-file-io card. If it's unfamiliar, check the tutorial.)

python
import csv
def load_primers(filepath):
"""Reads a list of (name, original sequence) tuples from a CSV file."""
records = []
with open(filepath, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
name = row["name"].strip()
seq = row["sequence"] # We intentionally don't clean it here โ€“ we'll handle it in the next step
records.append((name, seq))
return records
# In the exercise, we'll simulate a file with a list (to run it directly in the browser).
raw_records = [
("GAPDH_F", "ATGGCACCACAGTCCATGCC"),
("GAPDH_R", "GGCATGGACTGTGGTCATGAG"),
("hGAPDH_forward", " atggcaccacagtccatgcc "),
("cloning_F", "GTCGACCTGCAGGCATGCAA"),
("bad_entry", "ATGXGTNN"),
]
assert len(raw_records) == 5
assert raw_records[2][1] == " atggcaccacagtccatgcc " # The whitespace and lowercase characters are preserved

raw_records is still raw and messy. It contains lowercase letters, whitespace, and incorrect sequences. We will clean it up in the next step.


Step 2: Sequence Normalization and Validation โ˜… (Start here)

โœ๏ธ This is the section you will fill in. Component = Regular Expression. Goal: Transform sequences into a "standardized" form and filter out non-DNA sequences.

First, let's define "standardized". Our rules will be:

  1. Remove leading and trailing whitespace.
  2. Convert to all uppercase.
  3. Check if it consists only of A, C, G, and T. If not, discard it.

The third step is where regular expressions shine. "Does the entire string consist of repetitions of ACGT?" can be expressed with a single regular expression pattern: ^[ACGT]+$.

๐Ÿ”Ž Why Regular Expressions? (Drawer โ€” regex-pattern-matching) ^ denotes the beginning of the string, $ denotes the end, [ACGT] means "one of these four", and + means "one or more repetitions". Combined, it means "from the beginning to the end, only ACGT is allowed." You could also check each character with an if statement, but a regular expression expresses this intention in one line.

python
import re
# Pattern to check if the entire sequence consists of A/C/G/T (pre-compile for reuse)
DNA_PATTERN = re.compile(r"^[ACGT]+$")
def normalize(seq):
"""Remove whitespace + convert to uppercase. (Not yet validated)"""
return seq.strip().upper()
def is_valid_dna(seq):
"""Check if the normalized sequence is pure DNA."""
return DNA_PATTERN.match(seq) is not None

Now, let's verify.

python
assert normalize(" atggcaccacagtccatgcc ") == "ATGGCACCACAGTCCATGCC"
assert is_valid_dna("ATGGCACCACAGTCCATGCC") is True
assert is_valid_dna("ATGXGTNN") is False # Contains X, N โ†’ Discarded
assert is_valid_dna("") is False # Empty string also discarded (+ takes care of this)

Pay attention to the last assert. The reason the empty string is filtered is because we used + (one or more) in the pattern. If we had used * (zero or more), the empty string would have passed as "valid DNA". A single regular expression symbol determines data quality.

๐Ÿค” Self-Explanatory Prompt Why is it correct to perform normalize first and is_valid_dna later? If you were to validate first and then normalize, what would happen? Use atgc (lowercase) as an example to explain this yourself. (Hint: Will atgc match ^[ACGT]+$)

Now, complete the function that transforms raw records into a list of "cleaned sequence + name". Invalid entries will be collected separately and reported as "number of entries excluded" in the report.

python
def clean_records(raw_records):
cleaned = [] # (name, standardized sequence)
rejected = [] # (name, original) โ€” format error
for name, seq in raw_records:
norm = normalize(seq)
if is_valid_dna(norm):
cleaned.append((name, norm))
else:
rejected.append((name, seq))
return cleaned, rejected
cleaned, rejected = clean_records(raw_records)
assert len(cleaned) == 4 # Only bad_entry is removed
assert len(rejected) == 1
assert rejected[0][0] == "bad_entry"
# Key point: even if the names are different, the sequences are now exactly the same
assert cleaned[0][1] == cleaned[2][1] == "ATGGCACCACAGTCCATGCC"

The last line is the victory of this step. GAPDH_F and hGAPDH_forward now have the literally same sequence. Before normalization, they were different strings to the computer. Now, we are ready to count "duplicates".


Step 3: Detect Duplicates Instantly (Hash Set)

โœ๏ธ Fill-in-the-blanks section. Component = Hash Set. Goal: Answer "Have I seen this sequence before?" in O(1) time.

Let's recall why the initial, naive version was slow. It compared each sequence to every previously seen sequence. With 'n' items, this results in comparisons proportional to nยฒ.

๐Ÿ”Ž O(nยฒ) vs. O(n) (Drawer โ€” Big O Notation) Checking with in on a list involves traversing the list from the beginning โ€“ O(n) for each item, resulting in O(nยฒ) overall. In contrast, in on a set uses hashing to calculate the location directly, answering in O(1) time โ€“ resulting in O(n) overall. If the data increases tenfold, the list approach becomes 100 times slower, while the set approach only becomes 10 times slower.

The key to a hash set is this: feeding a sequence into a hash function produces a "slot number (address)," and looking directly at that slot allows for near-constant time checks, regardless of the amount of data. It's like finding a book in a library by its call number. In Python, set is precisely this hash table.

Let's implement the principle we learned in the hash-table card in just three lines of code.

python
def find_exact_duplicates(cleaned):
"""Finds duplicate pairs (names) of exactly the same sequence. O(n)."""
seen = set() # Sequences seen so far (hash set)
unique = [] # Sequences seen for the first time (name, sequence)
duplicates = [] # Duplicate sequences (name, sequence)
for name, seq in cleaned:
if seq in seen: # โ† O(1) check! This is the key
duplicates.append((name, seq))
else:
seen.add(seq)
unique.append((name, seq))
return unique, duplicates
unique, dups = find_exact_duplicates(cleaned)
assert len(unique) == 3 # GAPDH_F, GAPDH_R, cloning_F
assert len(dups) == 1 # hGAPDH_forward (same sequence as GAPDH_F)
assert dups[0][0] == "hGAPDH_forward"
assert dups[0][1] == "ATGGCACCACAGTCCATGCC"

if seq in seen: โ€“ this single line is the hero that transformed several minutes into 0.3 seconds. If seen were a list, this check would become increasingly slow as seen grew, but because it's a set, the check speed remains constant even with 100,000 items.

๐Ÿค” Self-explanatory prompt If you change seen from set() to [] (list), the code will still "run" and produce the same results. However, why shouldn't you do that? Explain what happens with 10,000 and 100,000 items, and relate it to O(nยฒ).

Step 4: Group by Sequence โ€“ How many times and with what names is it duplicated? โ˜… (Dictionary)

โœ๏ธ Manually filled section. Components = Dictionary. Goal: For each sequence, gather "the names that used this sequence."

After Step 3, you can determine whether there are duplicates. However, in practice, what you really want to know is, "How many times and with how many names has this sequence been registered?" GAPDH_F, hGAPDH_forward, GAPDH_qF... You need to gather these scattered entries under one sequence to organize them.

This is precisely what a dictionary does. Key = sequence, Value = a list of names that use that sequence.

๐Ÿ”Ž Why a dictionary? (Drawer โ€“ list and dictionary) A dictionary also uses a hash table internally (it's a close relative of the set from Step 3). While a set only remembers "exists/doesn't exist," a dictionary remembers "the value associated with the key." It is perfect for one-to-many groupings like "sequence โ†’ list of names."

python
from collections import defaultdict
def group_by_sequence(cleaned):
"""Groups by sequence, with the sequence as the key and a list of names that use that sequence as the value."""
groups = defaultdict(list)
for name, seq in cleaned:
groups[seq].append(name) # If the key doesn't exist, an empty list is automatically created
return groups
groups = group_by_sequence(cleaned)
# The GAPDH sequence should have 2 names associated with it
assert groups["ATGGCACCACAGTCCATGCC"] == ["GAPDH_F", "hGAPDH_forward"]
assert len(groups["GGCATGGACTGTGGTCATGAG"]) == 1 # GAPDH_R is unique
assert len(groups) == 3 # 3 unique sequences

Thanks to defaultdict(list), we were able to achieve "add if the key exists, create if it doesn't" with a single line, groups[seq].append(name), without needing to use if statements for branching. Now, if we extract only those with a value of 2 or more from this groups, we will get a "list of duplicated sequences."

python
def duplicated_groups(groups):
"""Selects only the sequences with 2 or more names associated with them (i.e., duplicated sequences) and sorts them in descending order of duplication."""
dups = {seq: names for seq, names in groups.items() if len(names) > 1}
# Sort in descending order of duplication count
return sorted(dups.items(), key=lambda kv: len(kv[1]), reverse=True)
ranked = duplicated_groups(groups)
assert len(ranked) == 1
assert ranked[0][0] == "ATGGCACCACAGTCCATGCC"
assert ranked[0][1] == ["GAPDH_F", "hGAPDH_forward"]

The "Top 3 most duplicated sequences" in the report comes from this ranked. The three components (regular expression, hash set, and dictionary) come together here.

Step 5: Going Further โ€“ Handling Reverse Complements (Advanced)

So far, we've only identified exact duplicates (sequences with identical characters). However, there's another nuance in biology: reverse complements.

Since DNA is double-stranded, the complementary strand to 5'-ATGC-3' is 3'-TACG-5', which, when read in reverse, becomes 5'-GCAT-3'. If someone enters a primer based on the complementary strand, the characters might look completely different, but it could still be the same oligonucleotide that binds to the same location.

To handle this, we can designate the "sequence or its reverse complement that comes first alphabetically" as the canonical form. This way, both the original sequence and its reverse complement will map to the same canonical form. This is an extension of the "normalization" we did in Step 2, and the power of normalization shines again here.

python
COMPLEMENT = str.maketrans("ACGT", "TGCA")
def reverse_complement(seq):
return seq.translate(COMPLEMENT)[::-1]
def canonical(seq):
"""Designate the alphabetically smaller of the sequence and its reverse complement as the canonical form."""
rc = reverse_complement(seq)
return min(seq, rc)
# Verification: Two sequences that are reverse complements should have the same canonical form.
assert reverse_complement("ATGC") == "GCAT"
assert canonical("ATGC") == canonical("GCAT") # Both converge to the one that is alphabetically smaller than "ATGC"
assert canonical("AAAA") == "AAAA" # It is smaller than its reverse complement "TTTT", so it remains itself.

Now, simply replace seq with canonical(seq) in Steps 3 and 4, and you'll handle reverse complement duplicates at once. We haven't created any new components; we've simply added a "canonical form" lens to the existing identification and aggregation components. A good tool expands in this way.

๐Ÿค” Self-Explanatory Prompt How can we use canonical to count the number of exact duplicates and the number of reverse complement duplicates separately? (Hint: Whether seq == canonical(seq) or not is a clue.)

Assembling the Parts โ€“ A Complete Engine

Now, by assembling the five steps, you will have the tool that you saw at the beginning.

python
def dedup_primer_library(raw_records, use_canonical=False):
cleaned, rejected = clean_records(raw_records)
key = canonical if use_canonical else (lambda s: s)
seen = set()
groups = defaultdict(list)
exact_dups = 0
for name, seq in cleaned:
k = key(seq)
if k in seen:
exact_dups += 1
else:
seen.add(k)
groups[k].append(name)
ranked = sorted(
((k, names) for k, names in groups.items() if len(names) > 1),
key=lambda kv: len(kv[1]),
reverse=True,
)
return {
"total": len(raw_records),
"valid": len(cleaned),
"rejected": len(rejected),
"unique": len(groups),
"exact_duplicates": exact_dups,
"top": ranked[:3],
}
report = dedup_primer_library(raw_records)
assert report["total"] == 5
assert report["valid"] == 4
assert report["rejected"] == 1
assert report["unique"] == 3
assert report["exact_duplicates"] == 1
assert report["top"][0][1] == ["GAPDH_F", "hGAPDH_forward"]

If all of these assert statements pass, then you have successfully combined three textbook concepts to create a working tool. Congratulations.

So, How Much Faster Is It? (Performance Deep Dive)

Simply saying something is "faster" isn't enough. Let's put it to the test. We'll create a set of 10,000 sequences (with duplicates) and pit the naive version against the hash version.

python
import random, time
random.seed(42)
bases = "ACGT"
pool = ["".join(random.choice(bases) for _ in range(20)) for _ in range(2000)]
# 2000 sequences, repeated 5 times each โ†’ 10,000 (lots of duplicates)
big = [(f"p{i}", random.choice(pool)) for i in range(10000)]
# Naive version (O(nยฒ)) โ€” testing on the first 1500 is slow enough
sample = [s for _, s in big[:1500]]
t0 = time.time()
naive_pairs = find_duplicates_naive(sample)
naive_time = time.time() - t0
# Hash version (O(n)) โ€” testing on all 10,000
t0 = time.time()
report = dedup_primer_library(big)
hash_time = time.time() - t0
print(f"Naive version (1,500) : {naive_time:.3f} seconds")
print(f"Hash version (10,000) : {hash_time:.3f} seconds")
# Even though there's more than 6 times the data, the hash version is significantly faster
assert hash_time < naive_time

The numbers will vary depending on the machine, but the trend will always be the same. Even with a small dataset, the naive version is slower, and the gap widens dramatically as the data grows. This is a tangible demonstration of the difference between O(nยฒ) and O(n). The two curves you saw as graphs in the big-o-notation card are now diverging on your screen, measured by the stopwatch.


There's More Than One Way (Multi-Path Reflection)

You just solved the problem using a hash set + dictionary. Great job. But there's more than one way to solve the same problem. True skill comes from knowing "what other methods exist besides my own, and when are they better?"

  • Pandas drop_duplicates(): If your data is already in a DataFrame, you can solve it with a single line: df.drop_duplicates(subset="seq"). Internally, it also uses a hash. It's simply a library doing what we implemented manually. When is our approach better? When you need to extract the number of duplicates and the list of names in a customized way. drop_duplicates simply removes duplicates; it doesn't report how many times each item was duplicated.
  • Biopython: There are already specialized tools for sequence parsing and reverse complement operations. The reverse complement can be safely handled with Seq(...).reverse_complement(). In practice, it's best to use these well-tested libraries. However, if you don't understand the underlying principles, you won't be able to debug why it's slow or why the results are unexpected. That's why we built it from scratch.
  • Sort and Compare Adjacent Elements: You can also sort the sequences (O(n log n)) and then compare only adjacent elements. This saves memory compared to using a set, which is advantageous when the data is too large to fit in RAM.

Key takeaway: Our tool isn't the "right" answer; the person who understands the trade-offs of each method is. Hashing is fast but uses memory, sorting saves memory but is slightly slower, and libraries are convenient but you don't know what's going on inside.

Next Steps (Links to Further Exploration)

If you want to expand this tool further, the following concepts are natural next steps.

  • Currently, we load all sequences into memory. What if the file is larger than RAM? โ†’ Learn how to "search quickly without loading everything into memory" using binary search and database indexing, and then move on to the practical application of sequence database indexing.
  • Real-time duplicate detection on the web? โ†’ Explore this through a practical application using WebSockets.
  • Want to delve deeper into why sets are O(1)? โ†’ Return to the hash table card and master collision resolution.

Hands-on Exercise (Independent Problem)

This time, let's apply your tools to a completely new scenario without the skeleton. The structure remains the same: Normalization โ†’ Validation โ†’ Aggregation.

  1. Add Length Filter: Primers are typically 18-25 nt long. Extend clean_records to send sequences outside this range to rejected. (Verify that a 30 nt sequence is filtered out using assert.)
  2. GC Content Warning: Calculate the GC content of each unique sequence and add a warning to the report if it falls outside the 40-60% range. (This is a preview of the next application, GC Content Calculator.)
  3. Reverse Complement Report: When running with use_canonical=True, count "exact duplicates" and "reverse complement duplicates" separately and display them in two separate lines in the report. (Use the hint from the self-explanatory prompt in step 5.)
  4. Challenge: To classify sequences containing IUPAC abbreviation characters (e.g., N, R, Y) as "abbreviated sequences" instead of "invalid", how would you modify the regular expression in step 2?

Self-assess each task using assert. If you pass, you are now the master of this tool.

Summary

We started with a seemingly trivial problem โ€“ eliminating duplicates โ€“ and explored how three textbook concepts can work together to form a practical tool.

  • Regular expressions were used to standardize the format of the sequences before comparison and filter out invalid entries. (Refinement)
  • Hash sets efficiently answered the question "Have we seen this before?" in O(1) time, reducing the processing time from several minutes to 0.3 seconds. (Validation)
  • Dictionaries grouped names by sequence, allowing us to determine how many times each name was duplicated. (Aggregation)

These three concepts, which might seem abstract when learned individually, come together to solve a real-world problem. This is the power of application โ€“ learn concepts individually and assemble them as tools.

To reiterate, this article presents a general educational example. In your actual library, there will be many more columns, rules, and exceptions. You can build upon this framework and add the necessary details. The underlying structure remains the same: normalization, validation, and aggregation.

๐Ÿ’ฌ Questions & Comments

0 comments

You can post without signing in. Guest comments cannot be edited or deleted by their author.

0/2000

Loading...