From 1d21f0cca3a80c79aac561ee9b20c35081b53cb3 Mon Sep 17 00:00:00 2001
From: Pieter12345
Date: Sat, 30 Mar 2019 23:16:19 +0100
Subject: [PATCH] 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).
---
app/src/processing/app/CommandHistory.java | 105 +++++++++++++++------
1 file changed, 77 insertions(+), 28 deletions(-)
diff --git a/app/src/processing/app/CommandHistory.java b/app/src/processing/app/CommandHistory.java
index 25ded6f1fb9..cae3c2fc498 100644
--- a/app/src/processing/app/CommandHistory.java
+++ b/app/src/processing/app/CommandHistory.java
@@ -1,7 +1,7 @@
package processing.app;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.LinkedList;
+import java.util.ListIterator;
/**
* Keeps track of command history in console-like applications.
@@ -9,9 +9,10 @@
*/
public class CommandHistory {
- private List commandHistory = new ArrayList();
- private int selectedCommandIndex = 0;
+ private final LinkedList commandHistory = new LinkedList();
private final int maxHistorySize;
+ private ListIterator iterator = null;
+ private boolean iteratorAsc;
/**
* Create a new {@link CommandHistory}.
@@ -19,26 +20,41 @@ public class CommandHistory {
*/
public CommandHistory(int maxHistorySize) {
this.maxHistorySize = (maxHistorySize < 0 ? 0 : maxHistorySize);
- this.commandHistory.add(""); // Current command placeholder.
+ this.commandHistory.addLast(""); // Current command placeholder.
}
/**
* Adds the given command to the history and resets the history traversal
- * position to the latest command. If the max history size is exceeded,
- * the oldest command will be removed from the history.
+ * position to the latest command. If the latest command in the history is
+ * equal to the given command, it will not be added to the history.
+ * If the max history size is exceeded, the oldest command will be removed
+ * from the history.
* @param command - The command to add.
*/
public void addCommand(String command) {
+ if (this.maxHistorySize == 0) {
+ return;
+ }
+
+ // Remove 'current' command.
+ this.commandHistory.removeLast();
+
+ // Add new command if it differs from the latest command.
+ if (this.commandHistory.isEmpty()
+ || !this.commandHistory.getLast().equals(command)) {
+
+ // Remove oldest command if max history size is exceeded.
+ if (this.commandHistory.size() >= this.maxHistorySize) {
+ this.commandHistory.removeFirst();
+ }
- // Remove the oldest command if the max history size is exceeded.
- if(this.commandHistory.size() >= this.maxHistorySize + 1) {
- this.commandHistory.remove(0);
+ // Add new command and reset 'current' command.
+ this.commandHistory.addLast(command);
}
- // Add the new command, reset the 'current' command and reset the index.
- this.commandHistory.set(this.commandHistory.size() - 1, command);
- this.commandHistory.add(""); // Current command placeholder.
- this.selectedCommandIndex = this.commandHistory.size() - 1;
+ // Re-add 'current' command and reset command iterator.
+ this.commandHistory.addLast(""); // Current command placeholder.
+ this.iterator = null;
}
/**
@@ -47,7 +63,14 @@ public void addCommand(String command) {
* returns {@code false} otherwise.
*/
public boolean hasNextCommand() {
- return this.selectedCommandIndex + 1 < this.commandHistory.size();
+ if (this.iterator == null) {
+ return false;
+ }
+ if (!this.iteratorAsc) {
+ this.iterator.next(); // Current command, ascending.
+ this.iteratorAsc = true;
+ }
+ return this.iterator.hasNext();
}
/**
@@ -55,8 +78,20 @@ public boolean hasNextCommand() {
* @return The next command or {@code null} if no next command is available.
*/
public String getNextCommand() {
- return this.hasNextCommand()
- ? this.commandHistory.get(++this.selectedCommandIndex) : null;
+
+ // Return null if there is no next command available.
+ if (!this.hasNextCommand()) {
+ return null;
+ }
+
+ // Get next command.
+ String next = this.iterator.next();
+
+ // Reset 'current' command when at the end of the list.
+ if (this.iterator.nextIndex() == this.commandHistory.size()) {
+ this.iterator.set(""); // Reset 'current' command.
+ }
+ return next;
}
/**
@@ -65,15 +100,22 @@ public String getNextCommand() {
* returns {@code false} otherwise.
*/
public boolean hasPreviousCommand() {
- return this.selectedCommandIndex > 0;
+ if (this.iterator == null) {
+ return this.commandHistory.size() > 1;
+ }
+ if (this.iteratorAsc) {
+ this.iterator.previous(); // Current command, descending.
+ this.iteratorAsc = false;
+ }
+ return this.iterator.hasPrevious();
}
/**
* Gets the previous (older) command from the history.
* When this method is called while the most recent command in the history is
* selected, this will store the current command as temporary latest command
- * so that {@link #getNextCommand()} will return it. This temporary latest
- * command gets reset when this case occurs again or when
+ * so that {@link #getNextCommand()} will return it once. This temporary
+ * latest command gets reset when this case occurs again or when
* {@link #addCommand(String)} is invoked.
* @param currentCommand - The current unexecuted command.
* @return The previous command or {@code null} if no previous command is
@@ -86,14 +128,21 @@ public String getPreviousCommand(String currentCommand) {
return null;
}
- // Store current unexecuted command if not traversing already.
- if (this.selectedCommandIndex == this.commandHistory.size() - 1) {
- this.commandHistory.set(this.commandHistory.size() - 1,
- (currentCommand == null ? "" : currentCommand));
+ // Store current unexecuted command and create iterator if not traversing.
+ if (this.iterator == null) {
+ this.iterator =
+ this.commandHistory.listIterator(this.commandHistory.size());
+ this.iterator.previous(); // Last element, descending.
+ this.iteratorAsc = false;
+ }
+
+ // Store current unexecuted command if on 'current' index.
+ if (this.iterator.nextIndex() == this.commandHistory.size() - 1) {
+ this.iterator.set(currentCommand == null ? "" : currentCommand);
}
// Return the previous command.
- return this.commandHistory.get(--this.selectedCommandIndex);
+ return this.iterator.previous();
}
/**
@@ -103,7 +152,7 @@ public String getPreviousCommand(String currentCommand) {
* was set.
*/
public String resetHistoryLocation() {
- this.selectedCommandIndex = this.commandHistory.size() - 1;
+ this.iterator = null;
return this.commandHistory.set(this.commandHistory.size() - 1, "");
}
@@ -111,8 +160,8 @@ public String resetHistoryLocation() {
* Clears the command history.
*/
public void clear() {
+ this.iterator = null;
this.commandHistory.clear();
- this.commandHistory.add(""); // Current command placeholder.
- this.selectedCommandIndex = 0;
+ this.commandHistory.addLast(""); // Current command placeholder.
}
}