Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit fe75b82

Browse files
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 fe75b82

File tree

1 file changed

+77
-28
lines changed

1 file changed

+77
-28
lines changed
Lines changed: 77 additions & 28 deletions
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)) {
45+
46+
// Remove oldest command if max history size is exceeded.
47+
if (this.commandHistory.size() >= this.maxHistorySize) {
48+
this.commandHistory.removeFirst();
49+
}
3250

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);
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,35 @@ 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+
82+
// Return null if there is no next command available.
83+
if (!this.hasNextCommand()) {
84+
return null;
85+
}
86+
87+
// Get next command.
88+
String next = this.iterator.next();
89+
90+
// Reset 'current' command when at the end of the list.
91+
if (this.iterator.nextIndex() == this.commandHistory.size()) {
92+
this.iterator.set(""); // Reset 'current' command.
93+
}
94+
return next;
6095
}
6196

6297
/**
@@ -65,15 +100,22 @@ public String getNextCommand() {
65100
* returns {@code false} otherwise.
66101
*/
67102
public boolean hasPreviousCommand() {
68-
return this.selectedCommandIndex > 0;
103+
if (this.iterator == null) {
104+
return this.commandHistory.size() > 1;
105+
}
106+
if (this.iteratorAsc) {
107+
this.iterator.previous(); // Current command, descending.
108+
this.iteratorAsc = false;
109+
}
110+
return this.iterator.hasPrevious();
69111
}
70112

71113
/**
72114
* Gets the previous (older) command from the history.
73115
* When this method is called while the most recent command in the history is
74116
* selected, this will store the current command as temporary latest command
75-
* so that {@link #getNextCommand()} will return it. This temporary latest
76-
* command gets reset when this case occurs again or when
117+
* so that {@link #getNextCommand()} will return it once. This temporary
118+
* latest command gets reset when this case occurs again or when
77119
* {@link #addCommand(String)} is invoked.
78120
* @param currentCommand - The current unexecuted command.
79121
* @return The previous command or {@code null} if no previous command is
@@ -86,14 +128,21 @@ public String getPreviousCommand(String currentCommand) {
86128
return null;
87129
}
88130

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));
131+
// Store current unexecuted command and create iterator if not traversing.
132+
if (this.iterator == null) {
133+
this.iterator =
134+
this.commandHistory.listIterator(this.commandHistory.size());
135+
this.iterator.previous(); // Last element, descending.
136+
this.iteratorAsc = false;
137+
}
138+
139+
// Store current unexecuted command if on 'current' index.
140+
if (this.iterator.nextIndex() == this.commandHistory.size() - 1) {
141+
this.iterator.set(currentCommand == null ? "" : currentCommand);
93142
}
94143

95144
// Return the previous command.
96-
return this.commandHistory.get(--this.selectedCommandIndex);
145+
return this.iterator.previous();
97146
}
98147

99148
/**
@@ -103,16 +152,16 @@ public String getPreviousCommand(String currentCommand) {
103152
* was set.
104153
*/
105154
public String resetHistoryLocation() {
106-
this.selectedCommandIndex = this.commandHistory.size() - 1;
155+
this.iterator = null;
107156
return this.commandHistory.set(this.commandHistory.size() - 1, "");
108157
}
109158

110159
/**
111160
* Clears the command history.
112161
*/
113162
public void clear() {
163+
this.iterator = null;
114164
this.commandHistory.clear();
115-
this.commandHistory.add(""); // Current command placeholder.
116-
this.selectedCommandIndex = 0;
165+
this.commandHistory.addLast(""); // Current command placeholder.
117166
}
118167
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /