0

The following code is itself in an outer loop where oldChar is defined:

List<Character> modifiableCollection = new ArrayList<Character>(Arrays.asList(AlphabeticalStatistics.ALL_LETTERS));
for (int j = 0; j < modifiableCollection.size(); j++) {
    Character letterOfAlphabet = modifiableCollection.get(j);
    String loaAsString = letterOfAlphabet.toString();
    if (replaceLetters(Character.toString(oldChar), loaAsString)) {
        modifiableCollection.remove(j);
        System.out.println(oldChar + " was replaced with " + loaAsString);
        break;
    }
}

I am trying to iterate through ALL_LETTERS as efficiently as possible. For each element, I want to check if replaceLetters() on that letter and another letter. If so, the letter will be replaced and I want to remove this from the modifiableCollection so the next time this is looped it won't look to replace that letter again.

However, what I am seeing is using an enhanced for loop WITH REMOVAL, the code runs AND is more efficient. I must not remove in an enhanced for:

 for (Character letterOfAlphabet : modifiableCollection) {...remove()} // Compiles 

, but when I use a regular for loop, like above, the program takes longer to run; when I use iterator, the program takes even longer to run. Should I stick to the foreach loop?

Edit replaceLetters() method:

protected boolean replaceLetters(String toReplace, String replacement, String toAdd, String toAdd2) {
        if (replacedLetters.contains(toAdd2) || solvedLetters.contains(toAdd)) 
            return false;
        return isCorrect(toReplace, replacement, toAdd, toAdd2);
    }

private boolean isCorrect(String toReplace, String replacement, String toAdd, String toAdd2) {
    String newText = getText().replace(toReplace,  replacement);
    if (Arrays.stream(newText.split(" "))
            .filter(w -> {
                return w.contains(replacement) && AlphabeticalStatistics.needsNoLetters(w);
            })
            .noneMatch(w -> {
                return !EnglishDeterminer.isWord(w);
            })
            ) {
        setText(newText);
        solvedLetters.add(toAdd);
        replacedLetters.add(toAdd2);
        return true;
    }
    return false;
}

The purpose of this program is to decipher a substitution ciphertext.

Marvin
  • 853
  • 2
  • 14
  • 38
  • What makes you think that the for loop is a bottleneck here? I'm concerned that you convert characters to strings before passing them to the `replaceLetters()` method. If this method does replacement on strings, it most likely creates a new `String` object, which is usually not what you want if you wish to optimize string processing operations. – Forketyfork Feb 14 '20 at 22:28
  • Show us your `replaceLetters`; it's quite possible that it takes most of the time. And let's know what's the purpose of the whole code as it may possibly be done more efficiently with a different approach. – maaartinus Feb 15 '20 at 00:25

1 Answers1

1

I seems faster with the indexed for loop (for int i=0; i<size; i++). Removal is probably faster by index as well since you dont have to compare your collection values to find the match. Maybe you should extract a variable for modifiableCollection.size() since it will be evaluated each iteration.

 public static final int ITERATIONS = 100000000;

 public static void main(String[] args) {
    List<Character> alphabetList = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmopqrstuvwxyz123456789".chars()
            .mapToObj(c -> Character.valueOf((char) c)).collect(Collectors.toList());
    ArrayList<Character> copy = new ArrayList<>(alphabetList);
    List<Character> unmodifiableAlphabetList = Collections.unmodifiableList(copy);

    double timeA = checkTime(ITERATIONS, () -> {
        List<Character> modifiableCollection = new ArrayList<>(alphabetList);
        modifiableCollection.removeIf(next -> checkRemove(next));
    });
    System.out.println("A : " + timeA);

    double timeB = checkTime(ITERATIONS, () -> {
        List<Character> modifiableCollection = new ArrayList<>(alphabetList);
        Iterator<Character> iterator = modifiableCollection.iterator();
        while (iterator.hasNext()) {
            if (checkRemove(iterator.next())) {
                iterator.remove();
                break;
            }
        }
    });
    System.out.println("B : " + timeB);

    double timeC = checkTime(ITERATIONS, () -> {
        List<Character> modifiableCollection = new ArrayList<>(alphabetList);
        int size = modifiableCollection.size();
        for (int i = 0; i < size; i++) {
            Character character = unmodifiableAlphabetList.get(i);
            if (checkRemove(character)) {
                modifiableCollection.remove(i);
                break;
            }
        }
    });
    System.out.println("C : " + timeC);

    double timeD = checkTime(ITERATIONS, () -> {
        List<Character> modifiableCollection = new ArrayList<>(alphabetList);
        for (Character c : unmodifiableAlphabetList) {
            if (checkRemove(c)) {
                modifiableCollection.remove(c);
                break;
            }
        }
    });
    System.out.println("D : " + timeD);
}

private static boolean checkRemove(Character next) {
    return next.equals('W');
}

private static double checkTime(long count, Runnable fn) {
    List<Long> diffs = new ArrayList<>();

    for (int i = 0; i < count; i++) {
        long now = System.nanoTime();
        fn.run();
        long after = System.nanoTime();
        long nanoDiff = after - now;
        diffs.add(nanoDiff);
    }
    double average = diffs.stream()
            .mapToLong(l -> l)
            .average()
            .orElse(0L);
    return average;
}

A : 247.68729885
B : 83.9981085
C : 63.98897325
D : 91.69348503
cghislai
  • 1,751
  • 15
  • 29
  • Where is checkTime defined? What I do is I use `System.currentTimeMillis()` at then beginning and end of the program and subtract. – Marvin Feb 15 '20 at 00:32
  • I added the checkTime used, i guess ive done it just like you. – cghislai Feb 15 '20 at 00:38
  • For me B is greater than D – Marvin Feb 15 '20 at 00:50
  • ~Have you extracted a variable for the `size()` operation?~ Nevertheless, these are nanoseconds. Does it matter for your real world use case? – cghislai Feb 15 '20 at 01:00
  • I have pretty much solved my problem, but I have one other question: Why does the enhanced for loop work even though I am removing something from that list? – Marvin Feb 15 '20 at 01:59
  • you are breaking the loop after first removal. If you didn't, you would get a runtimeexception. You could work around that by keeping an unmodifiable list of you letters, and iterate over that one while removing from the modifable one. – cghislai Feb 15 '20 at 02:04
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/207871/discussion-between-marvin-and-cghislai). – Marvin Feb 15 '20 at 02:09