Skip to content

Commit 51740b9

Browse files
Merge pull request #46 from thidaskaveesha/feat/didYouMean
Add Did You Mean Suggestion for Mistyped Commands #40
2 parents d224d3e + 9dcf018 commit 51740b9

File tree

3 files changed

+119
-60
lines changed

3 files changed

+119
-60
lines changed

src/main/java/com/mycmd/App.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package com.mycmd;
22

33
import com.mycmd.commands.*;
4+
import com.mycmd.utils.StringUtils;
5+
46
import java.util.*;
7+
import java.util.Scanner;
58

69
public class App {
710
public static void main(String[] args) {
@@ -35,14 +38,18 @@ public static void main(String[] args) {
3538
System.out.println("Error: " + e.getMessage());
3639
}
3740
} else {
41+
// Single, clear not-recognized message + optional suggestion
3842
System.out.println("'" + cmd + "' is not recognized as an internal or external command.");
39-
// Suggest closest command
40-
List<String> validCommands = new ArrayList<>(commands.keySet());
41-
String suggestion = StringUtils.findClosest(cmd, validCommands);
42-
if (suggestion != null && !suggestion.equals(cmd)) {
43-
System.out.println("'" + cmd + "' is not recognized as an internal or external command. Did you mean '" + suggestion + "'?");
44-
} else {
45-
System.out.println("'" + cmd + "' is not recognized as an internal or external command.");
43+
44+
// compute suggestion safely
45+
try {
46+
List<String> validCommands = new ArrayList<>(commands.keySet());
47+
String suggestion = StringUtils.findClosest(cmd, validCommands);
48+
if (suggestion != null && !suggestion.equalsIgnoreCase(cmd)) {
49+
System.out.println("Did you mean '" + suggestion + "'?");
50+
}
51+
} catch (Exception ex) {
52+
// don't let suggestion errors break the shell
4653
}
4754
}
4855
}
Lines changed: 77 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,88 @@
1-
package com.mycmd;
1+
package com.mycmd.utils;
22

3-
import java.util.List;
3+
import java.util.Collection;
4+
import java.util.Objects;
45

5-
public class StringUtils {
6-
// Compute Levenshtein distance between two strings
7-
public static int levenshtein(String a, String b) {
8-
int[] costs = new int[b.length() + 1];
9-
for (int j = 0; j < costs.length; j++) {
10-
costs[j] = j;
6+
/**
7+
* Small utility for string-based helper methods.
8+
* Provides a findClosest(...) implementation using Levenshtein distance.
9+
*/
10+
public final class StringUtils {
11+
12+
private StringUtils() {}
13+
14+
/**
15+
* Find the closest string in candidates to the input.
16+
* Returns null when no candidate is close enough.
17+
*
18+
* @param input the input string
19+
* @param candidates candidate strings
20+
* @return the closest candidate or null
21+
*/
22+
public static String findClosest(String input, Collection<String> candidates) {
23+
if (input == null || input.isEmpty() || candidates == null || candidates.isEmpty()) {
24+
return null;
1125
}
12-
for (int i = 1; i <= a.length(); i++) {
13-
costs[0] = i;
14-
int nw = i - 1;
15-
for (int j = 1; j <= b.length(); j++) {
16-
int cj = Math.min(1 + Math.min(costs[j], costs[j - 1]), a.charAt(i - 1) == b.charAt(j - 1) ? nw : nw + 1);
17-
nw = costs[j];
18-
costs[j] = cj;
26+
27+
String best = null;
28+
int bestDistance = Integer.MAX_VALUE;
29+
30+
for (String cand : candidates) {
31+
if (cand == null || cand.isEmpty()) continue;
32+
if (cand.equalsIgnoreCase(input)) {
33+
// exact match ignoring case - return immediately
34+
return cand;
1935
}
36+
int dist = levenshteinDistance(input.toLowerCase(), cand.toLowerCase());
37+
if (dist < bestDistance) {
38+
bestDistance = dist;
39+
best = cand;
40+
}
41+
}
42+
43+
// Choose a threshold: allow suggestion only when distance is reasonably small.
44+
// Here we allow suggestions when distance <= max(1, input.length()/3)
45+
int threshold = Math.max(1, input.length() / 3);
46+
if (bestDistance <= threshold) {
47+
return best;
2048
}
21-
return costs[b.length()];
49+
return null;
2250
}
2351

24-
// Find the closest string from a list
25-
public static String findClosest(String input, List<String> candidates) {
26-
String closest = null;
27-
int minDist = Integer.MAX_VALUE;
28-
for (String candidate : candidates) {
29-
int dist = levenshtein(input, candidate);
30-
if (dist < minDist) {
31-
minDist = dist;
32-
closest = candidate;
52+
// Classic iterative Levenshtein algorithm (memory O(min(m,n)))
53+
private static int levenshteinDistance(String a, String b) {
54+
if (Objects.equals(a, b)) return 0;
55+
if (a.length() == 0) return b.length();
56+
if (b.length() == 0) return a.length();
57+
58+
// ensure a is the shorter
59+
if (a.length() > b.length()) {
60+
String tmp = a;
61+
a = b;
62+
b = tmp;
63+
}
64+
65+
int[] prev = new int[a.length() + 1];
66+
int[] curr = new int[a.length() + 1];
67+
68+
for (int i = 0; i <= a.length(); i++) prev[i] = i;
69+
70+
for (int j = 1; j <= b.length(); j++) {
71+
curr[0] = j;
72+
char bj = b.charAt(j - 1);
73+
for (int i = 1; i <= a.length(); i++) {
74+
int cost = (a.charAt(i - 1) == bj) ? 0 : 1;
75+
curr[i] = min3(curr[i - 1] + 1, prev[i] + 1, prev[i - 1] + cost);
3376
}
77+
// swap prev and curr
78+
int[] tmp = prev;
79+
prev = curr;
80+
curr = tmp;
3481
}
35-
return closest;
82+
return prev[a.length()];
83+
}
84+
85+
private static int min3(int x, int y, int z) {
86+
return Math.min(x, Math.min(y, z));
3687
}
3788
}
Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
1-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\App.java
2-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\Command.java
3-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\CdCommand.java
4-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\ClsCommand.java
5-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\ColorCommand.java
6-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\CopyCommand.java
7-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\DateCommand.java
8-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\DelCommand.java
9-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\DirCommand.java
10-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\EchoCommand.java
11-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\ExitCommand.java
12-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\HelpCommand.java
13-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\HistoryCommand.java
14-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\HostnameCommand.java
15-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\LsCommand.java
16-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\MkdirCommand.java
17-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\MoveCommand.java
18-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\PingCommand.java
19-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\RmdirCommand.java
20-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\TimeCommand.java
21-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\TitleCommand.java
22-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\TouchCommand.java
23-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\TreeCommand.java
24-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\TypeCommand.java
25-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\VersionCommand.java
26-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\commands\WhoamiCommand.java
27-
c:\Users\kgfga\MyCMD\src\main\java\com\mycmd\ShellContext.java
1+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\App.java
2+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\Command.java
3+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\CdCommand.java
4+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\ClsCommand.java
5+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\ColorCommand.java
6+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\CopyCommand.java
7+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\DateCommand.java
8+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\DelCommand.java
9+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\DirCommand.java
10+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\EchoCommand.java
11+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\ExitCommand.java
12+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\HelpCommand.java
13+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\HistoryCommand.java
14+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\HostnameCommand.java
15+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\LsCommand.java
16+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\MkdirCommand.java
17+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\MoveCommand.java
18+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\PingCommand.java
19+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\RmdirCommand.java
20+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\TimeCommand.java
21+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\TitleCommand.java
22+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\TouchCommand.java
23+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\TreeCommand.java
24+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\TypeCommand.java
25+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\VersionCommand.java
26+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\commands\WhoamiCommand.java
27+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\ShellContext.java
28+
C:\Users\thidass\Desktop\gittest\MyCMD\src\main\java\com\mycmd\StringUtils.java

0 commit comments

Comments
 (0)