4
\$\begingroup\$

I am a Spanish programming teacher in a vocational training course and we are studying arrays. In order to practice and to get the students familiarized with some methods of the String class, I proposed them to reinvent the wheel, implementing some methods of the String class using char arrays.

This is the solution that I will provide to my students. The lack of comments is because the solution will be explained in class.

package es.programacion1k.arrays;

import java.util.Arrays;

//Using char arrays from strings with the String.toCharArray () function implement the following methods
//length
//indexOf
//charAt(index)
//lastIndexOf(ch);
//replace(oldChar, newChar)
//substring(beginIndex, endIndex)
//substring(beginIndex)
//equals(anObject)
//trim();
//concat(str)
//endsWith(suffix)
//contains(s)
//indexOf(str)
//split("-");

public class MyStringMethods {

public static void main(String[] args) {
    String empty = "";
    String ordinary = "Ordinary Test String";

    System.out.printf("String Class Method - String: \"%s\" Length: %d%n", empty, empty.length());
    System.out.printf("String Class Method - String: \"%s\" Length: %d%n", ordinary, ordinary.length());
    System.out.printf("myMethod            - String: \"%s\" Length: %d%n", empty, myLength(empty));
    System.out.printf("myMethod            - String: \"%s\" Length: %d%n", ordinary, myLength(ordinary));

    char characterToFind = 'N';
    String template = "indexOf   - String: \"%s\" character: %c position: %d%n";
    String myTemplate = "myIndexOf - String: \"%s\" character: %c position: %d%n";
    System.out.printf(template, empty, characterToFind, empty.indexOf(characterToFind));
    System.out.printf(myTemplate, empty, characterToFind, myIndexOf(empty, characterToFind));
    System.out.printf(template, ordinary, characterToFind, ordinary.indexOf(characterToFind));
    System.out.printf(myTemplate, ordinary, characterToFind, myIndexOf(ordinary, characterToFind));

    int position = 14;
    template = "charAt - String: \"%s\" position: %d character: %c%n";
    myTemplate = "myCharAt - String: \"%s\" position: %d character: %c%n";
    // System.out.printf(plantilla,StringVacia,position,StringVacia.charAt(position));
    // System.out.printf(myPlantilla,StringVacia,position,myCharAt(StringVacia,position));
    System.out.printf(template, ordinary, position, ordinary.charAt(position));
    System.out.printf(myTemplate, ordinary, position, myCharAt(ordinary, position));

    characterToFind = 'e';
    template = "lastIndexOf   - String: \"%s\" character: %c position: %d%n";
    myTemplate = "myLastIndexOf - String: \"%s\" character: %c position: %d%n";
    System.out.printf(template, empty, characterToFind, empty.lastIndexOf(characterToFind));
    System.out.printf(myTemplate, empty, characterToFind, myLastIndexOf(empty, characterToFind));
    System.out.printf(template, ordinary, characterToFind, ordinary.lastIndexOf(characterToFind));
    System.out.printf(myTemplate, ordinary, characterToFind, myLastIndexOf(ordinary, characterToFind));

    char characterBefore = 'e';
    char characterAfter = 'E';
    template = "replace   - String: \"%s\" characterBefore: %c characterAfter: %c StringAfter: \"%s\"%n";
    myTemplate = "myReplace - String: \"%s\" characterBefore: %c characterAfter: %c StringAfter: \"%s\"%n";
    System.out.printf(template, empty, characterBefore, characterAfter,
            empty.replace(characterBefore, characterAfter));
    System.out.printf(myTemplate, empty, characterBefore, characterAfter,
            myReplace(empty, characterBefore, characterAfter));
    System.out.printf(template, ordinary, characterBefore, characterAfter,
            ordinary.replace(characterBefore, characterAfter));
    System.out.printf(myTemplate, ordinary, characterBefore, characterAfter,
            myReplace(ordinary, characterBefore, characterAfter));

    int startPosition = 4;
    int endPosition = 13;
    template = "subString(start,end) - String: \"%s\" start: %d end: %d Result: %s%n";
    myTemplate = "mySubString(start,end) - String: \"%s\" start: %d end: %d Result: %s%n";
    // System.out.printf(plantilla,vacia,positionInicial,positionFinal,vacia.substring(positionInicial,
    // positionFinal));
    // System.out.printf(myPlantilla,vacia,positionInicial,positionFinal,mySubstring(vacia,positionInicial,
    // positionFinal));
    System.out.printf(template, ordinary, startPosition, endPosition,
            ordinary.substring(startPosition, endPosition));
    System.out.printf(myTemplate, ordinary, startPosition, endPosition,
            mySubstring(ordinary, startPosition, endPosition));

    startPosition = 4;
    template = "subString(start) - String: \"%s\" start: %d Result: %s%n";
    myTemplate = "mySubString(start) - String: \"%s\" start: %d Result: %s%n";
    // System.out.printf(plantilla,vacia,positionInicial,vacia.substring(positionInicial));
    // System.out.printf(myPlantilla,vacia,positionInicial,mySubstring(vacia,positionInicial));
    System.out.printf(template, ordinary, startPosition, ordinary.substring(startPosition));
    System.out.printf(myTemplate, ordinary, startPosition, mySubstring(ordinary, startPosition));

    String second = "Ordinary Test String";
    template = "equals(str) - String: \"%s\" Second String: \"%s\" Result: %b%n";
    myTemplate = "myEquals(str) - String: \"%s\" Second String: \"%s\" Result: %b%n";
    System.out.printf(template, empty, second, empty.equals(second));
    System.out.printf(myTemplate, empty, second, myEquals(empty, second));
    System.out.printf(template, ordinary, second, ordinary.equals(second));
    System.out.printf(myTemplate, ordinary, second, myEquals(ordinary, second));

    String withWhiteSpaces = "  Test     ";
    template = "trim() - String: \"%s\" Resultado: \"%s\"%n";
    myTemplate = "myTrim() - String: \"%s\" Resultado: \"%s\"%n";
    System.out.printf(template, empty, empty.trim());
    System.out.printf(myTemplate, empty, myTrim(empty));
    System.out.printf(template, withWhiteSpaces, withWhiteSpaces.trim());
    System.out.printf(myTemplate, withWhiteSpaces, myTrim(withWhiteSpaces));

    String added = " more characters";
    template = "concat(str) - String: \"%s\" Added: \"%s\" Result: \"%s\"%n";
    myTemplate = "myConcat(str) - String: \"%s\" Added: \"%s\" Result: \"%s\"%n";
    System.out.printf(template, empty, added, empty.concat(added));
    System.out.printf(myTemplate, empty, added, myConcat(empty, added));
    System.out.printf(template, ordinary, added, ordinary.concat(added));
    System.out.printf(myTemplate, ordinary, added, myConcat(ordinary, added));

    String ending = "String";
    template = "endsWith(str) - String: \"%s\" ending: \"%s\" Result: \"%b\"%n";
    myTemplate = "myEndsWith(str) - String: \"%s\" ending: \"%s\" Result: \"%b\"%n";
    System.out.printf(template, empty, ending, empty.endsWith(ending));
    System.out.printf(myTemplate, empty, ending, myEndsWith(empty, ending));
    System.out.printf(template, ordinary, ending, ordinary.endsWith(ending));
    System.out.printf(myTemplate, ordinary, ending, myEndsWith(ordinary, ending));

    String toFind = "String";
    template = "indexOf(str) - String: \"%s\" tofind: \"%s\" Result: \"%d\"%n";
    myTemplate = "myIndexOf(str) - String: \"%s\" tofind: \"%s\" Result: \"%d\"%n";
    System.out.printf(template, empty, toFind, empty.indexOf(toFind));
    System.out.printf(myTemplate, empty, toFind, myIndexOf(empty,toFind));
    System.out.printf(template, ordinary, toFind, ordinary.indexOf(toFind));
    System.out.printf(myTemplate, ordinary, toFind, myIndexOf(ordinary,toFind));

    String separator = "o";
    template = "split   - String: \"%s\" character: %s%n";
    myTemplate = "mySplit - String: \"%s\" character: %s%n";
//      System.out.printf(plantilla, vacia, separador, vacia.split(separador));
//      System.out.printf(myPlantilla, vacia, characterBuscado, myIndexOf(vacia, characterBuscado));
    System.out.printf(template, ordinary, separator);

    for(String s:ordinary.split(separator)){
        System.out.println(s);
    }
    System.out.printf(myTemplate, ordinary, separator);
    for(String s:mySplit(ordinary,separator)){
        System.out.println(s);
    }
}

static int myLength(String s) {
    char[] characters = s.toCharArray();
    return characters.length;
}

static int myIndexOf(String s, char c) {
    char[] characters = s.toCharArray();
    for (int i = 0; i < characters.length; i++) {
        if (characters[i] == c) {
            return i;
        }
    }
    return -1;
}

static int myCharAt(String s, int pos) {
    char[] characters = s.toCharArray();
    return characters[pos];

}

static int myLastIndexOf(String s, char c) {
    char[] characters = s.toCharArray();
    for (int i = characters.length - 1; i >= 0; i--) {
        if (characters[i] == c) {
            return i;
        }
    }
    return -1;
}

static String myReplace(String s, char before, char after) {
    char[] characters = s.toCharArray();
    for (int i = 0; i < s.length(); i++) {
        if (characters[i] == before) {
            characters[i] = after;
        }
    }

    return new String(characters);
}

private static String mySubstring(String s, int initialPosition, int finalPosition) {
    char[] characters = s.toCharArray();
    int lengthSubString = finalPosition - initialPosition;
    char[] result = new char[lengthSubString];
    for (int i = 0; i < lengthSubString; i++) {
        result[i] = characters[i + initialPosition];
    }

    return new String(result);
}

private static String mySubstring(String String, int positionInicial) {
    return mySubstring(String, positionInicial, String.length());
}

static boolean myEquals(String first, String second) {
    char[] charactersFirst = first.toCharArray();
    char[] charactersSecond = second.toCharArray();
    if (charactersFirst.length != charactersSecond.length) {
        return false;
    }
    for (int i = 0; i < charactersFirst.length; i++) {
        if (charactersFirst[i] != charactersSecond[i]) {
            return false;
        }
    }
    return true;
}

static String myTrim(String s) {
    char[] characters = s.toCharArray();
    if (characters.length == 0) {
        return "";
    }
    int initialWhiteSpaces = 0;
    while (Character.isWhitespace(characters[initialWhiteSpaces])) {
        initialWhiteSpaces++;
    }
    int finalWhiteSpaces = characters.length - 1;
    while (Character.isWhitespace(characters[finalWhiteSpaces])) {
        finalWhiteSpaces--;
    }

    return mySubstring(s, initialWhiteSpaces, finalWhiteSpaces + 1);
}

static String myConcat(String s, String added) {
    char[] characters = s.toCharArray();
    char[] charactersAdded = added.toCharArray();

    char[] result = new char[characters.length + charactersAdded.length];
    int i;
    for (i = 0; i < characters.length; i++) {
        result[i] = characters[i];
    }
    for (int j = 0; j < charactersAdded.length; j++) {
        result[i + j] = charactersAdded[j];
    }
    return new String(result);
}

static boolean myEndsWith(String s,String ending){
    char[] characters = s.toCharArray();
    char[] endingCharacters = ending.toCharArray();
    if(characters.length<endingCharacters.length){
        return false;
    }

    for(int i=characters.length-1,j=endingCharacters.length-1;j>=0;j--,i--){
        if(characters[i]!=endingCharacters[j]){
            return false;
        }
    }
    return true;
}
static int myIndexOf(String s,String toFind){
    char[] characters = s.toCharArray();
    char[] charactersToFind = toFind.toCharArray();
    if(characters.length<charactersToFind.length){
        return -1;
    }
    int lookingAtPosition=0;
    do{
//          System.out.println("myrando: "+characters[positionmyrada]);
        int iguales=0;
        int i=lookingAtPosition;
        int j=0;
        while(j<charactersToFind.length){
//              System.out.print("entro "+i+" "+j);
            if(characters[i]!=charactersToFind[j]){
                break;
            }
            i++;
            j++;

        }
        if(j==charactersToFind.length){
//              System.out.println("Encontrado en: "+positionmyrada);
            return lookingAtPosition;
        }

        lookingAtPosition++;
    }while(lookingAtPosition<=characters.length-charactersToFind.length);
    return -1;
}
static boolean myContains(String s,String toFind){
    return myIndexOf(s,toFind)!=-1;
}
static String[] mySplit(String s,String splitter){

    char[] characters = s.toCharArray();
    char separator=splitter.charAt(0);
    int tokens=1;
    for(int i=0;i<characters.length-1;i++){
        if(characters[i]==separator){
            tokens++;
        }
    }
    char[][] result=new char[s.length()][];

    int indexString = 0;
    for (int i = 0; i < tokens; i++) {
        int indexSubString = 0;

        while (characters[indexString] != separator) {
            if (indexString == characters.length - 1) {
                indexSubString++;
                break;
            }
            indexString++;
            indexSubString++;
        }
        result[i]=new char[indexSubString];
        indexString++;

    }


    indexString = 0;
    for (int i = 0; i < tokens; i++) {
        int indiceSubString = 0;

        while (characters[indexString] != separator) {
            result[i][indiceSubString] = characters[indexString];
            if (indexString == characters.length - 1) {
                break;
            }
            indexString++;
            indiceSubString++;
        }

        indexString++;

    }

    String[] finalResult=new String[tokens];
    for(int i=0;i<tokens;i++){
        finalResult[i]=new String(result[i]);
    }
    return finalResult;
    }

}

