Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
* [Matrix Chain Multiplication](https://github.com/TheAlgorithms/R/blob/HEAD/dynamic_programming/matrix_chain_multiplication.r)
* [Minimum Path Sum](https://github.com/TheAlgorithms/R/blob/HEAD/dynamic_programming/minimum_path_sum.r)
* [Subset Sum](https://github.com/TheAlgorithms/R/blob/HEAD/dynamic_programming/subset_sum.r)
* [Edit Distance (Levenshtein, Dynamic Programming)](https://github.com/TheAlgorithms/R/blob/HEAD/dynamic_programming/edit_distance.r)

## Graph Algorithms
* [Bellman Ford Shortest Path](https://github.com/TheAlgorithms/R/blob/HEAD/graph_algorithms/bellman_ford_shortest_path.r)
Expand Down
16 changes: 16 additions & 0 deletions documentation/edit_distance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Edit Distance

Levenshtein edit distance calculates the minimum number of single-character insertions, deletions, and substitutions required to transform one string into another.

``` r
source("dynamic_programming/edit_distance.r")

# Compute the edit distance
distance <- edit_distance("kitten", "sitting")
print(distance)

# Reconstruct the optimal sequence of operations
result <- edit_distance_with_path("kitten", "sitting")
print(result$distance)
print(result$operations)
```
105 changes: 105 additions & 0 deletions dynamic_programming/edit_distance.r
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# edit_distance.r
# Levenshtein edit distance algorithm in R
# Computes the minimum number of insertions, deletions, and substitutions
# required to transform one string into another.
# Time Complexity: O(m * n)
# Space Complexity: O(m * n)

# Compute the Levenshtein distance between two strings
edit_distance <- function(str1, str2) {
#' @param str1: First string
#' @param str2: Second string
#' @return: Integer edit distance
if (!is.character(str1) || !is.character(str2)) {
stop("Both inputs must be character strings.")
}
if (length(str1) != 1 || length(str2) != 1) {
stop("Each input must be a single string.")
}

m <- nchar(str1)
n <- nchar(str2)
dp <- matrix(0L, nrow = m + 1, ncol = n + 1)

# base cases: transform empty prefix
dp[, 1] <- seq(0L, m)
dp[1, ] <- seq(0L, n)

for (i in seq_len(m) + 1L) {
for (j in seq_len(n) + 1L) {
cost <- if (substr(str1, i - 1, i - 1) == substr(str2, j - 1, j - 1)) 0L else 1L
dp[i, j] <- min(
dp[i - 1, j] + 1L, # deletion
dp[i, j - 1] + 1L, # insertion
dp[i - 1, j - 1] + cost # substitution
)
}
}

return(dp[m + 1, n + 1])
}

# Compute the edit distance and reconstruct an optimal alignment path
edit_distance_with_path <- function(str1, str2) {
#' @param str1: First string
#' @param str2: Second string
#' @return: List with distance, operations, and dp table
if (!is.character(str1) || !is.character(str2)) {
stop("Both inputs must be character strings.")
}
if (length(str1) != 1 || length(str2) != 1) {
stop("Each input must be a single string.")
}

m <- nchar(str1)
n <- nchar(str2)
dp <- matrix(0L, nrow = m + 1, ncol = n + 1)
dp[, 1] <- seq(0L, m)
dp[1, ] <- seq(0L, n)

for (i in seq_len(m) + 1L) {
for (j in seq_len(n) + 1L) {
cost <- if (substr(str1, i - 1, i - 1) == substr(str2, j - 1, j - 1)) 0L else 1L
dp[i, j] <- min(
Comment on lines +54 to +63
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same empty-string issue as above: for (i in 2:(m + 1)) / for (j in 2:(n + 1)) can iterate invalid indices when m == 0 or n == 0, leading to out-of-bounds dp access. Please make the iteration empty-safe or handle m == 0 / n == 0 up front (and still produce a valid operations list).

Copilot uses AI. Check for mistakes.
dp[i - 1, j] + 1L,
dp[i, j - 1] + 1L,
dp[i - 1, j - 1] + cost
)
}
}

i <- m + 1
j <- n + 1
ops <- character()

while (i > 1 || j > 1) {
if (i > 1 && j > 1 && dp[i, j] == dp[i - 1, j - 1] +
(substr(str1, i - 1, i - 1) != substr(str2, j - 1, j - 1))) {
if (substr(str1, i - 1, i - 1) == substr(str2, j - 1, j - 1)) {
ops <- c("match", ops)
} else {
ops <- c(sprintf("substitute '%s' -> '%s'", substr(str1, i - 1, i - 1), substr(str2, j - 1, j - 1)), ops)
}
i <- i - 1
j <- j - 1
} else if (i > 1 && dp[i, j] == dp[i - 1, j] + 1L) {
ops <- c(sprintf("delete '%s'", substr(str1, i - 1, i - 1)), ops)
i <- i - 1
} else {
ops <- c(sprintf("insert '%s'", substr(str2, j - 1, j - 1)), ops)
j <- j - 1
}
}

return(list(
distance = dp[m + 1, n + 1],
operations = ops,
dp_table = dp
))
}

# Example usage:
# print(edit_distance("kitten", "sitting"))
# result <- edit_distance_with_path("kitten", "sitting")
# print(result$distance)
# print(result$operations)
Loading