dna-sequencing-match
PY file
"""
DNA Sequence Search System
Searches for gene sequences in complementary DNA strands stored in dna sequence 6GB
simulate DB with connection pooling (max 10 connections per database)
"""
from __future__ import annotations
import argparse
import os
import time
from typing import Iterable, List, Optional
def _human_readable(n: int) -> str:
for unit in ('B', 'KB', 'MB', 'GB', 'TB'):
if n < 1024:
return f"{n:.2f}{unit}"
n /= 1024
return f"{n:.2f}PB"
def reverse_complement(seq: bytes) -> bytes:
"""Return the reverse-complement of a DNA sequence bytes (A,T,G,C)."""
table = bytes.maketrans(b'ATGCatgc', b'TACGtacg')
return seq.translate(table)[::-1]
def normalize_pattern(p: str) -> bytes:
return p.strip().upper().encode('ascii')
def stream_search(file_path: str, pattern: bytes, chunk_size: int = 1 << 20) -> Iterable[int]:
"""Yield file offsets where `pattern` occurs in `file_path` using streaming reads.
This is memory efficient and handles matches that span chunk boundaries by
carrying an overlap of len(pattern)-1 bytes between chunks.
"""
if not pattern:
return
patlen = len(pattern)
overlap = patlen - 1
with open(file_path, 'rb') as f:
pos = 0
tail = b''
while True:
chunk = f.read(chunk_size)
if not chunk:
break
buf = tail + chunk
start = 0
while True:
idx = buf.find(pattern, start)
if idx == -1:
break
yield pos - len(tail) + idx
start = idx + 1
# prepare tail for next chunk
if overlap > 0:
tail = buf[-overlap:]
else:
tail = b''
pos += len(chunk)
def main(argv: Optional[List[str]] = None) -> int:
parser = argparse.ArgumentParser(description='Streaming DNA sequence search')
parser.add_argument('--file', '-f', help='Path to DNA sequence file', required=False)
parser.add_argument('--pattern', '-p', help='DNA pattern to search for (e.g. ATG)', required=False)
parser.add_argument('--pattern-file', help='File containing pattern (first line used)')
parser.add_argument('--reverse', '-r', action='store_true', help='Also search reverse-complement')
parser.add_argument('--chunk-size', type=int, default=1 << 20, help='Read chunk size in bytes')
parser.add_argument('--max-results', type=int, default=0, help='Stop after this many results (0 = unlimited)')
parser.add_argument('--example', action='store_true', help='Create a tiny example file and run a sample search')
args = parser.parse_args(argv)
if args.example:
sample = b'AAATGCCCATGTTTATGCGTATG'
sample_path = os.path.join(os.getcwd(), 'sample_dna.txt')
with open(sample_path, 'wb') as f:
f.write(sample)
size = os.path.getsize(sample_path)
print(f'Wrote example file: {sample_path} (size: {_human_readable(size)})')
args.file = sample_path
if not args.pattern:
args.pattern = 'ATG'
if not args.file:
parser.error('--file is required (or use --example)')
if args.pattern_file:
with open(args.pattern_file, 'r', encoding='utf-8') as pf:
args.pattern = pf.readline().strip()
if not args.pattern:
parser.error('--pattern is required (or use --pattern-file)')
pattern = normalize_pattern(args.pattern)
if any(c not in b'ATGC' for c in pattern):
print('Warning: pattern contains non-ATGC characters; results may be unexpected')
matches_found = 0
file_size = os.path.getsize(args.file) if os.path.exists(args.file) else 0
print(f"Searching {args.file} for pattern {pattern.decode()} (chunk={args.chunk_size}, size={_human_readable(file_size)})")
start_time = time.time()
for off in stream_search(args.file, pattern, chunk_size=args.chunk_size):
matches_found += 1
if args.max_results and matches_found >= args.max_results:
break
if args.reverse:
rc = reverse_complement(pattern)
print(f"Searching reverse-complement: {rc.decode()}")
for off in stream_search(args.file, rc, chunk_size=args.chunk_size):
matches_found += 1
if args.max_results and matches_found >= args.max_results:
break
duration = time.time() - start_time
if duration > 0:
rate = matches_found / duration
print(f"Search complete: {matches_found} matches in {duration:.2f}s ({rate:.2f} matches/s)")
else:
print(f"Search complete: {matches_found} matches (duration too short to measure)")
return 0
if __name__ == '__main__':
raise SystemExit(main())