Divide two String into substrings and pair them - java

I am looking for interesting solutions for this problem :
String key = "1;2;3;4";
String value = "Value1;Value2;Value whitespace;"
Now ';' devides each value from another. The same symbol ';' devides the keys also.
Now I want to end up with :
{"1" : "Value1", "2" : "Value2", "3" : "Value whitespace", "4" : null}
Of course if the values were more then the keys then the null should be no the left side of the pair (null: "Value5").
I made a pretty complecated solution to this problem using char arrays but is one big FOR with many cases and stuff.(it is O(n)). So I am curious to see a regex or substring solution or something that not includes big loop.
EDIT:
Mine solution :
private List<ExampleObject> getExampleObjects(String key , String value) {
// s
if (key == null || value == null) {
return new ArrayList<ExampleObject>();
}
List<ExampleObject> exampleObjects = new ArrayList<ExampleObject>();
char[] keyToCharArray = key.toCharArray();
char[] valueToCharArray = value.toCharArray();
StringBuilder name = new StringBuilder();
StringBuilder value = new StringBuilder();
boolean nameCompleted = false;
boolean valueCompleted = false;
for (int i = 0, j = 0; i < keyToCharArray.length || j < valueToCharArray.length;) {
if (!nameCompleted) {
char a = ' ';
try{
a = keyToCharArray[i];
} catch(Exception e){
a = ';';
// throw : VALES and key not match. More key then value
//throw(e);
}
if (a == ';' ) {
nameCompleted = true;
} else if (!(i + 1 < keyToCharArray.length)){
name.append(a);
nameCompleted = true;
} else {
name.append(a);
}
i++;
}
if (!valueCompleted) {
char a = ' ';
try{
a = valueToCharArray[j];
} catch(Exception e){
a = ';';
// throw : VALES and key not match. More value then key
//throw(e);
}
if (a == ';') {
valueCompleted = true;
} else if(!(j + 1 < valueToCharArray.length)) {
value.append(a);
valueCompleted = true;
} else {
value.append(a);
}
j++;
}
if (nameCompleted && valueCompleted) {
exampleObjects.add(new ExampleObject(name.toString(), value.toString()));
name.setLength(0);
value.setLength(0);
nameCompleted = false;
valueCompleted = false;
}
}
return exampleObjects;
}
Where ExampleObject.class has fields key and value.

I've come up with a solution to your problem:
Output
{"1" : "Value1", "2" : "Value2", "3" : "Value whitespace", "4" : "null"}
Code
public class HelloWorld{
public static void main(String []args){
String key = "1;2;3;4";
String value = "Value1;Value2;Value whitespace;";
String[] keyArr = key.split(";");
String[] valueArr = value.split(";");
String finalJSON = "{";
for(int i=0; i<(keyArr.length > valueArr.length ? keyArr.length : valueArr.length); i++) {
try {
finalJSON += "\"" + keyArr[i] + "\"";
}
catch(ArrayIndexOutOfBoundsException e) {
finalJSON += "\"null\"";
}
finalJSON += " : ";
try {
finalJSON += "\"" + valueArr[i] + "\"";
}
catch(ArrayIndexOutOfBoundsException e) {
finalJSON += "\"null\"";
}
if(i!=(keyArr.length > valueArr.length ? keyArr.length : valueArr.length) - 1)
finalJSON += ", ";
}
finalJSON += "}";
System.out.println(finalJSON);
}
}

Java 8:
String key = "1;2;3;4";
String value = "Value1;Value2;Value whitespace;";
String[] keys = key.split(";", -2);
String[] values = value.split(";", -2);
Map<String, String> result = IntStream.range(0, keys.length).mapToObj(i->i).collect(Collectors.toMap(i->keys[i], i-> values[i]));
result.entrySet().forEach(e->result.put(e.getKey(), e.getValue().length()==0 ? null : e.getValue()));

