This is my class Debugger. Can anyone try and run it and see whens wrong? Ive spent hours on it already. :(
public class Debugger {
private String codeToDebug = "";
public Debugger(String code) {
codeToDebug = code;
}
/**
* This method itterates over a css file and adds all the properties to an arraylist
*/
public void searchDuplicates() {
boolean isInside = false;
ArrayList<String> methodStorage = new ArrayList();
int stored = 0;
String[] codeArray = codeToDebug.split("");
try {
int i = 0;
while(i<codeArray.length) {
if(codeArray[i].equals("}")) {
isInside = false;
}
if(isInside && !codeArray[i].equals(" ")) {
boolean methodFound = false;
String method = "";
int c = i;
while(!methodFound) {
method += codeArray[c];
if(codeArray[c+1].equals(":")) {
methodFound = true;
} else {
c++;
}
}
methodStorage.add(stored, method);
System.out.println(methodStorage.get(stored));
stored++;
boolean stillInside = true;
int skip = i;
while(stillInside) {
if(codeArray[skip].equals(";")) {
stillInside = false;
} else {
skip++;
}
}
i = skip;
}
if(codeArray[i].equals("{")) {
isInside = true;
}
i++;
}
} catch(ArrayIndexOutOfBoundsException ar) {
System.out.println("------- array out of bounds exception -------");
}
}
/**
* Takes in String and outputs the number of characters it contains
* #param input
* #return Number of characters
*/
public static int countString(String input) {
String[] words = input.split("");
int counter = -1;
for(int i = 0; i<words.length; i++){
counter++;
}
return counter;
}
public static void main(String[] args) {
Debugger h = new Debugger("body {margin:;\n}");
h.searchDuplicates();
}
}
Any place where an element of an array is being obtained without a bounds check after the index is manipulated is an candidate for an ArrayIndexOutOfBoundsException.
In the above code, there are at least two instances where the index is being manipulated without being subject to a bounds check.
The while loop checking the !methodFound condition
The while loop checking the stillInside condition
In those two cases, the index is being manipulated by incrementing or adding a value to the index, but there are no bound checks before an element is being obtained from the String[], therefore there is no guarantee that the index being specified is not outside the bounds of the array.
I think this block of codes can create your problem
int c = i;
while(!methodFound) {
method += codeArray[c];
if(codeArray[c+1].equals(":")) {
methodFound = true;
} else {
c++;
}
}
int skip = i;
while(stillInside) {
if(codeArray[skip].equals(";")) {
stillInside = false;
} else {
skip++;
}
}
i = skip;
The reason is that if the condition is true, and i = codeArray.length - 1. The c + 1 will create the error of ArrayIndexOutOfBound
Try evaluating if your index exists in the array...
adding:
while (!methodFound && c < codeArray.length) {
while (stillInside && skip < codeArray.length) {
if (i < codeArray.length && codeArray[i].equals("{")) {
so, your code looks like:
public class Debugger {
private String codeToDebug = "";
public Debugger(String code) {
codeToDebug = code;
}
/**
* This method itterates over a css file and adds all the properties to an
* arraylist
*/
public void searchDuplicates() {
boolean isInside = false;
List<String> methodStorage = new ArrayList<String>();
int stored = 0;
String[] codeArray = codeToDebug.split("");
try {
int i = 0;
while (i < codeArray.length) {
if (codeArray[i].equals("}")) {
isInside = false;
}
if (isInside && !codeArray[i].equals(" ")) {
boolean methodFound = false;
String method = "";
int c = i;
while (!methodFound && c < codeArray.length) {
method += codeArray[c];
if (codeArray[c].equals(":")) {
methodFound = true;
} else {
c++;
}
}
methodStorage.add(stored, method);
System.out.println(methodStorage.get(stored));
stored++;
boolean stillInside = true;
int skip = i;
while (stillInside && skip < codeArray.length) {
if (codeArray[skip].equals(";")) {
stillInside = false;
} else {
skip++;
}
}
i = skip;
}
if (i < codeArray.length && codeArray[i].equals("{")) {
isInside = true;
}
i++;
}
} catch (ArrayIndexOutOfBoundsException ar) {
System.out.println("------- array out of bounds exception -------");
ar.printStackTrace();
}
}
/**
* Takes in String and outputs the number of characters it contains
*
* #param input
* #return Number of characters
*/
public static int countString(String input) {
String[] words = input.split("");
int counter = -1;
for (int i = 0; i < words.length; i++) {
counter++;
}
return counter;
}
public static void main(String[] args) {
Debugger h = new Debugger("body {margin:prueba;\n}");
h.searchDuplicates();
}
}
Also, declaring implementation types is a bad practice, because of that in the above code i Change the ArrayList variable = new ArrayList() to List variable = new ArrayList()
I couldn't resist to implement this task of writing a CSS parser in a completely different way. I have split the task of parsing into many small ones.
The smallest is called skipWhitespace, since you will need it everywhere when parsing text files.
The next one is parseProperty, which reads one property of the form name:value;.
Based on that, parseSelector reads a complete CSS selector, starting with the selector name, an opening brace, possibly many properties, and finishing with the closing brace.
Still based on that, parseFile reads a complete file, consisting of possibly many selectors.
Note how carefully I checked whether the index is small enough. I did that before every access to the chars array.
I used LinkedHashMaps to save the properties and the selectors, because these kinds of maps remember in which order the things have been inserted. Normal HashMaps don't do that.
The task of parsing a text file is generally quite complex, and this program only attempts to handle the basics of CSS. If you need a full CSS parser, you should definitely look for a ready-made one. This one cannot handle #media or similar things where you have nested blocks. But it shouldn't bee too difficult to add it to the existing code.
This parser will not handle CSS comments very well. It only expects them at a few places. If comments appear in other places, the parser will not treat them as comments.
import java.util.LinkedHashMap;
import java.util.Map;
public class CssParser {
private final char[] chars;
private int index;
public Debugger(String code) {
this.chars = code.toCharArray();
this.index = 0;
}
private void skipWhitespace() {
/*
* Here you should also skip comments in the CSS file, which either look
* like this comment or start with a // and go until the end of line.
*/
while (index < chars.length && Character.isWhitespace(chars[index]))
index++;
}
private void parseProperty(String selector, Map<String, String> properties) {
skipWhitespace();
// get the CSS property name
StringBuilder sb = new StringBuilder();
while (index < chars.length && chars[index] != ':')
sb.append(chars[index++]);
String propertyName = sb.toString().trim();
if (index == chars.length)
throw new IllegalArgumentException("Expected a colon at index " + index + ".");
// skip the colon
index++;
// get the CSS property value
sb.setLength(0);
while (index < chars.length && chars[index] != ';' && chars[index] != '}')
sb.append(chars[index++]);
String propertyValue = sb.toString().trim();
/*
* Here is the check for duplicate property definitions. The method
* Map.put(Object, Object) always returns the value that had been stored
* under the given name before.
*/
String previousValue = properties.put(propertyName, propertyValue);
if (previousValue != null)
throw new IllegalArgumentException("Duplicate property \"" + propertyName + "\" in selector \"" + selector + "\".");
if (index < chars.length && chars[index] == ';')
index++;
skipWhitespace();
}
private void parseSelector(Map<String, Map<String, String>> selectors) {
skipWhitespace();
// get the CSS selector
StringBuilder sb = new StringBuilder();
while (index < chars.length && chars[index] != '{')
sb.append(chars[index++]);
String selector = sb.toString().trim();
if (index == chars.length)
throw new IllegalArgumentException("CSS Selector name \"" + selector + "\" without content.");
// skip the opening brace
index++;
skipWhitespace();
Map<String, String> properties = new LinkedHashMap<String, String>();
selectors.put(selector, properties);
while (index < chars.length && chars[index] != '}') {
parseProperty(selector, properties);
skipWhitespace();
}
// skip the closing brace
index++;
}
private Map<String, Map<String, String>> parseFile() {
Map<String, Map<String, String>> selectors = new LinkedHashMap<String, Map<String, String>>();
while (index < chars.length) {
parseSelector(selectors);
skipWhitespace();
}
return selectors;
}
public static void main(String[] args) {
CssParser parser = new CssParser("body {margin:prueba;A:B;a:Arial, Courier New, \"monospace\";\n}");
Map<String, Map<String, String>> selectors = parser.parseFile();
System.out.println("There are " + selectors.size() + " selectors.");
for (Map.Entry<String, Map<String, String>> entry : selectors.entrySet()) {
String selector = entry.getKey();
Map<String, String> properties = entry.getValue();
System.out.println("Selector " + selector + ":");
for (Map.Entry<String, String> property : properties.entrySet()) {
String name = property.getKey();
String value = property.getValue();
System.out.println(" Property name \"" + name + "\" value \"" + value + "\"");
}
}
}
}
Related
In the given array in Java, [766-09-9090, 766-09-9090, 877-90-9090, 877-90-9090, "S", "T", "U"]
How could we obtain a new array with values like this :
[766-09-9090, 877-90-9090, 877-90-9090, 766-90-9090, "S", "T", "U"]
Note : No changes on non SSN values like "S", "T, "U"
This is my first stab but I am not getting the results I am looking for. Any suggestion would be appreciate
public static modifyArray(List<String> arrays) {
List<String> newArray = new ArrayList<String>();
boolean matchedFound = false;
for (int index = 0; index < arrays.size(); index++) {
if (arrays.get(index).length() == 9 && isValidSSN(arrays.get(index))) {
String nextMatchingSsn = getNextDistinctSsn(arrays);
System.out.println("Next Distinct SSN IS : " + nextMatchingSsn);
if (nextMatchingSsn != "") {
String[] pair = nextMatchingSsn.split(":");
if (pair.length == 2) {
Integer key = Integer.parseInt(pair[1]);
String ssn = pair[0];
swap(arrays.toArray(), index, key);
}
}
newArray.add(nextMatchingSsn);
} else {
System.out.println("Non Matching " + arrays.get(index));
newArray.add(arrays.get(index));
}
}
}
private static boolean isValidSSN(String s) {
if (s.length() != 9) {
throw new IllegalArgumentException("An SSN length must be 9");
}
for (int i = 0; i < 9; i++)
if (!Character.isDigit(s.charAt(i))) {
throw new IllegalArgumentException("SSN must have only digits.");
}
return (true);
}
private static String getNextDistinctSsn(List<String> ssns) {
String firstDiffSsn = "";
String currentSsn = "";
for (int index = 0; index < ssns.size(); index++) {
if (!currentSsn.equals(ssns.get(index)) && currentSsn != "") {
firstDiffSsn = ssns.get(index);
return firstDiffSsn + ":" + index;
} else {
currentSsn = ssns.get(index);
}
}
return firstDiffSsn;`enter code here`
}
public static final <T> void swap (T[] a, int i, int j) {
T t = a[i];
a[i] = a[j];
a[j] = t;
}
This is my first stab but I am not getting the results I am looking for. Any suggestion would be appreciated. So basically, if I have to write a unit test my expected result would look something like this:
public void validateResult(){
}
I can see a number of problems.
Your code is creating a new list (called newArray !?!) and populating it, but then not using it.
Your code is splitting SSNs on a : character, but the input data has no : characters.
Your SSN validation method will throw an unchecked exception if it encounters something that is not a valid SSN, but your code expects it to return false in that scenario.
This is wrong: currentSsn != "". Do not use == or != to compare strings. You are liable to get an incorrect result.
And so on.
I am working on a Evil Hangman Program And for some reason I am getting this error:
What length word do you want to use? 4
How many wrong answers allowed? 4
guesses : 4
guessed : []
current : ----
Your guess? e
Exception in thread "main" java.lang.NullPointerException
at Game.HangmanManager.record(HangmanManager.java:142)
at Game.HangmanMain.playGame(HangmanMain.java:62)
at Game.HangmanMain.main(HangmanMain.java:42)
Here is my program:
package Game;
import java.util.*;
public class HangmanManager
{
private String pattern;
private int max;
private int length;
private SortedSet<Character> guessesMade;
private Set<String> currentWords;
private Map<String, Set<String>> patternMap;
public HangmanManager(List<String> dictionary , int length, int max)
{
this.max = max;
this.length = length;
if( length < 1 && max < 0)
{
throw new IllegalArgumentException();
}
words = new TreeSet<String>();
guessesMade = new TreeSet<Character>();
currentWords = new TreeSet<String>(); // current words =(words)
patternMap = new TreeMap<String, Set<String>>(); // patternMAP = < pattern, words>
for (String word : dictionary)
{
if (word.length() == length)
{
words.add(word); // if length of the word matches a word with the same length it will be added
}
}
}
public Set<String> words()
{
return words;
}
public int guessesLeft()
{
return max - guessesMade.size();
}
public SortedSet<Character> guesses()
{
return guessesMade;
}
public String pattern()
{
if (words.isEmpty())
{
throw new IllegalArgumentException("Invalid No Words");
}
pattern = " "; // blank for now
for (int i = 0; i < length; i++)
{
pattern += "-"; // will have a "-" for how long the length of the word is
}
return pattern; // will return the number of lines
}
public int record(char guess)
{
if (guessesLeft() < 1 || words.isEmpty())
{
throw new IllegalStateException();
}
if (!words.isEmpty() && guessesMade.contains(guess))
{
throw new IllegalArgumentException();
}
guessesMade.add(guess); // guess
int occurences = 0;
for( String word: words)
{
if( patternMap.containsKey (pattern))
{
occurences = generatePattern(word, guess); // the word including the guess letter will fill in the blank spots
currentWords.add(word); // the word will be added to the possibilities
currentWords = patternMap.get(pattern); // the word will be able to fill once the guesses are made
// if(patternMap.get(pattern)!=null)
// {
// currentWords = patternMap.get(pattern);
//
// }
patternMap.put(pattern, currentWords);
}
else
{
currentWords.add(word);
patternMap.put(pattern, currentWords);
}
}
words = find();
return occurences;
}
private Set<String> find()
{
int maxSize = 0;
Map <String, Integer> patternCount = new TreeMap<String, Integer>();
for (String key : patternMap.keySet()) // keyset equals word
{
patternCount.put(key, patternMap.get(key).size()); // size of the word
if (patternMap.get(key).size() > maxSize)
{
maxSize = patternMap.get(key).size();
pattern = key; // pattern will becomes based on the word
} else if (patternMap.get(key).size() == maxSize)
{
if (key.length() >= pattern.length())
{
pattern = key;
maxSize = patternMap.get(key).size(); // the pattern is now the word key
}
}
}
System.out.println("Current pattern: " + pattern);
return patternMap.get(pattern); // the pattern that will becomes now that the word was picked
}
private int generatePattern(String s, char guess)
{
int count = 0;
pattern = "";
for (int i = 0; i < length; i++)
{
if (s.charAt(i) == guess)
{
pattern += guess + " ";
count++;
} else
{
pattern += "- ";
}
}
return count;
}
}
The error seems to happen in the record method for the :
patternMap.put(pattern, currentWords); on Line 149
I ran the debugger numerous times and I do notice that if you follow the program you will see that currentWords becomes Null eventually after the program runs through the words after one time even though I instantiate it and I created a Map for patternMap.
If anyone can tell me what to do or what to change I will really appreciate it because I am so lost with this
patternMap is a TreeMap. As you said, your problem is with patternMap.put(pattern, currentWords); on line 149. According to the JavaDocs for TreeMap, put() throws NullPointerException...
if the specified key is null and this map uses natural ordering, or its comparator does not permit null keys
Since a TreeMap<String,Object> uses natural ordering of it's keys (i.e. String), the problem is that pattern is null. Why don't you just initialize pattern to a default value:
private String pattern = " ";
I have a requirement, in which I have to scan certain files for the match of certain keywords. My keywords list size is around 40000 and all my files have approximately 4000 lines. Also the keyword should not be commented in the file, hence I have to take care of comments as well. The code what I have written to know the occurrence of the keyword is taking around 5 minutes for each file. I don't know what change I can make to reduce the execution time.
The code is as shown below.
for (File fl : files) {
flag = false;
content = FileUtils.readFileToString(fl);
System.out.println(fl.getName());
fileName = fl.getName();
// Object Keywords scanning
keywords = null;
keywords = findKeywordType(fileName);
if (keywords != null) {
Boolean keywordCount = false;
for (String[] key : keywords) {
key[0] = key[1];
}
for (String[] key : keywords) {
Boolean check = false;
if (content.contains(key[0])) {
if (content.contains(key[3] + ".")) {
check = true;
}
if (check) {
continue;
}
if (content.contains(key[3])) {
keywordCount = FindOccurence(fl, key[0], key[3]);
if (keywordCount) {
System.out.println("Writing keywords");
objKwm = new ObjectKeywordMaster();
objKwm.setObjectName(key[0]);
objKwm.setObjectType(key[1]);
objKwm.setObjectOwner(key[2]);
objKwm.setDependentObjectName(key[3]);
objKwm.setDependentObjectType(key[4]);
objKwm.setDependentObjectOwner(key[5]);
objKw.getObjectKeywords().add(objKwm);
}
}
}
}
}
FindOccurrence method code is
private static Boolean FindOccurence(File fl, String objectName, String keyword) throws IOException {
int startComment = 0;
int endComment = 0;
Boolean objCheck = false;
Boolean keyCheck = false;
Boolean check = false;
List line = FileUtils.readLines(fl);
int fileLength = line.size();
int objCount = 0;
int keyCount = 0;
loop:
for (int j = 0; j < fileLength; j++) {
if (line.get(j).toString().contains("/*")) {
startComment = j;
}
if (line.get(j).toString().contains("*/")) {
endComment = j;
}
if (line.get(j).toString().contains(objectName)) {
objCheck = false;
Pattern p = Pattern.compile("\\b" + objectName + "\\b");
Matcher m = p.matcher(line.get(j).toString());
while (m.find()) {
objCheck = true;
objCount++;
}
if (objCheck) {
if (line.get(j).toString().contains("#")) {
int objIndex = line.get(j).toString().indexOf(objectName);
int commentIndex = line.get(j).toString().indexOf("#");
if (objIndex > commentIndex) {
objCount--;
}
} else {
if (line.get(j).toString().contains("--")) {
int objIndex = line.get(j).toString().indexOf(objectName);
int commentIndex = line.get(j).toString()
.indexOf("--");
if (objIndex > commentIndex) {
objCount--;
}
}
}
if ((j >= startComment && j <= endComment)||(j >= startComment && endComment==0)) {
objCount--;
}
}
}
if (line.get(j).toString().contains(keyword)) {
keyCheck = false;
Pattern p = Pattern.compile("\\b" + keyword + "\\b");
Matcher m = p.matcher(line.get(j).toString());
while (m.find()) {
keyCheck = true;
keyCount++;
}
if (keyCheck) {
if (line.get(j).toString().contains("#")) {
int objIndex = line.get(j).toString().indexOf(keyword);
int commentIndex = line.get(j).toString().indexOf("#");
if (objIndex > commentIndex) {
keyCount--;
}
} else {
if (line.get(j).toString().contains("--")) {
int objIndex = line.get(j).toString().indexOf(keyword);
int commentIndex = line.get(j).toString()
.indexOf("--");
if (objIndex > commentIndex) {
keyCount--;
}
}
}
if ((j >= startComment && j <= endComment)||(j >= startComment && endComment==0)) {
keyCount--;
}
}
}
if(objCount > 0 && keyCount >0){
check = true;
break loop;
} else
check = false;
}
return check;
}
}
I have two find occurence of two keywords present in the same list. Please suggest some ways so that I can reduce the execution time.
1) Before to start looking for any keyword, prepare the file content: remove the comments, ... .
2) Split file content in words.
3) Do not loop for each keyword: use a Set who stores all keywords.
I am trying to count frequency of words in a text file. But I have to use a different approach. For example, if the file contains BRAIN-ISCHEMIA and ISCHEMIA-BRAIN, I need to count BRAIN-ISCHEMIA twice (and leaving ISCHEMIA-BRAIN) or vice versa. Here is my piece of code-
// Mapping of String->Integer (word -> frequency)
HashMap<String, Integer> frequencyMap = new HashMap<String, Integer>();
// Iterate through each line of the file
String[] temp;
String currentLine;
String currentLine2;
while ((currentLine = in.readLine()) != null) {
// Remove this line if you want words to be case sensitive
currentLine = currentLine.toLowerCase();
temp=currentLine.split("-");
currentLine2=temp[1]+"-"+temp[0];
// Iterate through each word of the current line
// Delimit words based on whitespace, punctuation, and quotes
StringTokenizer parser = new StringTokenizer(currentLine);
while (parser.hasMoreTokens()) {
String currentWord = parser.nextToken();
Integer frequency = frequencyMap.get(currentWord);
// Add the word if it doesn't already exist, otherwise increment the
// frequency counter.
if (frequency == null) {
frequency = 0;
}
frequencyMap.put(currentWord, frequency + 1);
}
StringTokenizer parser2 = new StringTokenizer(currentLine2);
while (parser2.hasMoreTokens()) {
String currentWord2 = parser2.nextToken();
Integer frequency = frequencyMap.get(currentWord2);
// Add the word if it doesn't already exist, otherwise increment the
// frequency counter.
if (frequency == null) {
frequency = 0;
}
frequencyMap.put(currentWord2, frequency + 1);
}
}
// Display our nice little Map
System.out.println(frequencyMap);
But for the following file-
ISCHEMIA-GLUTAMATE
ISCHEMIA-BRAIN
GLUTAMATE-BRAIN
BRAIN-TOLERATE
BRAIN-TOLERATE
TOLERATE-BRAIN
GLUTAMATE-ISCHEMIA
ISCHEMIA-GLUTAMATE
I am getting the following output-
{glutamate-brain=1, ischemia-glutamate=3, ischemia-brain=1, glutamate-ischemia=3, brain-tolerate=3, brain-ischemia=1, tolerate-brain=3, brain-glutamate=1}
The problem is in second while block I think. Any light on this problem will be highly appreciated.
From an algorithm perspective, you may want to consider the following approach:
For each string, split, then sort, then re-combine (i.e. take DEF-ABC and convert to ABC-DEF. ABC-DEF would convert to ABC-DEF). Then use that as the key for your frequency count.
If you need to hold onto the exact original item, just include that in your key - so the key would have: ordinal (the re-combined string) and original.
Disclaimer: I stole the sweet trick suggested by Kevin Day for my implementation.
I still want to post just to let you know that using the right data structure (Multiset/Bad) and the right libraries (google-guava) will not only simplify the code but also makes it efficient.
Code
public class BasicFrequencyCalculator
{
public static void main(final String[] args) throws IOException
{
#SuppressWarnings("unchecked")
Multiset<Word> frequency = Files.readLines(new File("c:/2.txt"), Charsets.ISO_8859_1, new LineProcessor() {
private final Multiset<Word> result = HashMultiset.create();
#Override
public Object getResult()
{
return result;
}
#Override
public boolean processLine(final String line) throws IOException
{
result.add(new Word(line));
return true;
}
});
for (Word w : frequency.elementSet())
{
System.out.println(w.getOriginal() + " = " + frequency.count(w));
}
}
}
public class Word
{
private final String key;
private final String original;
public Word(final String orig)
{
this.original = orig.trim();
String[] temp = original.toLowerCase().split("-");
Arrays.sort(temp);
key = temp[0] + "-"+temp[1];
}
#Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((getKey() == null) ? 0 : getKey().hashCode());
return result;
}
#Override
public boolean equals(final Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (!(obj instanceof Word))
{
return false;
}
Word other = (Word) obj;
if (getKey() == null)
{
if (other.getKey() != null)
{
return false;
}
}
else if (!getKey().equals(other.getKey()))
{
return false;
}
return true;
}
#Override
public String toString()
{
return getOriginal();
}
public String getKey()
{
return key;
}
public String getOriginal()
{
return original;
}
}
Output
BRAIN-TOLERATE = 3
ISCHEMIA-GLUTAMATE = 3
GLUTAMATE-BRAIN = 1
ISCHEMIA-BRAIN = 1
Thanks everyone for your help. Here is how I solved it-
// Mapping of String->Integer (word -> frequency)
TreeMap<String, Integer> frequencyMap = new TreeMap<String, Integer>();
// Iterate through each line of the file
String[] temp;
String currentLine;
String currentLine2;
while ((currentLine = in.readLine()) != null) {
temp=currentLine.split("-");
currentLine2=temp[1]+"-"+temp[0];
// Iterate through each word of the current line
StringTokenizer parser = new StringTokenizer(currentLine);
while (parser.hasMoreTokens()) {
String currentWord = parser.nextToken();
Integer frequency = frequencyMap.get(currentWord);
Integer frequency2 = frequencyMap.get(currentLine2);
// Add the word if it doesn't already exist, otherwise increment the
// frequency counter.
if (frequency == null) {
if (frequency2 == null)
frequency = 0;
else {
frequencyMap.put(currentLine2, frequency2 + 1);
break;
}//else
} //if (frequency == null)
frequencyMap.put(currentWord, frequency + 1);
}//while (parser.hasMoreTokens())
}//while ((currentLine = in.readLine()) != null)
// Display our nice little Map
System.out.println(frequencyMap);
I have the following string which will probably contain ~100 entries:
String foo = "{k1=v1,k2=v2,...}"
and am looking to write the following function:
String getValue(String key){
// return the value associated with this key
}
I would like to do this without using any parsing library. Any ideas for something speedy?
If you know your string will always look like this, try something like:
HashMap map = new HashMap();
public void parse(String foo) {
String foo2 = foo.substring(1, foo.length() - 1); // hack off braces
StringTokenizer st = new StringTokenizer(foo2, ",");
while (st.hasMoreTokens()) {
String thisToken = st.nextToken();
StringTokenizer st2 = new StringTokenizer(thisToken, "=");
map.put(st2.nextToken(), st2.nextToken());
}
}
String getValue(String key) {
return map.get(key).toString();
}
Warning: I didn't actually try this; there might be minor syntax errors but the logic should be sound. Note that I also did exactly zero error checking, so you might want to make what I did more robust.
The speediest, but ugliest answer I can think of is parsing it character by character using a state machine. It's very fast, but very specific and quite complex. The way I see it, you could have several states:
Parsing Key
Parsing Value
Ready
Example:
int length = foo.length();
int state = READY;
for (int i=0; i<length; ++i) {
switch (state) {
case READY:
//Skip commas and brackets
//Transition to the KEY state if you find a letter
break;
case KEY:
//Read until you hit a = then transition to the value state
//append each letter to a StringBuilder and track the name
//Store the name when you transition to the value state
break;
case VALUE:
//Read until you hit a , then transition to the ready state
//Remember to save the built-key and built-value somewhere
break;
}
}
In addition, you can implement this a lot faster using StringTokenizers (which are fast) or Regexs (which are slower). But overall, individual character parsing is most likely the fastest way.
If the string has many entries you might be better off parsing manually without a StringTokenizer to save some memory (in case you have to parse thousands of these strings, it's worth the extra code):
public static Map parse(String s) {
HashMap map = new HashMap();
s = s.substring(1, s.length() - 1).trim(); //get rid of the brackets
int kpos = 0; //the starting position of the key
int eqpos = s.indexOf('='); //the position of the key/value separator
boolean more = eqpos > 0;
while (more) {
int cmpos = s.indexOf(',', eqpos + 1); //position of the entry separator
String key = s.substring(kpos, eqpos).trim();
if (cmpos > 0) {
map.put(key, s.substring(eqpos + 1, cmpos).trim());
eqpos = s.indexOf('=', cmpos + 1);
more = eqpos > 0;
if (more) {
kpos = cmpos + 1;
}
} else {
map.put(key, s.substring(eqpos + 1).trim());
more = false;
}
}
return map;
}
I tested this code with these strings and it works fine:
{k1=v1}
{k1=v1, k2 = v2, k3= v3,k4 =v4}
{k1= v1,}
Written without testing:
String result = null;
int i = foo.indexOf(key+"=");
if (i != -1 && (foo.charAt(i-1) == '{' || foo.charAt(i-1) == ',')) {
int j = foo.indexOf(',', i);
if (j == -1) j = foo.length() - 1;
result = foo.substring(i+key.length()+1, j);
}
return result;
Yes, it's ugly :-)
Well, assuming no '=' nor ',' in values, the simplest (and shabby) method is:
int start = foo.indexOf(key+'=') + key.length() + 1;
int end = foo.indexOf(',',i) - 1;
if (end==-1) end = foo.indexOf('}',i) - 1;
return (start<end)?foo.substring(start,end):null;
Yeah, not recommended :)
Adding code to check for existance of key in foo is left as exercise to the reader :-)
String foo = "{k1=v1,k2=v2,...}";
String getValue(String key){
int offset = foo.indexOf(key+'=') + key.length() + 1;
return foo.substring(foo.indexOf('=', offset)+1,foo.indexOf(',', offset));
}
Please find my solution:
public class KeyValueParser {
private final String line;
private final String divToken;
private final String eqToken;
private Map<String, String> map = new HashMap<String, String>();
// user_uid=224620; pass=e10adc3949ba59abbe56e057f20f883e;
public KeyValueParser(String line, String divToken, String eqToken) {
this.line = line;
this.divToken = divToken;
this.eqToken = eqToken;
proccess();
}
public void proccess() {
if (Strings.isNullOrEmpty(line) || Strings.isNullOrEmpty(divToken) || Strings.isNullOrEmpty(eqToken)) {
return;
}
for (String div : line.split(divToken)) {
if (Strings.isNullOrEmpty(div)) {
continue;
}
String[] split = div.split(eqToken);
if (split.length != 2) {
continue;
}
String key = split[0];
String value = split[1];
if (Strings.isNullOrEmpty(key)) {
continue;
}
map.put(key.trim(), value.trim());
}
}
public String getValue(String key) {
return map.get(key);
}
}
Usage
KeyValueParser line = new KeyValueParser("user_uid=224620; pass=e10adc3949ba59abbe56e057f20f883e;", ";", "=");
String userUID = line.getValue("user_uid")