The split function (with a character as a separator) is really difficult to implement and I do not ask the students to do it, it's just an example.

We have only studied what is used in the program.

\$\endgroup\$
1
\$\begingroup\$

Object-oriented

The first comment, is that all of the methods are static, and their names begin with my.... Java is an object-oriented language, so in order to re-implement a String class working on character arrays, it would be preferable to create a new MyString class. On that class, you can then have the typical methods length() or trim(), with a clear name (instead of having them prefixed with my).

Consider having the starting template of:

public class MyString {

    private char[] characters;

    public MyString(String str) {
        characters = str.toCharArray();
    }

    public int length() {
        // ...
    }

}

Then the rest of the code can operate on the character array alone, without interference from a String parameter to a static method. The big advantage with this approach is that you gain

Unit testing

Right now, all of the test code is written inside the main method. While this can work during development to easily test some scenarios, having a proper unit test, written with JUnit or TestNG for example, is preferable: you can create this test class on your side, and verify automatically the all of the students submissions with it.

A simple JUnit test would be

import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class MyStringTest {

    @Test
    public int testLengthOfEmptyString() {
        MyString str = new MyString("");
        assertEquals(0, str.length());
    }

    // more tests (one with a non empty string for example)

}

The methods

The currently all follow the pattern of fetching the character array from the given String parameter. When refactoring this to the MyString class having a character array field, this is simplified. Instead of

