From 6549f1f879781f66ef378f9f9950d61afca0eb87 Mon Sep 17 00:00:00 2001 From: Dylan Costo <99520325+DylanCosto@users.noreply.github.com> Date: Sat, 10 Oct 2026 23:43:01 -0400 Subject: [PATCH 1/2] Remove all-gap alignment columns without repeated list deletion --- lib/bx/align/core.py | 41 +++++++++++---------------------------- lib/bx/align/maf_tests.py | 9 ++++++++- 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/lib/bx/align/core.py b/lib/bx/align/core.py index 379736e..6864525 100644 --- a/lib/bx/align/core.py +++ b/lib/bx/align/core.py @@ -166,36 +166,17 @@ def remove_all_gap_columns(self): Remove any columns containing only gaps from alignment components, text of components is modified IN PLACE. """ - seqs = [] - for c in self.components: - if c.empty: - seqs.append(None) - try: - seqs.append(list(c.text)) - except TypeError: - seqs.append(None) - i = 0 - text_size = self.text_size - while i < text_size: - all_gap = True - for seq in seqs: - if seq is None: - continue - if seq[i] != "-": - all_gap = False - if all_gap: - for seq in seqs: - if seq is None: - continue - del seq[i] - text_size -= 1 - else: - i += 1 - for i in range(len(self.components)): - if seqs[i] is None: - continue - self.components[i].text = "".join(seqs[i]) - self.text_size = text_size + components = [c for c in self.components if not c.empty and c.text is not None] + if any("-" not in c.text for c in components): + return + # Decide which columns to retain before modifying any component. Deleting + # individual gaps from lists repeatedly shifts the remaining alignment. + keep = [i for i in range(self.text_size) if any(c.text[i] != "-" for c in components)] + if len(keep) == self.text_size: + return + for component in components: + component.text = "".join(component.text[i] for i in keep) + self.text_size = len(keep) def __eq__(self, other): if other is None or not isinstance(other, type(self)): diff --git a/lib/bx/align/maf_tests.py b/lib/bx/align/maf_tests.py index d343f0f..d82b1e1 100644 --- a/lib/bx/align/maf_tests.py +++ b/lib/bx/align/maf_tests.py @@ -4,6 +4,8 @@ from io import StringIO +import pytest + from bx import align from bx.align import maf @@ -211,7 +213,8 @@ def test_column_iter(): assert c == expected[i] -def test_remove_all_gap_column(): +@pytest.mark.parametrize("empty_index", [0, 1, 3]) +def test_remove_all_gap_column(empty_index): complex_maf_gap = align.Alignment() complex_maf_gap.score = "7009" complex_maf_gap.components.append( @@ -231,7 +234,11 @@ def test_remove_all_gap_column(): complex_maf_gap.components[-1].empty = True complex_maf_gap.components[-1].synteny_empty = maf.MAF_INSERT_STATUS complex_maf_gap.text_size = 11 + empty = complex_maf_gap.components.pop() + complex_maf_gap.components.insert(empty_index, empty) complex_maf_gap.remove_all_gap_columns() + # Return the empty row to its fixture position for the equality check. + complex_maf_gap.components.append(complex_maf_gap.components.pop(empty_index)) assert complex_maf_gap == complex_maf From 5637291dd37a2842785d834dae4c31912ac687a7 Mon Sep 17 00:00:00 2001 From: Dylan Costo <99520325+DylanCosto@users.noreply.github.com> Date: Sun, 11 Oct 2026 00:05:19 -0400 Subject: [PATCH 2/2] Compact shared alignment components only once --- lib/bx/align/core.py | 2 ++ lib/bx/align/maf_tests.py | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/bx/align/core.py b/lib/bx/align/core.py index 6864525..3772191 100644 --- a/lib/bx/align/core.py +++ b/lib/bx/align/core.py @@ -167,6 +167,8 @@ def remove_all_gap_columns(self): text of components is modified IN PLACE. """ components = [c for c in self.components if not c.empty and c.text is not None] + # A component can occur more than once; rebuild each object only once. + components = list({id(c): c for c in components}.values()) if any("-" not in c.text for c in components): return # Decide which columns to retain before modifying any component. Deleting diff --git a/lib/bx/align/maf_tests.py b/lib/bx/align/maf_tests.py index d82b1e1..cf0710c 100644 --- a/lib/bx/align/maf_tests.py +++ b/lib/bx/align/maf_tests.py @@ -214,7 +214,8 @@ def test_column_iter(): @pytest.mark.parametrize("empty_index", [0, 1, 3]) -def test_remove_all_gap_column(empty_index): +@pytest.mark.parametrize("repeat_component", [False, True]) +def test_remove_all_gap_column(empty_index, repeat_component): complex_maf_gap = align.Alignment() complex_maf_gap.score = "7009" complex_maf_gap.components.append( @@ -236,7 +237,11 @@ def test_remove_all_gap_column(empty_index): complex_maf_gap.text_size = 11 empty = complex_maf_gap.components.pop() complex_maf_gap.components.insert(empty_index, empty) + if repeat_component: + complex_maf_gap.add_component(next(c for c in complex_maf_gap.components if not c.empty)) complex_maf_gap.remove_all_gap_columns() + if repeat_component: + complex_maf_gap.components.pop() # Return the empty row to its fixture position for the equality check. complex_maf_gap.components.append(complex_maf_gap.components.pop(empty_index)) assert complex_maf_gap == complex_maf