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 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 dates) { List 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 dates) { Set seen = new HashSet<>(); List 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 generateDates(int n) { List 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 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 testDates = new ArrayList<>(); testDates.add("20260101"); testDates.add("20260102"); testDates.add("20260101"); // duplicate testDates.add("20260103"); List defStore = new ArrayList<>(); for (String d : testDates) appendDefective(defStore, d); Set fixSeen = new HashSet<>(); List 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 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); } }