Skip to content

Commit f9edb70

Browse files
saahilmahatoalxkm
andauthored
feat: add smith-waterman sequence alignment algorithm (#6708)
Co-authored-by: a <alexanderklmn@gmail.com>
1 parent 50b1bcd commit f9edb70

File tree

2 files changed

+109
-0
lines changed

2 files changed

+109
-0
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.thealgorithms.dynamicprogramming;
2+
3+
/**
4+
* Smith–Waterman algorithm for local sequence alignment.
5+
* Finds the highest scoring local alignment between substrings of two sequences.
6+
*
7+
* Time Complexity: O(n * m)
8+
* Space Complexity: O(n * m)
9+
*/
10+
public final class SmithWaterman {
11+
12+
private SmithWaterman() {
13+
// Utility Class
14+
}
15+
16+
/**
17+
* Computes the Smith–Waterman local alignment score between two strings.
18+
*
19+
* @param s1 first string
20+
* @param s2 second string
21+
* @param matchScore score for a match
22+
* @param mismatchPenalty penalty for mismatch (negative)
23+
* @param gapPenalty penalty for insertion/deletion (negative)
24+
* @return the maximum local alignment score
25+
*/
26+
public static int align(String s1, String s2, int matchScore, int mismatchPenalty, int gapPenalty) {
27+
if (s1 == null || s2 == null) {
28+
throw new IllegalArgumentException("Input strings must not be null.");
29+
}
30+
31+
int n = s1.length();
32+
int m = s2.length();
33+
int maxScore = 0;
34+
35+
int[][] dp = new int[n + 1][m + 1];
36+
37+
for (int i = 1; i <= n; i++) {
38+
for (int j = 1; j <= m; j++) {
39+
int matchOrMismatch = (s1.charAt(i - 1) == s2.charAt(j - 1)) ? matchScore : mismatchPenalty;
40+
41+
dp[i][j] = Math.max(0,
42+
Math.max(Math.max(dp[i - 1][j - 1] + matchOrMismatch, // match/mismatch
43+
dp[i - 1][j] + gapPenalty // deletion
44+
),
45+
dp[i][j - 1] + gapPenalty // insertion
46+
));
47+
48+
if (dp[i][j] > maxScore) {
49+
maxScore = dp[i][j];
50+
}
51+
}
52+
}
53+
54+
return maxScore;
55+
}
56+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.thealgorithms.dynamicprogramming;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.params.ParameterizedTest;
8+
import org.junit.jupiter.params.provider.CsvSource;
9+
10+
/**
11+
* Unit tests for the {@code SmithWaterman} class.
12+
*/
13+
class SmithWatermanTest {
14+
15+
@Test
16+
void testIdenticalStrings() {
17+
int score = SmithWaterman.align("GATTACA", "GATTACA", 2, -1, -2);
18+
assertEquals(14, score); // full match, 7*2
19+
}
20+
21+
@Test
22+
void testPartialMatch() {
23+
int score = SmithWaterman.align("GATTACA", "TTAC", 2, -1, -2);
24+
assertEquals(8, score); // best local alignment "TTAC"
25+
}
26+
27+
@Test
28+
void testNoMatch() {
29+
int score = SmithWaterman.align("AAAA", "TTTT", 1, -1, -2);
30+
assertEquals(0, score); // no alignment worth keeping
31+
}
32+
33+
@Test
34+
void testInsertionDeletion() {
35+
int score = SmithWaterman.align("ACGT", "ACGGT", 1, -1, -2);
36+
assertEquals(3, score); // local alignment "ACG"
37+
}
38+
39+
@Test
40+
void testEmptyStrings() {
41+
assertEquals(0, SmithWaterman.align("", "", 1, -1, -2));
42+
}
43+
44+
@ParameterizedTest
45+
@CsvSource({"null,ABC", "ABC,null", "null,null"})
46+
void testNullInputs(String s1, String s2) {
47+
String first = "null".equals(s1) ? null : s1;
48+
String second = "null".equals(s2) ? null : s2;
49+
50+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> SmithWaterman.align(first, second, 1, -1, -2));
51+
assertEquals("Input strings must not be null.", ex.getMessage());
52+
}
53+
}

0 commit comments

Comments
 (0)