public static Map<Integer, Integer> primeFactors(int x) {
		if (primeFactorsCache == null) {
			primeFactorsCache = new HashMap<Integer, Map<Integer, Integer>>();
			System.out.println("Prefilling prime factors cache...");
			// fill cache
			for (int i = 1; i < 1000000; i++) {
				primeFactorsCache.put(i, primeFactors(i));
			}
			System.out.println("Prime factors cache filled!");
		}

		Map<Integer, Integer> result = new HashMap<Integer, Integer>();
		// terminate early if x is prime. otherwise we're just re-proving something we already know, more slowly.
		if (Primes.primeSet().contains(x)) {
			result.put(x, 1);
			return result;
		}

		// iterate through primes until we've destroyed x
		for (Integer p : Primes.primeList()) {
			if (x == 1) {
				break;
			}

			// optimization: we cache prime factorization of some small numbers
			if (x < 1000000) {
				if (primeFactorsCache.containsKey(x)) {
					Map<Integer, Integer> cached = primeFactorsCache.get(x);
					for (Integer cachedP : cached.keySet()) {
						if (result.containsKey(cachedP)) {
							result.put(cachedP, result.get(cachedP) + cached.get(cachedP));
						} else {
							result.put(cachedP, cached.get(cachedP));
						}
					}
					return result;
				}
			}

			if (x % p == 0) {
				// TODO: do exponential division
				int count = 0;
				while (x % p == 0) {
					x /= p;
					count++;
				}
				result.put(p, count);
			}
		}
		return result;
	}