112 lines
4.2 KiB
Java
112 lines
4.2 KiB
Java
import java.util.*;
|
|
|
|
/**
|
|
* Models evolution-0001: GNOME Evolution e-date-time-list.c
|
|
*
|
|
* e_date_time_list_append() deduplicates exception dates (EXDATE) using
|
|
* g_list_find_custom() on a GList. When loading a recurring calendar event
|
|
* with E exception dates, the fill loop calls append E times, each scanning
|
|
* up to E-1 entries already in the list: O(E^2) comparisons total.
|
|
*
|
|
* Fix: maintain a parallel GHashTable keyed on ISO date string for O(1)
|
|
* membership test. Append stays O(1) amortized; fill loop is O(E) total.
|
|
*/
|
|
public class EDateTimeListTest {
|
|
|
|
// --- Defective model: GList linear scan on each append ---
|
|
static int appendDefective(List<String> list, String date) {
|
|
for (String existing : list) { // O(N) scan
|
|
if (existing.equals(date)) return 0; // duplicate found
|
|
}
|
|
list.add(date);
|
|
return 1;
|
|
}
|
|
|
|
static long fillExceptionsDefective(List<String> dates) {
|
|
List<String> store = new ArrayList<>();
|
|
long ops = 0;
|
|
for (String d : dates) {
|
|
ops += store.size(); // scan cost
|
|
appendDefective(store, d);
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
// --- Fixed model: HashSet for O(1) membership ---
|
|
static long fillExceptionsFixed(List<String> dates) {
|
|
Set<String> seen = new HashSet<>();
|
|
List<String> store = new ArrayList<>();
|
|
long ops = 0;
|
|
for (String d : dates) {
|
|
ops += 1; // O(1) hash lookup
|
|
if (seen.add(d)) store.add(d);
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
static List<String> generateDates(int n) {
|
|
List<String> dates = new ArrayList<>();
|
|
for (int i = 0; i < n; i++) {
|
|
// simulate EXDATE strings like 20260101, 20260102, ...
|
|
int day = i % 28 + 1;
|
|
int month = (i / 28) % 12 + 1;
|
|
int year = 2026 + (i / 336);
|
|
dates.add(String.format("%04d%02d%02d", year, month, day));
|
|
}
|
|
return dates;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("evolution-0001: e-date-time-list EXDATE dedup O(E^2) vs O(E)");
|
|
System.out.println("=============================================================");
|
|
|
|
int[] sizes = {50, 100, 200, 500, 1000};
|
|
|
|
for (int n : sizes) {
|
|
List<String> dates = generateDates(n);
|
|
|
|
long tDefStart = System.nanoTime();
|
|
long opsDefective = fillExceptionsDefective(new ArrayList<>(dates));
|
|
long tDefEnd = System.nanoTime();
|
|
|
|
long tFixStart = System.nanoTime();
|
|
long opsFixed = fillExceptionsFixed(new ArrayList<>(dates));
|
|
long tFixEnd = System.nanoTime();
|
|
|
|
long timeDefective = tDefEnd - tDefStart;
|
|
long timeFixed = tFixEnd - tFixStart;
|
|
double ratio = (double) opsDefective / opsFixed;
|
|
|
|
System.out.printf("E=%4d: defective=%6d ops (%6.3f ms) | fixed=%6d ops (%6.3f ms) | ratio=%.1fx%n",
|
|
n,
|
|
opsDefective, timeDefective / 1_000_000.0,
|
|
opsFixed, timeFixed / 1_000_000.0,
|
|
ratio);
|
|
}
|
|
|
|
// Correctness: verify both produce same unique list
|
|
List<String> testDates = new ArrayList<>();
|
|
testDates.add("20260101");
|
|
testDates.add("20260102");
|
|
testDates.add("20260101"); // duplicate
|
|
testDates.add("20260103");
|
|
|
|
List<String> defStore = new ArrayList<>();
|
|
for (String d : testDates) appendDefective(defStore, d);
|
|
|
|
Set<String> fixSeen = new HashSet<>();
|
|
List<String> fixStore = new ArrayList<>();
|
|
for (String d : testDates) { if (fixSeen.add(d)) fixStore.add(d); }
|
|
|
|
assert defStore.equals(fixStore) : "Mismatch: " + defStore + " vs " + fixStore;
|
|
System.out.println("\nCorrectness: PASS (both produce same deduplicated list)");
|
|
|
|
// Verify ratio at E=1000
|
|
List<String> bigDates = generateDates(1000);
|
|
long bigDef = fillExceptionsDefective(new ArrayList<>(bigDates));
|
|
long bigFix = fillExceptionsFixed(new ArrayList<>(bigDates));
|
|
double bigRatio = (double) bigDef / bigFix;
|
|
assert bigRatio > 200 : "Expected >200x ratio at E=1000, got " + bigRatio;
|
|
System.out.printf("Speedup at E=1000: %.0fx -- PASS%n", bigRatio);
|
|
}
|
|
}
|