Another - slightly more elegant Java8 and Generic as possible.
/**
* General pair of items.
*
* #param <P> - Type of the first item in the pair.
* #param <Q> - Type of the second item.
*/
static class Pair<P, Q> {
final P p;
final Q q;
public Pair(P p, Q q) {
this.p = p;
this.q = q;
}
#Override
public String toString() {
return "{" + p + "," + q + "}";
}
}
/**
* Gets the `n`th item is present in the array - otherwise returns null.
*
* #param a - The array
* #param n - Which one in the array we want.
* #param <T> - The type of the array entries.
* #return - The `n`th entry in the array or null if not present.
*/
private static <T> T n(T[] a, int n) {
return n < a.length ? a[n] : null;
}
/**
* Pairs up each element in the arrays.
*
* #param <P> - The type of the elements in the `P` array.
* #param <Q> - The type of the elements in the `Q` array.
* #param ps - The `P` array.
* #param qs - The `Q` array.
* #return A list of `Pair`s of each element.
*/
static <P, Q> List pairUp(P[] ps, Q[] qs) {
return IntStream.range(0, Math.max(ps.length, qs.length))
.mapToObj(i -> new Pair<>(n(ps, i), n(qs, i)))
.collect(Collectors.toList());
}
/**
* Splits the two strings on a separator and returns a list of Pairs of the corresponding items.
*
* #param a - The first string.
* #param b - The second string.
* #param separator - The separator.
* #return - A List of Paired up entries from `a` and `b`.
*/
private static List<Pair<String, String>> fold(String a, String b, String separator) {
return pairUp(a.split(separator, -1), b.split(separator, -1));
}
public void test() {
System.out.println(fold("1;2;3;4", "Value1;Value2;Value whitespace", ";"));
}

Try the following: (if you want to print a String as you said)
Split the two Strings into Arrays using String#split()
Create counters for the value[] and the key[]
Create a boolean to indicate if a key or a value gets appended
Use a StringBuilder and loop through the length of the key[]
Append stuff
Return a new String using StringBuilder#append()
Done. Try it before checking out the solution!
StringBuilder: https://docs.oracle.com/javase/7/docs/api/java/lang/StringBuilder.html
String: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html
My solution for printing out the String:
public static void main(String[] args) {
String key = "1;2;3;4";
String value = "Value1;Value2;Value whitespace";
String[] keys = key.split(";");
String[] values = value.split(";");
StringBuilder sb = new StringBuilder("{");
boolean isKey = true;
int keyCount = 0;
int valueCount = 0;
for(int i = 0; i < key.length(); i++) {
sb.append("\"");
if(isKey) {
sb.append(keys[keyCount]).append("\" : ");
keyCount++;
} else {
sb.append(values[valueCount]).append("\", ");
valueCount++;
}
isKey = !isKey;
}
sb.append("}");
System.out.println(sb.toString());
}

