Skip to content

Commit 496e6ff

Browse files
committed
CommandHistory optimization
- Use LinkedList with ListIterator to make all methods except for `clear()` run in `O(1)` (constant runtime) instead of `O(n)` (linear runtime). - No longer store executed commands that are executed multiple times (executing {1, 1, 1, 1, 2} now only adds {1, 2} to the history).
1 parent 82c67b0 commit 496e6ff

File tree

1 file changed

+62
-26
lines changed

1 file changed

+62
-26
lines changed
+62-26
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,60 @@
11
package processing.app;
22

3-
import java.util.ArrayList;
4-
import java.util.List;
3+
import java.util.LinkedList;
4+
import java.util.ListIterator;
55

66
/**
77
* Keeps track of command history in console-like applications.
88
* @author P.J.S. Kools
99
*/
1010
public class CommandHistory {
1111

12-
private List<String> commandHistory = new ArrayList<String>();
13-
private int selectedCommandIndex = 0;
12+
private final LinkedList<String> commandHistory = new LinkedList<String>();
1413
private final int maxHistorySize;
14+
private ListIterator<String> iterator = null;
15+
private boolean iteratorAsc;
1516

1617
/**
1718
* Create a new {@link CommandHistory}.
1819
* @param maxHistorySize - The max command history size.
1920
*/
2021
public CommandHistory(int maxHistorySize) {
2122
this.maxHistorySize = (maxHistorySize < 0 ? 0 : maxHistorySize);
22-
this.commandHistory.add(""); // Current command placeholder.
23+
this.commandHistory.addLast(""); // Current command placeholder.
2324
}
2425

2526
/**
2627
* Adds the given command to the history and resets the history traversal
27-
* position to the latest command. If the max history size is exceeded,
28-
* the oldest command will be removed from the history.
28+
* position to the latest command. If the latest command in the history is
29+
* equal to the given command, it will not be added to the history.
30+
* If the max history size is exceeded, the oldest command will be removed
31+
* from the history.
2932
* @param command - The command to add.
3033
*/
3134
public void addCommand(String command) {
35+
if(this.maxHistorySize == 0) {
36+
return;
37+
}
38+
39+
// Remove 'current' command.
40+
this.commandHistory.removeLast();
41+
42+
// Add new command if it differs from the latest command.
43+
if(this.commandHistory.isEmpty()
44+
|| !this.commandHistory.getLast().equals(command)) {
3245

33-
// Remove the oldest command if the max history size is exceeded.
34-
if(this.commandHistory.size() >= this.maxHistorySize + 1) {
35-
this.commandHistory.remove(0);
46+
// Remove oldest command if max history size is exceeded.
47+
if(this.commandHistory.size() >= this.maxHistorySize) {
48+
this.commandHistory.removeFirst();
49+
}
50+
51+
// Add new command and reset 'current' command.
52+
this.commandHistory.addLast(command);
3653
}
3754

38-
// Add the new command, reset the 'current' command and reset the index.
39-
this.commandHistory.set(this.commandHistory.size() - 1, command);
40-
this.commandHistory.add(""); // Current command placeholder.
41-
this.selectedCommandIndex = this.commandHistory.size() - 1;
55+
// Re-add 'current' command and reset command iterator.
56+
this.commandHistory.addLast(""); // Current command placeholder.
57+
this.iterator = null;
4258
}
4359

4460
/**
@@ -47,16 +63,22 @@ public void addCommand(String command) {
4763
* returns {@code false} otherwise.
4864
*/
4965
public boolean hasNextCommand() {
50-
return this.selectedCommandIndex + 1 < this.commandHistory.size();
66+
if (this.iterator == null) {
67+
return false;
68+
}
69+
if (!this.iteratorAsc) {
70+
this.iterator.next(); // Current command, ascending.
71+
this.iteratorAsc = true;
72+
}
73+
return this.iterator.hasNext();
5174
}
5275

5376
/**
5477
* Gets the next (more recent) command from the history.
5578
* @return The next command or {@code null} if no next command is available.
5679
*/
5780
public String getNextCommand() {
58-
return this.hasNextCommand()
59-
? this.commandHistory.get(++this.selectedCommandIndex) : null;
81+
return this.hasNextCommand() ? this.iterator.next() : null;
6082
}
6183

6284
/**
@@ -65,7 +87,14 @@ public String getNextCommand() {
6587
* returns {@code false} otherwise.
6688
*/
6789
public boolean hasPreviousCommand() {
68-
return this.selectedCommandIndex > 0;
90+
if (this.iterator == null) {
91+
return this.commandHistory.size() > 1;
92+
}
93+
if (this.iteratorAsc) {
94+
this.iterator.previous(); // Current command, descending.
95+
this.iteratorAsc = false;
96+
}
97+
return this.iterator.hasPrevious();
6998
}
7099

71100
/**
@@ -86,14 +115,21 @@ public String getPreviousCommand(String currentCommand) {
86115
return null;
87116
}
88117

89-
// Store current unexecuted command if not traversing already.
90-
if (this.selectedCommandIndex == this.commandHistory.size() - 1) {
91-
this.commandHistory.set(this.commandHistory.size() - 1,
92-
(currentCommand == null ? "" : currentCommand));
118+
// Store current unexecuted command and create iterator if not traversing.
119+
if (this.iterator == null) {
120+
this.iterator =
121+
this.commandHistory.listIterator(this.commandHistory.size());
122+
this.iterator.previous(); // Last element, descending.
123+
this.iteratorAsc = false;
124+
}
125+
126+
// Store current unexecuted command if on 'current' index.
127+
if (this.iterator.nextIndex() == this.commandHistory.size() - 1) {
128+
this.iterator.set(currentCommand == null ? "" : currentCommand);
93129
}
94130

95131
// Return the previous command.
96-
return this.commandHistory.get(--this.selectedCommandIndex);
132+
return this.iterator.previous();
97133
}
98134

99135
/**
@@ -103,16 +139,16 @@ public String getPreviousCommand(String currentCommand) {
103139
* was set.
104140
*/
105141
public String resetHistoryLocation() {
106-
this.selectedCommandIndex = this.commandHistory.size() - 1;
142+
this.iterator = null;
107143
return this.commandHistory.set(this.commandHistory.size() - 1, "");
108144
}
109145

110146
/**
111147
* Clears the command history.
112148
*/
113149
public void clear() {
150+
this.iterator = null;
114151
this.commandHistory.clear();
115-
this.commandHistory.add(""); // Current command placeholder.
116-
this.selectedCommandIndex = 0;
152+
this.commandHistory.addLast(""); // Current command placeholder.
117153
}
118154
}

0 commit comments

Comments
 (0)