codewarrior
codewarrior

Reputation: 1034

Given two strings, find if they are one edit away from each other

I came across this question recently:

Given two strings, return true if they are one edit away from each other,else return false.
An edit is insert/replace/delete a character. 
Ex. {"abc","ab"}->true, {"abc","adc"}->true, {"abc","cab"}->false

One way to solve this problem would be to find the edit distance between the two strings using dynamic programming, and check if it is 1. That would take O(N2) time. Is there a way to do this in linear time, as we've to only check if they are 1 edit away?

The code I wrote below works for most cases, but fails for cases like {"m",""}

public boolean isOneEditDistance(String s1,String s2){
    if(s1==null || s2==null)
        return true;
    if(s1.equals(s2))
        return false;
    if (Math.abs(s1.length() - s2.length()) > 1)
        return false;
    int i = -1;
    int j = -1;
    while (true)
    {
        i++;
        j++;
        if (i == s1.length())
            return true;
        if (s1.charAt(i) == s2.charAt(j))
            continue;
        if (i != j)
            return false;
        if (s1.length() < s2.length())
            i--;
        else
            j--;
    }
}

Upvotes: 12

Views: 17181

Answers (20)

Raju Vadnala
Raju Vadnala

Reputation: 70

Check my c# solution O(n) time complexity

 public static bool IsOneEdit(string val1, string val2)
        {
            if (val1 == null || val2 == null || val1.Equals(val2) || val1.Except(val2).Count() > 1 || val2.Except(val1).Count() > 1)
                return false;

            var len1 = val1.Length;
            var len2 = val2.Length;

            if (Math.Abs(len1 - len2) > 1)
                return false;

            //replace operation
            if (len1 == len2)
            {
                for (int i = 0; i < len1; i++)
                {
                    if(val2[i] != val1[i])
                    {
                        if(val1 == val2.Remove(i, 1).Insert(i, val1[i].ToString()))
                        {
                            return true;
                        }
                    }
                }
            }
            else
            {
                var bigOne = len1 > len2 ? val1 : val2;
                var smallOne = len1 < len2 ? val1 : val2;

                for (int i = 0; i < bigOne.Length; i++)
                {
                    if(bigOne.Remove(i,1) == smallOne)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

Upvotes: 0

Gustavo Daniel
Gustavo Daniel

Reputation: 2478

This is my very short implementation in JavaScript:

function oneAway(str1, str2) {
    // Check for identical strings
    if (str1 === str2) {
        return true;
    }
    // Check length diff is not more that one char
    if (Math.abs(str2.length - str1.length) > 1) {
        return false;
    }
    // If first characters are equal, check following substring
    if (str1[0] === str2[0]) {
        return oneAway(str1.slice(1), str2.slice(1));
    } else {
        // Check first character edition
        if (str1.length === str2.length && str1 === str1[0] + str2.slice(1)) {
            return true;
        }
        // Check first character deletion
        else if (str1.length > str2.length && str1 === str1[0] + str2) {
            return true;
        }
        // Check first character insertion
        else if (str1.length < str2.length && str1 === str2.slice(1)) {
            return true;
        }
    }
    return false;
}

This is the test result, against "pale":

pale true
pae true
pal true
ple true
ale true
xale true
pxle true
paxe true
palx true
xpale true
palex true
xxle false
pxxe false
paxx false
xalx false
xaxe false
palexx false
le false

Upvotes: 0

Vinod Ghai
Vinod Ghai

Reputation: 1

A generic implementation in Java with the desired number of edits running in O(N). It determines that both strings should be less than or equal edits away.

public boolean isEditsAway(String s1, String s2, int edits) {
        int abs = Math.abs(s1.length() - s2.length());
        if (abs > edits)
            return false;

        int edit = 0;
        for (int i = 0; i < s1.length() && i < s2.length(); i++) {
            if (s1.charAt(i) != s2.charAt(i))
                edit++;
            if (edit == edits + 1)
                return false;
        }

        return abs + edit <= edits;
    }

Upvotes: 0

Umesh Chauhan
Umesh Chauhan

Reputation: 1470

A Java version

public class Test {

public static void main(String[] args) {

    System.out.println(fun("car", "cart"));
    System.out.println(fun("cat", "bat"));
    System.out.println(fun("balck", "back"));
}

/*
 * Modifications : add, delete, update
 * 
 * i/p Example Add: a = car b = cart
 * 
 * delete : a = balck b = back
 * 
 * update: a = cat b = bat
 * 
 */
public static boolean fun(String a, String b) {
    Boolean isTestPositive = Boolean.FALSE;
    if (a == null || b == null) {
        return isTestPositive;
    }
    if (a.equals(b)) {
        // No Modifications required
        return isTestPositive;
    }
    // Start comparing
    char[] arrayForA = a.toCharArray();
    char[] arrayForB = b.toCharArray();

    return testCase(arrayForA, arrayForB);

}

public static boolean testCase(char[] a, char[] b) {
    int correctionCount = 0;
    int aLen = a.length;
    int bLen = b.length;
    if (Math.abs(aLen - bLen) > 1) {
        return Boolean.FALSE;
    }
    int minLen = Math.min(aLen, bLen);
    for (int i = 0; i < minLen; i++) {
        if (a[i] != b[i]) {
            ++correctionCount;
            if (correctionCount > 1) {
                return Boolean.FALSE;
            }
            // Test Delete case
            if (b.length > i + 1 && a[i] == b[i + 1]) {
                return testDeleteCase(Arrays.copyOfRange(a, i, a.length - 1),
                        Arrays.copyOfRange(b, i + 1, b.length - 1));
            } else if (a.length > i + 1 && b[i] == a[i + 1]) {
                return testDeleteCase(Arrays.copyOfRange(a, i + 1, a.length - 1),
                        Arrays.copyOfRange(b, i, b.length - 1));
            }

        }

    }
    return Boolean.TRUE;
}

public static boolean testDeleteCase(char[] a, char[] b) {
    int aLen = a.length;
    int bLen = b.length;
    if (Math.abs(aLen - bLen) >= 1) {
        return Boolean.FALSE;
    }
    int minLen = Math.min(aLen, bLen);
    for (int i = 0; i < minLen; i++) {
        if (a[i] != b[i]) {

            return Boolean.FALSE;
        }
    }
    return Boolean.TRUE;
}}

Upvotes: 1

Akbar Qofrani
Akbar Qofrani

Reputation: 19

There is a simple way to do it in C#.

    static bool OneEdit(string s1, string s2)
    {
        var diff = s1.Length > s2.Length
                ? s1.Except(s2).ToList()
                : s2.Except(s1).ToList();

        return diff.Count() == 1;
    }

Upvotes: 0

Xaris
Xaris

Reputation: 23

public static Boolean isOneAway(String s1, String s2) {
    if (s1.equals(s2))
        return true;
    char[] s1Array = s1.toCharArray();
    char[] s2Array = s2.toCharArray();
    if (s1Array.length == s2Array.length) {
        int differingElementsCount = 0;
        for (Character c1 : s1Array) {
            boolean exists = false;
            for (Character c2 : s2Array) {
                if (!c1.equals(c2)) {
                    continue;
                } else {
                    if (s1.lastIndexOf(c1) == s2.indexOf(c2)) {
                        exists = true;
                        break;
                    } else {
                        differingElementsCount++;
                        continue;
                    }
                }
            }
            if (exists == false) {
                differingElementsCount++;
            }
        }
        if (differingElementsCount > 1) {
            return false;
        }
    } else if (s1Array.length > s2Array.length) {
        if ((s1Array.length - s2Array.length) > 1) {
            return false;
        } else {
            return true;
        }
    } else {
        if (s2Array.length - s1Array.length > 1) {
            return false;
        } else {
            return true;
        }
    }
    return true;
}

Upvotes: 0

lio
lio

Reputation: 459

it will take o(n) run time

  public static  boolean isOneEditDistance(String str1 ,String str2 ){
    if(Math.abs(str1.length() - str2.length()) > 1){
        return false;
    }
    String s1 = str1.length() < str2.length() ? str1 : str2; // smallest
    String s2 = str1.length() < str2.length() ? str2 : str1; // biggest
    int index1 = 0, index2 = 0;
    boolean isMoreThanOneEdit = false;

    while (index1 < s1.length() && index2 < s2.length()){
        if(s1.charAt(index1) != s2.charAt(index2)){
            if(isMoreThanOneEdit)
                return false;
            isMoreThanOneEdit = true;
            if(s1.length() == s2.length()) // if replace
                index1++;
        }else {
            index1++; // if match
        }
        index2++; // always longer string index
    }
    return true;
}

Upvotes: 0

shubhranshu
shubhranshu

Reputation: 419

def isEditDistanceOne(s1, s2):
isoneEdit = False



short = s1 if len(s1) < len(s2) else s2
longg = s2 if len(s1) < len(s2) else s1
if len(longg) - len(short) > 1:
    return False

ind1 = 0
ind2 = 0

while ind1 < len(short) and ind2 < len(longg):
    if short[ind1] != longg[ind2]:
        if isoneEdit:
            return False
        isoneEdit = True

        if len(short) == len(longg):
            ind1 += 1 
    else:
        ind1 += 1 

    ind2 += 1

return True   

Upvotes: 0

theTypan
theTypan

Reputation: 5877

Here is my python implementation. I am using two arrays for each string

 import unittest

# Assume characters stored in 8 bits. 256 possible characters
MAX_CHARS = 256

def is_edit(string1, string2):
    """Given two strings, return if they are one or zero edits away.

    Insert, remove or replace a character."""
    # If the absolute difference in length is more than one
    # return false
    string1_len = len(string1)
    string2_len = len(string2)

    if string1_len != string2_len and abs(string1_len - string2_len) > 1:
        return False

    # Initialize two arrays, each for each string
    count1 = [0] * MAX_CHARS
    count2 = [0] * MAX_CHARS

    # For each character in input strings get unicode representation
    # and  increment counter in corresponding array
    for i in string1:
        count1[ord(i)] += 1

    for i in string2:
        count2[ord(i)] += 1

    edit = 0

    # compare the arrays
    # If number of edits required is more than 2 return false
    # This will handle replacement when given words of the same length
    for i in range(MAX_CHARS):
        if count1[i] != count2[i]:
            edit += 1

        if edit > 2:
            return False

    # Return false if string1 is the same as string2 (No edit required) e.g pale, pale
    if not edit:
        return False

    return True


class EditCheckTestCase(unittest.TestCase):
    """Tests for is_edit method."""

    def test_insert_missing_character(self):
        """Test insertion of character is valid."""
        self.assertEqual(is_edit('pale', 'ple'), True)

    def test_insert_more_than_one_character(self):
        """Test insertion of more than one character is invalid"""
        self.assertEqual(is_edit('pale', 'pe'), False)

    def test_append_one_character(self):
        """Test the append of one character is valid."""
        self.assertEqual(is_edit('pales', 'pale'), True)

    def test_append_more_than_one_character(self):
        """Test append more than one character is invalid."""
        self.assertEqual(is_edit('paless', 'pale'), False)

    def test_replace_one_character(self):
        """Test replacement of one character is valid"""
        self.assertEqual(is_edit('pale', 'bale'), True)

    def test_no_edit_character(self):
        """Test no edit required is valid."""
        self.assertEqual(is_edit('pale', 'bake'), False)
        self.assertEqual(is_edit('pale', 'pale'), False)


if __name__ == "__main__":
    unittest.main()

Upvotes: 0

VAT
VAT

Reputation: 210

Here's my solution in O(n) time in C#. Few scenarios:

  • If difference of string lengths is > 1, then exit
  • First traverse through the first string and increment english lower-case array for each corresponding character
  • Then traverse through second string and decrement that corresponding character.
  • Finally, check if the edit count is more than 1... if yes, break the for loop...
  • we will consider lower-case English alphabets only

    public static bool IsStringOneAway(string s1, string s2)
    {
        //we will consider lower-case English alphabets only
        int[] engArray = new int[26];
        var tmp = 0;
        var editCount = 0;
    
        //if string lengths differ by more than 1, then return
        if (Math.Abs(s1.Length - s2.Length) > 1)
        {
            Console.WriteLine("Length difference is more than 1, One Away edit doesn't exist");
            return false;
        }
    
        //append the english alphabet array from String 1
        for (int i = 0; i < s1.Length; i++)
        {
            tmp = (int)s1[i];
            engArray[tmp - 97]++;
        }
    
        //deduct the english alphabet arry from String 2
        for (int i = 0; i < s2.Length; i++)
        {
            tmp = (int)s2[i];
            engArray[tmp - 97]--;
        }
    
        //now check the count of edits; if count > 1, break
        for (int i = 0; i < engArray.Length; i++)
        {
            if (engArray[i] != 0)
            {
                editCount++;
                if (editCount > 2)
                {
                    Console.WriteLine("One Away edit doesn't exist");
                    return false;
                }
            }
        }
    
        Console.WriteLine("One Away edit exist");
        return true;
    
    }
    

Upvotes: 0

abhinav1602
abhinav1602

Reputation: 1290

I solved the problem in C++ and it is the correct version of what Khalid Habib is trying to say in this answer. Here is the solution below(I have added this solution on Github too, you can follow the link here).

#include<bits/stdc++.h>
using namespace std;

// checks if there is only one one different in both arrays
bool oneReplaceAway(string s1, string s2){
    bool firstChangeDone = false;
    int l1 = s1.length();
    // l1 == l2 already
    // int l2 = s2.length();
    int l2 = l1;
    int i=0, j=0;

    while(i<l1 && j<l2){
        if(s1[i] != s2[j]){
            // if first change is already checked then return false as there are more than one dissimilarities
            if(firstChangeDone){
                //cout<<"IGI@"<< i<<" "<<j<<"\n";
                return false;   
            }
            firstChangeDone = true;
        }
        i++;
        j++;
    }
    return true;
}


// checks if one word can be added to one string to create the other one
bool oneInsertAway(string s1, string s2){
    bool firstChangeDone = false;
    int l1 = s1.length();
    int l2 = s2.length();

    int i=0, j=0;

    while(i<l1 && j<l2){
        if(s1[i]!=s2[j]){
            // if the chars at i and j are not equal and i!=j, that means one change is already checked, i.e., it is the second change
            if(i!=j)
                return false;
            j++;
        }
        else{
            i++;
            j++;
        }
    }
    return true;
}

// checks of two strings are One Edit Away
bool oneEditAway(string s1, string s2) {
    int l1 = s1.length();
    int l2 = s2.length();

    // cout<<s1<<" - "<<l1<<"\n"<<s2<<" - "<<l2<<"\n"<<(l1==l2)<<"\n";
    if(l1 == l2){
        return oneReplaceAway(s1, s2);
    }
    else if(l1+1 == l2){
        return oneInsertAway(s1, s2);
    }
    else if(l2+1 == l1){
        return oneInsertAway(s2, s1);
    }
    else
        return false;
}


int main(){
    int t;
    cin>>t;

    while(t--){
        string s1,s2;
        cin>>s1>>s2;

        // cout<<oneEditAway(s1, s2)<<"\n";
        string ans = oneEditAway(s1, s2)==1?"One Edit Awway":"Not one Edit Away";
        cout<<ans<<"\n";
    }
    return 0;
}

Upvotes: 1

Ishitha
Ishitha

Reputation: 13

C# version

static bool IsOneEditAway(string word1, string word2)
        {
            if (string.IsNullOrEmpty(word1) && string.IsNullOrEmpty(word2))
                return false;

            ActionType actionToPerform;
            if (word1.Length == word2.Length)
            {
                actionToPerform = ActionType.Replace;
            }
            else if (word1.Length < word2.Length)
            {
                actionToPerform = ActionType.Delete;
            }
            else
                actionToPerform = ActionType.Insert;

            int i = 0, j = 0;
            var numOfEdits = 0;
            var chrWord1 = word1.ToCharArray();
            var chrWord2 = word2.ToCharArray();
            while (numOfEdits <= 1)
            {
                if (i >= chrWord1.Length && j >= chrWord2.Length)
                    break;
                if (i >= chrWord1.Length && j < chrWord2.Length)
                {
                    j++;
                    numOfEdits++;
                    continue;
                }
                if (j >= chrWord2.Length && i < chrWord1.Length)
                {
                    i++;
                    numOfEdits++;
                    continue;
                }
                if (chrWord1[i] == chrWord2[j])
                {
                    i++; j++;
                }
                else
                {
                    switch(actionToPerform)
                    {
                        case ActionType.Insert:
                            i++;
                            break;
                        case ActionType.Delete:
                            j++;
                            break;
                        case ActionType.Replace:
                            i++;j++;
                            break;
                    }
                    numOfEdits++;
                }
            }

            return numOfEdits == 1 ? true : false;
        }
public enum ActionType
    {
        Insert=0,
        Delete=1,
        Replace=2
    }

Upvotes: 0

deniz g&#252;l
deniz g&#252;l

Reputation: 19

Java version may be like below:

public static boolean oneEdit(String w1, String w2) 
{
    char[] word1= w1.trim().toCharArray();
    char[] word2 = w2.trim().toCharArray();

    int length1=word1.length;
    int length2=word2.length;

    if(Math.abs(length2-length1) > 1) return false;

    Arrays.sort(word1);
    Arrays.sort(word2);

    int length = word1.length >= word2.length? word2.length:word1.length; //take the minimum length

    int falseCounter=0;
    for(int i=0; i < length; i++ ) {
        if(word1[i] != word2[i] && ++falseCounter > 1){
            return false;
        }
    }
    return true;
}

Upvotes: 1

Gabriel Goncalves
Gabriel Goncalves

Reputation: 5160

Answer in Swift explained step by step:

func isOneEdit(str1: String, str2: String) -> Bool {

// check if they are the same
if str1 == str2 {
    return true
}

let difference = abs(str1.count - str2.count)

// check if the difference between then is bigger than 1
if difference > 1 {
    return false
}

// lets iterate over the words

var i = 0
var j = 0
var changes = 0

while i < str1.count && j < str2.count {
    let char1 = str1[str1.index(str1.startIndex, offsetBy: i)]
    let char2 = str2[str1.index(str2.startIndex, offsetBy: j)]

    // if the difference is 1 we need to move just one index (the one from the bigger word)
    // this is just necessary when the char1 and char2 are different
    if difference == 1 && char1 != char2 {
        if str1.count > str2.count {
            i += 1
        } else {
            j += 1
        }
        changes += 1
    } else {
        // if chars are equal (in this step we don't care about the difference)
        // we move both indexes.
        i += 1
        j += 1

        if char1 != char2 {
            changes += 1
        }
    }
}

return changes <= 1
}

Upvotes: 0

A H K
A H K

Reputation: 1750

Here is the Ruby implementation:

def one_away(s1, s2)
  return false if (s1.size - s2.size).abs > 1

  missed_count = 0
  counter = 0
  s1.each_char do |c|
    if !s2[counter].nil? && (s2[counter] != c)
      missed_count += 1
    end
    counter += 1
    return false if missed_count > 1
  end
  true
end

p one_away('pale', 'bake') #=> false

Upvotes: 0

Khalid Habib
Khalid Habib

Reputation: 1156

    boolean oneEditAway(String first, String second) {
    if (first.length() == second.length()) {
        //call a function which replce the character from the string
    } else if (first.length() + 1 == second.length()) {
        //call a function which remove the character from string
    } else if (first.length() - 1 == second.length()) {
        //call a function which insert the character in the string
    }
    return false;
}

Upvotes: 0

Madhu Cheepati
Madhu Cheepati

Reputation: 869

static boolean isEditDistanceOne(String s1, String s2)
    {
        // Find lengths of given strings
        int m = s1.length(), n = s2.length();

        // If difference between lengths is more than
        // 1, then strings can't be at one distance
        if (Math.abs(m - n) > 1)
            return false;

        int count = 0; // Count of edits

        int i = 0, j = 0;
        while (i < m && j < n)
        {
            // If current characters don't match
            if (s1.charAt(i) != s2.charAt(j))
            {
                if (count == 1)return false;

                // If length of one string is
                // more, then only possible edit
                // is to remove a character
                if (m > n) i++;
                else if (m< n) j++;
                else //Iflengths of both strings is same
                {
                    i++;
                    j++;
                }

                // Increment count of edits 
                count++;
            }

            else // If current characters match
            {
                i++;
                j++;
            }
        }

        // If last character is extra in any string
        if (i < m || j < n)
            count++;

        return count == 1;
    }

Upvotes: 1

YoungHobbit
YoungHobbit

Reputation: 13402

Here is the solution for finding the one edit in O(n). Below are the scenario, I have covered in the implementation.

  1. The length difference between two input strings should not be more than 1.
  2. When the length of the strings is same, the number of different chars should not be more than 1.
  3. If the length difference is 1, then either one char can be inserted in the short string or deleted from the longer string. Considering that, the number of different char should not be more than 1.
private static boolean isOneEdit(String first, String second) {
    // if the input string are same
    if (first.equals(second))
        return false;

    int len1 = first.length();
    int len2 = second.length();
    // If the length difference of the stings is more than 1, return false.
    if ((len1 - len2) > 1 || (len2 - len1) > 1  ) {
        return false;
    }
    int i = 0, j = 0;
    int diff = 0;
    while (i<len1 && j<len2) {
        char f = first.charAt(i);
        char s = second.charAt(j);
        if (f != s) {
            diff++;
            if (len1 > len2)
                i++;
            if (len2 > len1)
                j++;
            if (len1 == len2)
                i++; j++;
        }
        else{
            i++; j++;
        }
        if (diff > 1) {
            return false;
        }
    }
    // If the length of the string is not same. ex. "abc" and "abde" are not one edit distance.
    if (diff == 1 && len1 != len2 && (i != len1 || j != len2)) {
        return false;
    }
    return true;
}

Upvotes: 13

Guy Kahlon
Guy Kahlon

Reputation: 4520

Here you can find a solution rate in Swift.

func isOneEdit(str1: String, str2: String) -> Bool {

  //The length difference between two input strings should not be more than 1.
  let diff = abs(str1.characters.count - str2.characters.count)
  if diff > 1 {
    return false
  }

  //When the length of the strings is same, the number of different chars should not be more than 1.
  if diff == 0 {
    var numberOfEdits = 0
    for (char1, char2) in zip(str1.characters, str2.characters) {
      if char1 != char2 {
        numberOfEdits++
      }
    }
    return numberOfEdits < 2
  }

  //If the length difference is 1.
  //then either one char can be inserted in the short string or deleted from the longer string. 
  //Considering that, the number of different char should not be more than 1.

  return str1.rangeOfString(str2) != nil || str2.rangeOfString(str1) != nil



  //return str1.isSubstring(str2) || str2.isSubstring(str1)

}

This function takes O(n) time and constant space.

Upvotes: 0

Ami Tavory
Ami Tavory

Reputation: 76297

In the dynamic programming method, frequently a matrix is used. The rows correspond to one string, and the columns to the other. The point is to find the cheapest path from the top-left cell to the bottom right. At any point, a horizontal or vertical transition stands for an insertion.

Your problem is the same, but the paths are restricted. With k insertions/deletions, the path is restricted to be in the k-diagonal. Other than that, the classical DP algorithm should work. The complexity is linear.

Upvotes: 4

Related Questions