A different point of view: don't do such things "manually".
What I mean is: instead of doing all "low level" operations yourself; you should abstract.
First, transform your key value strings into a Map. Like:
String keys[] = keyString.split(";");
String values[] = valueString.split(";);
... probably some consistency checks that arrays have same length; and no nulls in keys
Map<String, String> map = new HashMap<>();
for (int i=0; i < keys.length; i++) {
map.put(keys[i], values[i]);
}
then finally, use some existing JSON library to simply generate a JSON representation based on that map.
In other words: unless you are talking about lists with millions of entries; do not worry about performance. Instead, worry about good abstractions, not re-inventing the wheel and code readability.
But if you really have to worry about performance, or memory aspects, then simply split into arrays, and then use those two arrays as input to some function that uses a StringBuilder to build the required output string.

Related

Trying to alternate two strings together in Java

/* Programs goal is to take two strings and use a method to alternate the characters.
example - "test" "case" -> "tceasste"
Sorry I tried looking at other similar questions but I didn't understand how they worked as I am very new to Java and coding in general.
*/
public class MyProgram extends ConsoleProgram
{
public void run()
{
System.out.println(sCombine("test","case")); //"tceasste"
System.out.println(sCombine("avalanche","dog")); //"advoaglanche"
System.out.println(sCombine("","water")); //"water"
}
public String sCombine(String a, String b)
{
String result = "";
int lengtha = a.length();
int lengthb = b.length();
int lengthc = 0;
if(lengtha < lengthb)
{
lengthc = lengtha;
}
else
{
lengthc = lengthb;
}
for(int i = 0; i < lengthc; i++)
{
char currentA = a.charAt(i);
char currentB = b.charAt(i);
result += a;
result += b;
}
return result;
}
}
The problem is that you're doing:
result += a;
You need to do:
result += currentA;
I would also suggest looking at the StringBuilder class. It has a lot of built in functionality for things of this nature :)
Just another way. Read all the comments in code:
/**
* Sequentially blends two strings together one character at a time, for
* example, if the first argument was "cat" and the second argument was
* "dog" then the returned result will be: "cdaotg".<br><br>
*
* <b>Example Usage:</b><pre>
* {#code
* final String a = "cat";
* final String b = "elephant";
* String newString = sCombine(a, b);
* System.out.println(newString);
* // Console will display: cealte }
*
* OR
* {#code
* final String a = "cat";
* final String b = "elephant";
* String newString = sCombine(a, b, true); // true is optionally supplied here.
* System.out.println(newString);
* // Console will display: eclaetphant }</pre>
*
* #param stringA (String) The first String to blend.<br>
*
* #param stringB (String) The second String to blend.<br>
*
* #param startWithLongest (Optional - boolean varArgs) Default is false <pre>
* whereas this method will always take the first
* supplied argument and blend the second argument
* into it. In this default situation, the first
* argument is always considered to contain the
* longest String. If however, the second argument
* contains more characters then the those extra
* characters will be truncated, for example: "cat"
* and "elephant". Result will be: "cealte". It would
* be beneficial to always pass the longest string as
* the first argument in order to achieve the result
* of: "eclaetphant".
*
* If boolean true is supplied to this optional parameter
* then the longest argument passed to this method will
* always be considered as the first argument rather than
* the first supplied argument, for example: "cat" as the
* first argument and "elephant" as the second argument
* and true as the third argument will return a result
* of "eclaetphant".
*
* Supplying nothing forces default to be used.</pre>
*
* #return (String) The blended String;
*/
public static String sCombine(final String stringA, final String stringB,
final boolean... startWithLongest) {
String strgA = stringA, strgB = stringB; // So to maintain original Strings
/* If `true` is supplied to the startWithLongest optional
vararg parameter then ensure the String argument with the
most characters is the first argument (if true, always place
the longest first). */
if (startWithLongest.length > 0 && startWithLongest[0]) {
if (strgB.length() > strgA.length()) {
strgA = stringB;
strgB = stringA;
}
}
// Build the new blended string
StringBuilder sb = new StringBuilder("");
for (int i = 0; i < strgA.length(); i++) {
sb.append(strgA.charAt(i))
/* 'Ternary Operator' is used here to ensure strgB
contains the current index to carry on. If not
then just the remaining characters of strgA are
sequentially apended to the StringBuilder object
to finish up things. */
.append(i < strgB.length() ? strgB.charAt(i) : "");
}
return sb.toString();
}
You can use a loop that iterates the maximum length of both the strings. Then you can extract the individual character at the ith position and add it alternatively to a resulting String object. I have used StringBuiler as it mutable( diff here ) This is the code that I have attached.
public class MyProgram {
public static void main(String[] args) {
System.out.println(sCombine("test", "case")); // "tceasste"
System.out.println(sCombine("avalanche", "dog")); // "advoaglanche"
System.out.println(sCombine("", "water")); // "water"
}
public static String sCombine(String a, String b) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < Math.max(a.length(), b.length()); i++) {
char aChar = i < a.length() ? a.charAt(i) : '#';
char bChar = i < b.length() ? b.charAt(i) : '#';
if (aChar != '#') {
result.append(aChar + "");
}
if (bChar != '#') {
result.append(bChar + "");
}
}
return result.toString();
}
}
and the output is :
tceasste
advoaglanche
water
You can use substring to savely append the remainder of the Strings. Should the limit be out of bounds, no exception will be thrown, because substring will just return an empty String.
public String sCombine(String a, String b) {
final StringBuilder builder = new StringBuilder();
final int min = Math.min(a.length(), b.length());
for (int i = 0; i < min; i++) {
builder.append(a.charAt(i)).append(b.charAt(i));
}
builder.append(a.substring(min)).append(b.substring(min));
return builder.toString();
}
take two strings and use a method to alternate the characters.
example - "test" "case" -> "tceasste"
private static String mixedUp(String first, String second)
{
First, identify the shorter string so it won't attempt to pull a character that is out out bounds
int length = (first.length() > second.length())
? second.length() : first.length();
Then for that length, add their alternating characters to a new String() Object like so:
String output = new String();
for(int index = 0; index < length; index++)
{
output += first.charAt(index);
output += second.charAt(index);
}
You can use substring to savely append the remainder of the Strings. Should the limit be out of bounds, no exception will be thrown, because substring will just return an empty String. - #thinkgruen
You can add the rest of the characters using the ternary operator again like so:
output += (first.length() > second.length())
? second.substring(length) : first.substring(length);
return output;
}

java program for incrementing array at last

Write a java program with the following scenario.
A function which increments a string to create a new string.
If the string ends with a number, the number should be incremented by 1.
If the string doesn't end with a number, 1 should be added to the new string.
If the number has leading zeros, the amount of digits should be considered.
You want to achieve something like this
1.If the last character is a number increment it. (Taken possibility of '9' as a last character so incrementing it to 10.)
2.If the last character is a not a number increment it.
String string = "StackOverflowError7";
if(Character.isDigit(string.charAt(string.length()-1)))
if(string.charAt(string.length()-1) != '9'){
string = string.substring(0,string.length()-1) + (char)(string.charAt(string.length()-1)+1);
} else {
string = string.substring(0,string.length()-1) + "10";
}
else {
string = string.substring(0,string.length()-1) + (char)(string.charAt(string.length()-1)+1);
}
System.out.println(string);//Final output
This is for searching for an individual value, or a specific value that you want to find in a sequence:
static void methodOne() {
// initialize array
String[] array = { "one", "2", "three", "four" };
// initialize value to search
String valueToSearch = "2";
// initialize boolean variable
boolean isExists = false;
// iterate over array
for (int i = 0; i < array.length; i++) {
// get the value at current array index
String arrayValue = array[i];
// compare values
if (valueToSearch.equals(arrayValue)) {
isExists = true;
// if value is found, terminate the loop
break;
}
}
if (isExists) {
//add code
} else {
//add code
}
}
If you want to search strictly, then if it is a number, I recommend this:
private Pattern pattern = Pattern.compile("-?\\d+(\\.\\d+)?");
public boolean isNumeric(String strNum) {
if (strNum == null) {
return false;
}
return pattern.matcher(strNum).matches();
}
Try this.
static final Pattern N = Pattern.compile("(.*?)(\\d+)");
static String increment(String s) {
String r = N.matcher(s).replaceFirst(m ->
String.format("%s%0" + m.group(2).length() + "d",
m.group(1), Integer.parseInt(m.group(2)) + 1));
return r.equals(s) ? s + "1" : r;
}
static void testIncrement(String s) {
System.out.println(s + " -> " + increment(s));
}
and
testIncrement("abc0000");
testIncrement("abc0011");
testIncrement("abc0099");
testIncrement("abc9999");
testIncrement("abc012def8");
testIncrement("abc012def");
output:
abc0000 -> abc0001
abc0011 -> abc0012
abc0099 -> abc0100
abc9999 -> abc10000
abc012def8 -> abc012def9
abc012def -> abc012def1

Suggestions on how to fix this tricky problem in Java

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.

Need to encode repetitive pattern in String with * , such that * means "repeat from beginning"

