Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to generate a list of palindromes without a check

I'm working on a problem where I'm required to manipulate large lists of palindromes up to a certain number of digits. This should work with numbers up 15 digits. The most common method I've seen for this is iterating through each number and checking whether each is a palindrome and then adding that to a list. This is my implementation in java and it works fine.

public class Palindrome {

public ArrayList<Long> List = new ArrayList<Long>();
public double d;

public Palindrome(double digits) {
    this.d = digits;

    long dig = (int)(Math.pow(10,digits));

    for (long i = 1; i <= dig; i++) {

        long a = i;
        long b = inverse(a);
        if (a == b) {
            List.add(a);
        }
    }

public long inverse(long x){
    long inv = 0;
    while (x > 0) {
        inv = inv * 10 + x % 10;
        x = x / 10;
    }
    return inv;
}

}

Only problem is it's pretty slow when I get to 10+ digit palindromes. I've been considering alternative ways to create this list and one consideration I've had is generating the list of palindromes rather than iterating through each number and checking if it's a palindrome.

I'm still working on paper but the pattern isn't as obvious as I thought I would find it to turn into pseudocode. I'm working it out that for n number of digits, going from i to n, if the number of digits is even, generate numbers from 1 up to [10^(i/2 + 1) - 1]. Then append the reverse of each number to itself. A little stuck on how to do it for the odd digits. That's where I am right now.

I will come back with my own response if I figure this out and implement the code but in the meantime, I would just like to know if anyone has done this before or has an alternative method I've overlooked that would be more efficient.

UPDATE

So I did manage to work out something thanks to all your suggestions. I decided to work with the numbers as strings but contrary to what I intended this has actually increased the runtime :/

public class Palindrome2 {
public ArrayList<Long> List = new ArrayList<Long>();
public double d;

public Palindrome2(double digits) {
    this.d = digits;

    for (long n = 1; n <= d; n++) {
        if (n == 1) {
            for (long i = 1; i < 10; i++) {
                List.add(i);
            }
        }

        if (n % 2 != 0 && n != 1) {
            long max = (long) Math.pow(10, (n + 1) / 2);
            long min = (long) Math.pow(10, Math.floor(n / 2));

            for (long i = min; i < max; i++) {
                String str = Long.toString(i);
                str = str + removeFirst(reverse(str));
                Long x = Long.parseLong(str);
                List.add(x);
            }
        } else if (n % 2 == 0) {
            long max = (long) (Math.pow(10, Math.floor((n + 1) / 2)) - 1);
            long min = (long) Math.pow(10, (n / 2) - 1);

            for (long i = min; i <= max; i++) {
                String str = Long.toString(i);
                str = str + reverse(str);
                Long x = Long.parseLong(str);
                List.add(x);
            }
        }
    }
}

public String reverse(String x) {
    String rev = new StringBuffer(x).reverse().toString();
    return rev;
}

public String removeFirst(String x) {
    return x.substring(1);
}

}

Once again, accurate but still slow :(

like image 882
Sithe Avatar asked Oct 30 '22 07:10

Sithe


1 Answers

Introduction

You need to analyzing the regular pattern for an algorithm roughly before jump into developing, that will saving lot of time, for example:

each 1 digit is 1 palindrome, e.g: 1
each 2 digits has 1 palindrome, e.g: 11.

each 3 digits has 10 palindromes, e.g: 101,111,...,191.
each 4 digits has 10 palindromes, e.g: 1001, 1111, ..., 1991.

each 5 digits has 100 palindromes, e.g: 10001, 11011, ..., 19091, ..., 19991.
each 6 digits has 100 palindromes, e.g: 100001, 110011, ..., 190091, ..., 199991.

each 7 digits has 1000 palindromes, e.g: 1000001, ...,1900091,...,1090901, ..., 1999991.
each 8 digits has 1000 palindromes, e.g: 10000001, ...,19000091,...,10900901, ..., 19999991.

....

then you can write some arrangement algorithm to implement this .

Implementation

But I can tell you this implementation can optimizing as further, if you using a cache to saving palindromes generated from low digits palindromes(2), then any high digits palindromes(n>2) can reusing it.

Maybe it's not robust but it pass all my tests on github. I left the rest working & optimization to you, and I wish you can done by yourself.

private static List<Integer> palindromes(int digits) {
    return palindromes(digits, 0);
}

private static List<Integer> palindromes(int digits, int shifts) {
    List<Integer> result = new ArrayList<>();
    int radix = (int) Math.pow(10, digits - 1);
    int renaming = digits - 2;
    boolean hasRenaming = renaming > 0;
    for (int i = start(digits, shifts); i <= 9; i++) {
        int high = i * radix;
        int low = low(digits, i);
        if (hasRenaming) {
            for (Integer m : palindromes(renaming, shifts + 1)) {
                int ret = high + m * 10 + low;
                if (ret < 0) {
                    return result;
                }
                result.add(ret);
            }
        } else {
            result.add(high + low);
        }
    }
    return result;
}

private static int low(int digits, int high) {
    return digits > 1 ? high : 0;
}

private static int start(int digits, int shifts) {
    return digits > 1 && shifts == 0 ? 1 : 0;
}

Usage

then you can collect all palindrome numbers as below:

           //  v--- min:0, max: 2147447412, count: 121474
List<Integer> all = IntStream.rangeClosed(1, 10)
            .mapToObj(PalindromeTest::palindromes)
            .flatMap(List::stream)
            .collect(Collectors.toList());

Time Cost:

191ms

Enable Caching

public class Palindromes {
    private static final int[] startingNonZerosTable = {
            0,// 0
            0, 1,// 1 2
            10, 10,//3 4
            100, 100, //5 6
            1000, 1000,//7 8
            10000, 10000,// 9 10
            100000, 100000,//11 12
            1000000, 1000000,//13 14
            10000000, 10000000,//15 16
            100000000, 100000000,//17 18
            1000000000, 1000000000//19 20
    };

    private static final int MAX_DIGIT = 9;
    private static final int MIN_DIGIT = 0;
    private static final int RADIX = MAX_DIGIT - MIN_DIGIT + 1;
    private static final int LONG_MAX_DIGITS = 19;
    private static volatile long[][] cache = new long[LONG_MAX_DIGITS + 1][];
    //                                      includes palindromes(0)  ---^

    static {
        cache[0] = new long[0];
        cache[1] = new long[]{0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L};
        cache[2] = new long[]{0L, 11L, 22L, 33L, 44L, 55L, 66L, 77L, 88L, 99L};
    }

    public static LongStream since1(int end) {
        return between(1, end);
    }

    public static LongStream between(int start, int end) {
        return IntStream.rangeClosed(start, end)
                        .mapToObj(Palindromes::of)
                        .flatMapToLong(identity());
    }

    public static LongStream of(int digits) {
        return Arrays.stream(palindromes0(digits))
                     .skip(startingNonZerosTable[digits]);
    }

    private final static long[] palindromes0(int digits) {
        if (cache[digits] != null) {
            return cache[digits];
        }

        long[] result = new long[sizeOf(digits)];
        int size = 0;
        long high = (long) Math.pow(RADIX, digits - 1);

        for (int i = MIN_DIGIT; i <= MAX_DIGIT; i++) {
            for (long mid : palindromes0(digits - 2)) {
                long value = i * high + mid * RADIX + i;
                if (value < 0) {//overflow
                    return cache[digits] = Arrays.copyOf(result, size);
                }
                result[size++] = value;
            }
        }
        return cache[digits] = result;
    }

    private static int sizeOf(int digits) {
        return MAX_DIGIT * (int) Math.pow(RADIX, (digits - 1) >>> 1)
                + startingNonZerosTable[digits];
    }

    //                  v--- java -Xms1024m -Xmx2048m test.algorithm.Palindromes
    public static void main(String[] args) {
        Duration duration = timing(() -> {
                         // palindromes[1..15] ---v
            LongSummaryStatistics result = since1(15).summaryStatistics();
            long max = result.getMax();
            long count = result.getCount();

            System.out.printf("Max: %d, Count: %d%n", max, count);
        });

        System.out.printf("Time Elapsed:%s%n", duration);
                                      // ^--- time elapsed: 4s 
    }

    private static Duration timing(Runnable task) {
        long starts = System.currentTimeMillis();
        task.run();
        return Duration.ofMillis(System.currentTimeMillis() - starts);
    }
}

Time Cost:

palindromes[1..15] time elapsed: 4s

like image 132
holi-java Avatar answered Nov 15 '22 06:11

holi-java