static int myLength(String s) {
    char[] characters = s.toCharArray();
    return characters.length;
}

you can directly have

public int length() {
    return characters.length;
}

Keep in mind that the methods differ slightly from the ones of the String class (charAt method returns a char instead of an int, or indexOf takes an int as parameter instead of a char, etc.), although it may not matter since the point is to learn arrays. I also noticed that some of them are private (like mySubstring); was that the intent? They should probably be made public and to be implemented by the student.

Be careful with the implementation of trim(), it does not have the same behaviour as String.trim(). For example, consider the following String having a paragraph separator (e.g. \u2029), and a tabulation character (to see the difference):

System.out.println("\u2029\tABAB".trim());  // prints "    ABAB"
System.out.println(myTrim("\u2029\tABAB")); // prints "ABAB"

This is because the rewritten trim() removes all Java white space characters as returned by isWhitespace, but the trim() built-in considers all characters below ' '.

A note regarding split as well: currently it fails if it is passed an empty String. That case should be taken care of: it could be by returning an array of all the characters, like the built-in split does, but the intent for this method is more to accept a char separator as parameter, instead of a String, because what it does is split by a single character.

The method split can be implemented in a more direct way, still reasoning on the character arrays: step 1 is to calculate the total number of tokens and step 2 is to loop over the characters, keeping the running index where a character equal to the separator is found (starting at 0) and taking the substring between that index and the next one (I used Arrays.copyOfRange in the snippet below, but that can easily be replaced with a for loop if you wish to keep it strictly without utility methods).

public String[] split(char separator) {
    int length = characters.length;
    int tokens = 0;
    for (char ch : characters) {
        if (ch == separator) {
            tokens++;
        }
    }

    String[] result = new String[tokens + 1];
    int resultIndex = 0;
    int startIndex = 0;
    for (int i = 0; i < length; i++) {
        if (characters[i] == separator) {
            result[resultIndex] = new String(Arrays.copyOfRange(characters, startIndex, i));
            startIndex = i + 1; // + 1 to skip the separator
            resultIndex++;
        }
    }
    // to take the last part into account
    result[resultIndex] = new String(Arrays.copyOfRange(characters, startIndex, length));

    return result;
}

The rest of the code is fine; just make sure to remove the unused variable (int iguales = 0;) and commented-out code in indexOf(toFind).

\$\endgroup\$
  • \$\begingroup\$ Thank you very much for your appropiate comment. It was really helpful \$\endgroup\$ – Miguel-David Jan 17 '17 at 17:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.