Encoding format: introduce * to indicate "repeat from beginning". Example. Input-{a,b,a,b,c,a,b,a,b,c,d} can be written as {a , b, * ,c, * , d}. Output:5; E.g 2: ABCABCE, output- 5.
Here * means repeat from beginning. For example if given String is ABCABCABCABC , it will return ABC**, another example is if String is ABCABCABC, it will return ABC*ABC.
I have the below code but this code assumes that the string will contain the repetitive pattern only and no other characters, I want to modify it to check :
1. Which pattern is repeating
2. Ignore non repeating patterns
2. encode that pattern according to the problem statement
import java.util.Scanner;
public class Magicpotion {
public static void main(String args[]) {
Scanner sc = new Scanner(System.in);
System.out.println("Enter the string:");
String str = sc.nextLine();
int len = str.length();
if (len != 0) {
int lenby3 = len / 3;
int starcount = ( int).(Math.log(lenby3) / Math.log(2));
int leftstring = (lenby3 - (int) Math.pow(2, starcount));
int resultlen = (1 * 3) + starcount + (leftstring * 3);
System.out.println("ResultLength: " + resultlen);
System.out.print("ABC");
for (int i = 0; i < starcount; i++) {
System.out.print("*");
}
for (int i = 0; i < leftstring; i++) {
System.out.print("ABC");
}
} else
System.out.println("ResultLength: " + 0);
}
}
Here my assumption is that ABC will always be repeating pattern , hence I have divided the length by 3. I want to generalise it such that I find the repeating pattern which can be a AB or BC or ABCD and proceed accordingly.
This looks like homework. So instead of a full solution just some hints:
You can process the input string character by character and encode as you go. If you have at some point already read k characters and the next k characters are exactly the same, output a * and advance to position 2k.
Otherwise, output the next input character and advance position to k+1.
As mentioned by dyukha this algorithm does not always result in the shortest possible encoding. If this is required some more effort has to be put into the search.
This problem can be solved using dynamic programming.
Assume that you processed your stay at some position i. You want to understand what it the minimal length of encoding of str[0..i]. Let's call it ans[i]. You have two options:
Just add i-th character to the encoding. So the length is ans[i-1] + 1.
You may write *, when possible. In this case the length is ans[i / 2] + 1 or something like this.
The final length is in ans[n-1]. You can store how you obtained ans[i] to recover the encoding itself.
Checking whether you can write * can be optimized, using some hashing (to obtain O(n) solution instead of O(n^2)).
The difference with Henry's solution is that he always applies * when it's possible. It's not clear to me that it results into the minimal length (if I understood correctly, aaaaaa is a counterexample), so I'm giving a solution I'm sure about.
/**
* #author mohamed ali
* https://www.linkedin.com/in/oo0shaheen0oo/
*/
public class Magic_potion_encoding
{
private static int minimalSteps( String ingredients )
{
StringBuilder sb = new StringBuilder(ingredients);
for(int i =0;i<sb.length();i++)
{
char startChar = sb.charAt(i);
int walkingIndex1=i;
int startIndex2 =sb.toString().indexOf(startChar,i+1);
int walkingIndex2=startIndex2;
while(walkingIndex2 !=-1 && walkingIndex2<sb.length() && sb.charAt(walkingIndex1) == sb.charAt(walkingIndex2) )
{
if(walkingIndex1+1==startIndex2)
{
String subStringToBeEncoded = sb.substring(i,walkingIndex2+1);//substring the string found and the original "substring does not include the last index hence the +1
int matchStartIndex = sb.indexOf(subStringToBeEncoded,walkingIndex2+1);// look for first match for the whole string matched
int matchEndeIndex= matchStartIndex+subStringToBeEncoded.length();
int origStartIndex=i;
int origEndIndex = i+subStringToBeEncoded.length();
if (matchStartIndex!=-1 )
{
if(origEndIndex==matchStartIndex)
{
sb.replace(matchStartIndex,matchEndeIndex,"*");
}
else
{
while(matchStartIndex!=-1 && matchEndeIndex<sb.length() && sb.charAt(origEndIndex) == sb.charAt(matchEndeIndex) )
{
if(origEndIndex==matchStartIndex-1)// if the index of the 2 strings are right behind one another
{
sb.replace(matchStartIndex,matchEndeIndex+1,"*");
}
else
{
origEndIndex++;
matchEndeIndex++;
}
}
}
}
sb.replace(startIndex2,walkingIndex2+1,"*");
break;
}
walkingIndex1++;
walkingIndex2++;
}
}
System.out.println("orig= " + ingredients + " encoded = " + sb);
return sb.length();
}
public static void main( String[] args )
{
if ( minimalSteps("ABCABCE") == 5 &&
minimalSteps("ABCABCEA") == 6 &&
minimalSteps("abbbbabbbb") == 5 &&
minimalSteps("abcde") == 5 &&
minimalSteps("abcbcbcbcd") == 6 &&
minimalSteps("ababcababce") == 6 &&
minimalSteps("ababababxx") == 6 &&
minimalSteps("aabbccbbccaabbccbbcc") == 8)
{
System.out.println( "Pass" );
}
else
{
System.out.println( "Fail" );
}
}
}
Given that the repetitions are from the beginning, every such repeating substring will have the very first character of the given string. [Every repetition needs to be represented by a "star". (i.e ABCABCABC ans = ABC** ) . If all sequential repetitions are to be represented with one "star". (i.e ABCABCABC and = ABC* ), a slight modification to (2) will do the thing (i.e remove the if case where the just a star is added)]
Divide the given string to substrings based on the first character.
Eg. Given String = "ABABCABD"
Sub Strings = {"AB", "ABC", "AB", "ABD"}
Just traverse through the list of substrings and get the required result. I've used a map here, to make the search easy.
Just a rough write up.
SS = {"AB", "ABC", "AB", "ABD"};
result = SS[0];
Map<string, bool> map;
map.put(SS[0],true);
for (i = 1; i < SS.length; i++){
if (map.hasKey(SS[i])){
result += "*";
}
else {
res = nonRepeatingPart(SS[i], map);
result += "*" + res;
map.put(SS[i], true);
}
}
String nonRepeatingPart(str, map){
for (j = str.length-1; j >= 0; j--){
if (map.hasKey(str.subString(0, j))){
return str.subString(j, str.length-1);
}
}
return throwException("Wrong Input");
}
string getCompressed(string str){
string res;
res += str[0];
int i=1;
while(i<str.size()){
//check if current char is the first char in res
char curr = str[i];
if(res[0]==curr){
if(str.substr(0,i)==str.substr(i,i)){
res += '*';
i+=i; continue;
}else{
res += curr;
i++; continue;
}
}else {
res += curr;
i++; continue;
}
}
return res;
}
int main()
{
string s = "ABCABCABC";
string res = getCompressed(s);
cout<<res.size();
return 0;
}

arrayListOutOfBoundsException

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 + "\"");
}
}
}
}

Categories