Introduction
Suppose we have a dynamic table \$T\$. Let \$\vert T \vert\$ denote the number of elements currently stored in \$T\$, and \$\vert \vert T \vert \vert\$ denote the capacity of \$T\$. Also, suppose we are given two parameters for \$T\$: \$m \in \mathbb{N}\$ is the initial (minimum) capacity; \$T\$ can't be contracted below \$m\$. Also, we are given a \$d \in \mathbb{N}\$: when \$T\$ ends up full (\$\vert T \vert = \vert \vert T \vert \vert\$), the table is expanded by \$d\$ elements. This is expansion, yet we want to investigate contraction that happens when there emerges \$d\$ free element spots in the tail of the \$T\$ as the list is popped from the back.
Here, my claim is that the work done for removing all \$n\$ elements runs in the following number of steps $$ W(n, m, d) = n + \sum_{i = 0}^{\big\lceil \frac{n - m}{d} \big\rceil - 1} (di + m) = \begin{cases} W(n - 1, m, d) + n & \text{if } (n - 1 - m) \mod d = 0, \\ & \\ W(n - 1, m, d) + 1 & \text{otherwise.} \end{cases} $$
Code
Array.java:
public final class Array {
private final int m;
private final int d;
private int capacity;
private int size = 0;
private int expansions = 0;
public Array(int m, int d) {
this.m = m;
this.d = d;
this.capacity = m;
}
public Array(Array array) {
this.m = array.m;
this.d = array.d;
this.capacity = array.capacity;
this.expansions = array.expansions;
this.size = array.size;
}
public int getM() {
return m;
}
public int getD() {
return d;
}
public int add() {
if (isFull()) {
capacity += d;
size++;
return m + 1 + d * (expansions++);
}
size++;
return 1;
}
public int add(int k) {
int work = 0;
for (int i = 0; i < k; i++) {
work += add();
}
return work;
}
public int remove() {
if (size > m && shouldContract()) {
capacity -= d;
size--;
return m + 1 + (--expansions) * d;
} else {
size--;
return 1;
}
}
public int remove(int k) {
int count = 0;
for (int i = 0, end = Math.min(size, k); i < end; i++) {
count += remove();
}
return count;
}
public int size() {
return size;
}
public int getCapacity() {
return capacity;
}
public boolean canContract(int n) {
return (n - m - 1) % d == 0;
}
boolean canContract() {
return Array.this.canContract(size);
}
public int discharge() {
return remove(size);
}
public int getWork(int n) {
return n + getSum(n);
}
public int getWorkRecursively(int n) {
if (n == 0) {
return 0;
}
int x = n - 1 - m;
return (x % d == 0 ? n : 1) + getWorkRecursively(n - 1);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size; i++) {
if (i > 0 && (i - m) % d == 0) {
sb.append("|");
}
sb.append("X");
}
for (int i = size; i < getCurrentCapacity(); i++) {
sb.append(".");
}
return sb.toString();
}
private int getSum(int n) {
int sum = 0;
int endIndex = (int)(Math.ceil((double)(n - m) /
(double)(d)
)
) - 1;
int startIndex = 0;
for (int i = startIndex; i <= endIndex; i++) {
sum += d * i + m;
}
return sum;
}
private int getCurrentCapacity() {
return m + d * expansions;
}
private boolean shouldContract() {
return getCurrentCapacity() - size == d - 1;
}
private boolean isFull() {
return size == capacity;
}
}
ArrayContraction.java:
import java.util.Scanner;
public class ArrayContraction {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Array array = new Array(3, 3);
while (true) {
try {
System.out.print(">>> ");
String line = scanner.nextLine().trim();
String[] tokens = line.split("\\s+");
if (tokens[0].equals("new")) {
int m = Integer.parseInt(tokens[1]);
int d = Integer.parseInt(tokens[2]);
array = new Array(m, d);
System.out.println(array);
} else if (tokens[0].equals("print")) {
System.out.println(array);
} else if (tokens[0].equals("add")) {
int k = tokens.length > 1 ? Integer.parseInt(tokens[1]) : 1;
int work = array.add(k);
System.out.printf("Work: %d.\n", work);
System.out.println(array);
} else if (tokens[0].equals("cond")) {
int n = tokens.length > 1 ?
Integer.parseInt(tokens[1]) :
array.size();
System.out.println(array.canContract(n));
} else if (tokens[0].equals("remove")) {
int k = tokens.length > 1 ? Integer.parseInt(tokens[1]) : 1;
int work = array.remove(k);
System.out.printf("Work: %d.\n", work);
System.out.println(array);
} else if (tokens[0].equals("alln")) {
int workClosedForm = array.getWork(array.size());
int workRecursive = array.getWorkRecursively(array.size());
Array arr = new Array(array);
int workDischarge = arr.discharge();
System.out.printf(
"Closed form work: " +
"%d, recursive: %d, discharged: %d.\n",
workClosedForm,
workRecursive,
workDischarge);
} else if (tokens[0].equals("work")) {
int n = Integer.parseInt(tokens[1]);
System.out.println(array.getWork(n));
} else if (tokens[0].equals("rwork")) {
int n = Integer.parseInt(tokens[1]);
System.out.println(array.getWorkRecursively(n));
} else if (tokens[0].equals("discharge")) {
Array other = new Array(array);
System.out.println(other.discharge());
} else if (tokens[0].equals("exit")
|| tokens[0].equals("quit")) {
System.out.println("Bye!");
System.exit(0);
} else if (tokens[0].equals("help")) {
System.out.println(
"""
HELP MESSAGE:
add - Add an element.
add k - Add k elements.
alln - Print the total works.
cond - Delegates to 'cond size'.
cond n - Print condition for n.
discharge - Discharge the entire array.
exit - Halt this program.
new m d - Create new array.
print - Print the array.
quit - Halt this program.
remove - Remove an element.
remove k - Remove k elements.
rwork n - Get work recursively for n parameter.
work n - Get work for n parameter.
help - Print this help message.
""");
}
} catch (Exception ex) {
System.out.printf("Error: %s.\n", ex.getMessage());
}
}
}
}
ArrayTest.java:
import org.junit.Test;
import static org.junit.Assert.*;
public class ArrayTest {
private Array array;
@Test
public void testAdd_0args() {
array = new Array(2, 3);
assertEquals(0, array.size());
assertEquals(2, array.getCapacity());
assertEquals(1, array.add());
assertEquals(1, array.size());
assertEquals(2, array.getCapacity());
assertEquals(1, array.add());
assertEquals(2, array.size());
assertEquals(2, array.getCapacity());
assertEquals(3, array.add());
assertEquals(3, array.size());
assertEquals(5, array.getCapacity());
}
@Test
public void testAdd_int() {
array = new Array(2, 3);
assertEquals(2, array.add(2));
assertEquals(2, array.size());
assertEquals(4, array.add(2));
assertEquals(4, array.size());
assertEquals(5, array.getCapacity());
}
@Test
public void testRemove_0args() {
array = new Array(2, 3);
array.add(8);
assertFalse(array.canContract());
assertEquals(1, array.remove());
assertEquals(7, array.size());
assertFalse(array.canContract());
assertEquals(1, array.remove());
assertEquals(6, array.size());
assertTrue(array.canContract());
assertEquals(6, array.remove());
assertEquals(5, array.size());
assertFalse(array.canContract());
assertEquals(1, array.remove());
assertEquals(4, array.size());
assertFalse(array.canContract());
assertEquals(1, array.remove());
assertEquals(3, array.size());
assertTrue(array.canContract());
assertEquals(3, array.remove());
assertEquals(2, array.size());
assertFalse(array.canContract());
assertEquals(1, array.remove());
assertEquals(1, array.size());
assertFalse(array.canContract());
assertEquals(1, array.remove());
assertEquals(0, array.size());
}
@Test
public void testRemove_int() {
array = new Array(2, 3);
array.add(8);
// Here, array = xx|xxx|xxx
assertEquals(1 + 1 + 6, array.remove(3));
assertEquals(1 + 1 + 3, array.remove(3));
assertEquals(1 + 1, array.remove(2));
}
@Test
public void testDischargeGetWork() {
array = new Array(2, 3);
array.add(100);
for (int i = 0; i < array.size(); i++) {
Array aux = new Array(array.getM(),
array.getD());
aux.add(i);
assertEquals(array.getWork(i), aux.discharge());
assertEquals(array.getWork(i), array.getWorkRecursively(i));
}
}
}
Demo REPL session
>>> new 2 3
..
>>> add 8
Work: 15.
XX|XXX|XXX
>>> alln
Closed form work: 15, recursive: 15, discharged: 15.
>>> work 7
14
>>> rwork 7
14
>>> remove 4
Work: 9.
XX|XX.
>>> alln
Closed form work: 6, recursive: 6, discharged: 6.
>>>
Critique request
As always, tell me whatever comes to mind.