Skip to content

Commit 4fe37c3

Browse files
saahilmahatoalxkm
andauthored
feat: add needleman–wunsch sequence alignment algorithm (#6707)
Co-authored-by: a <alexanderklmn@gmail.com>
1 parent 061463a commit 4fe37c3

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.thealgorithms.dynamicprogramming;
2+
3+
/**
4+
* The Needleman–Wunsch algorithm performs global sequence alignment between two strings.
5+
* It computes the optimal alignment score using dynamic programming,
6+
* given a scoring scheme for matches, mismatches, and gaps.
7+
*
8+
* Time Complexity: O(n * m)
9+
* Space Complexity: O(n * m)
10+
*/
11+
public final class NeedlemanWunsch {
12+
13+
private NeedlemanWunsch() {
14+
// Utility Class
15+
}
16+
17+
/**
18+
* Computes the Needleman–Wunsch global alignment score between two strings.
19+
*
20+
* @param s1 the first string
21+
* @param s2 the second string
22+
* @param matchScore score for a character match
23+
* @param mismatchPenalty penalty for a mismatch (should be negative)
24+
* @param gapPenalty penalty for inserting a gap (should be negative)
25+
* @return the optimal alignment score
26+
*/
27+
public static int align(String s1, String s2, int matchScore, int mismatchPenalty, int gapPenalty) {
28+
if (s1 == null || s2 == null) {
29+
throw new IllegalArgumentException("Input strings must not be null.");
30+
}
31+
32+
int n = s1.length();
33+
int m = s2.length();
34+
35+
int[][] dp = new int[n + 1][m + 1];
36+
37+
// Initialize gap penalties for first row and column
38+
for (int i = 0; i <= n; i++) {
39+
dp[i][0] = i * gapPenalty;
40+
}
41+
for (int j = 0; j <= m; j++) {
42+
dp[0][j] = j * gapPenalty;
43+
}
44+
45+
// Fill the DP matrix
46+
for (int i = 1; i <= n; i++) {
47+
for (int j = 1; j <= m; j++) {
48+
int matchOrMismatch = (s1.charAt(i - 1) == s2.charAt(j - 1)) ? matchScore : mismatchPenalty;
49+
50+
dp[i][j] = Math.max(Math.max(dp[i - 1][j - 1] + matchOrMismatch, // match/mismatch
51+
dp[i - 1][j] + gapPenalty // deletion (gap in s2)
52+
),
53+
dp[i][j - 1] + gapPenalty // insertion (gap in s1)
54+
);
55+
}
56+
}
57+
58+
return dp[n][m];
59+
}
60+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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 NeedlemanWunsch} class
12+
*/
13+
class NeedlemanWunschTest {
14+
15+
@Test
16+
void testIdenticalStrings() {
17+
int score = NeedlemanWunsch.align("GATTACA", "GATTACA", 1, -1, -2);
18+
assertEquals(7, score); // All matches, 7*1
19+
}
20+
21+
@Test
22+
void testSimpleMismatch() {
23+
int score = NeedlemanWunsch.align("GATTACA", "GACTATA", 1, -1, -2);
24+
assertEquals(3, score);
25+
}
26+
27+
@Test
28+
void testInsertion() {
29+
int score = NeedlemanWunsch.align("GATTACA", "GATACA", 1, -1, -2);
30+
// One deletion (gap penalty)
31+
assertEquals(4, score);
32+
}
33+
34+
@Test
35+
void testEmptyStrings() {
36+
assertEquals(0, NeedlemanWunsch.align("", "", 1, -1, -2));
37+
}
38+
39+
@Test
40+
void testOneEmpty() {
41+
assertEquals(-14, NeedlemanWunsch.align("GATTACA", "", 1, -1, -2)); // 7 gaps × -2
42+
}
43+
44+
@Test
45+
void testGapHeavyAlignment() {
46+
int score = NeedlemanWunsch.align("AAAA", "AA", 1, -1, -2);
47+
assertEquals(-2, score); // Two matches (2*1) + two gaps (2*-2)
48+
}
49+
50+
@ParameterizedTest
51+
@CsvSource({"null,ABC", "ABC,null", "null,null"})
52+
void testNullInputs(String s1, String s2) {
53+
// Interpret "null" literal as Java null
54+
String first = "null".equals(s1) ? null : s1;
55+
String second = "null".equals(s2) ? null : s2;
56+
57+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> NeedlemanWunsch.align(first, second, 1, -1, -2));
58+
assertEquals("Input strings must not be null.", ex.getMessage());
59+
}
60+
}

0 commit comments

Comments
 (0)