Looking for shorter alternatives to `range(...)`

8

The best solution I've found so far for a golf code puzzle I'm working on includes two rather fat-looking invocations of range. I'm very new at code golf, especially in Python, so I could use a few tips.

The relevant fragment is this

[x for x in range(n+1,7**6)if`x`==`x`[::-1]*all(x%i for i in range(2,x))]

The upper limit of the first range is not a sharp one. It should be at least 98690, and all else being equal (golf-wise, that is), the smaller the difference between this upper limit and 98690 the better, performance-wise1. I'm using 76 (=117649) because 7**6 is the shortest Python expression I can come up with that fits the bill.

In contrast, the lower limit in the first range, as well as both limits in the second one, are firm. IOW, the program (in its current form) will produce incorrect results if those limits are changed.

Is there a way to shorten one or both of the expressions

range(n+1,7**6)
range(2,x)

?

BTW, in this case, aliasing range to, say, r gains nothing:

r=range;rr
rangerange

EDIT: FWIW, the full program is this:

p=lambda n:[x for x in range(n+1,7**6)if`x`==`x`[::-1]*all(x%i for i in range(2,x))][0]

p(n) should be the smallest palindromic prime greater than n. Also, p should not be recursive. Warning: It is already obscenely slow!


1Yes, I know: performance is irrelevant in code golf, but that's why I wrote "all else being equal (golf-wise, that is)". For example, my choice of 7**6, and not the more immediately obvious, but poorer-performing, "golf-equivalent" alternative 9**9. I like to actually run my code golf attempts, which means not letting the performance degrade to the point that it would take years to run the code. If I can help it, of course.

kjo

Posted 2014-11-23T19:51:39.723

Reputation: 183

1fwiw, you could make yours much faster for testing purposes by using generators; it's equivalent except for that: p=lambda n:(x for x in xrange(n+1,7**6)if`x`==`x`[::-1]*all(x%i for i in xrange(2,x))).next(). Of course, while your at that, might as well change xrange(2,x) to xrange(2,int(x**.5+1)) and make your testing really fast. Clearly this code is equivalent to yours, just longer and faster. – Justin – 2014-11-23T20:55:29.380

1It's impossible to come up with many good golf tricks with a short fragment isolated from its context. The best-golfed programs often make surprising connections between different part of the programs. For example, a seemingly useless discarded variable may prove key, or two unrelated loops combined into one. – feersum – 2014-11-23T21:09:14.740

@feersum: I posted the whole thing (in an EDIT) quite a while ago. It was before you posted your comment. Didn't you see it? – kjo – 2014-11-23T22:47:51.810

@FryAmTheEggman: yes, the statement of the puzzle is that it has to be a function, and, what's worse, the function can't be recursive. – kjo – 2014-11-23T22:50:18.080

I suspect you can save characters by directly finding the lowest prime rather than finding all of them in a range and taking the lowest one. Also, what exactly is the requirement that the function can't be recursive? Can you define f recursively, then say p=f? – xnor – 2014-11-24T00:17:25.180

@xnor:there's an automated test for this that runs with a recursion limit set to ~100 – kjo – 2014-11-24T00:44:37.707

@kjo I didn't refresh the page at that time...and now it is indeed shown one of the ranges was absolutely unnecessary for the actual program. – feersum – 2014-11-24T05:19:49.647

1

You might want to look here

– Beta Decay – 2014-11-24T07:41:14.530

Apparently this very coding challenge was already asked about on PPCG. You should look at isaacg's answer.

– xnor – 2014-11-25T05:41:12.817

Answers

7

Make it a single loop

As is, you have two loops: one iterating over x that might be palindromic primes, another iterating over i to check if x is prime by trial division. As you noticed, loops is Python take many characters, often to write range, but also to write while _: or for x in _. So, a golfed Python solution should take pains to use as few loops as possible.

feersum's comment "The best-golfed programs often make surprising connections between different part of the programs" is very applicable here. The prime-checking might seem like a separate subroutine for which all(x%i for i in range(2,x)) is the classic expression. But we'll do it another way.

The idea is to use Wilson's Theorem. For each potential prime k, we keep a running product of (k-1)!, and check is it's a multiple of k. We can keep track of (k-1)! while we test potential k to be prime palindromes by keeping a running product P.

Actually, we'll use the stronger version of Wilson's Theorem that (k-1)! % k equals 0 for composite k and only for composite numbers, except k=4 gives 2, and so (k-1)!**2 % k equals 0 exactly for composite numbers. We'll update P to equal k!**2 via the update P*=k*k.

(See this answer for this method used to find primes in Python.)

Putting it all together:

def p(n):
 k=P=1
 while(`k`!=`k`[::-1])+(k<=n)+(P%k==0):P*=k*k;k+=1
 return k

This isn't yet fully golfed -- the condition in particular is written inefficiently. We can compress the condition to check that k is a palindrome while at the time enforcing the other conditions via a chained inequality.

def p(n):
 k=P=1
 while`k`*(P%k>0>n-k)!=`k`[::-1]:P*=k*k;k+=1
 return k

xnor

Posted 2014-11-23T19:51:39.723

Reputation: 115 687

1a dazzling solution. i'm grateful to see it, even though it goes into a realm beyond my reach... Up to now my improvements in golf had all been a matter of picking up tricks like using backticks instead of str, but these tricks get one only so much... the big improvements come from better algorithms, as always. – kjo – 2014-11-24T01:18:43.097

1Following FryAmTheEggman's solution, one can rewrite the condition as \k`(k>n)(P%k>0)!=`k`[::-1]`, which shaves off 4 – kjo – 2014-11-24T01:57:34.057

1

@kjo Yes, and even more similarly if you chain the inequalities into a single one. You should also check out Python Golf Practice too see tricks like this

– xnor – 2014-11-24T04:55:10.053

that's very nicely done! thank you all! this has been a very eye-opening thread for me... the shortest solution lengths I know of, for Python 2.7 and 3.3 are, respectively, 28 and 32 characters shorter than the version I posted originally (my absolute best effort), and when I found about this i was incredulous; i thought there had to be some foul play somewhere (e.g. by reverse engineering the automated solution-testing program). but after seeing how the experts here quickly shaved up to 16 characters from my best effort, i am now more willing to believe those numbers. – kjo – 2014-11-24T14:15:00.877

@kjo I'm glad you're seeing the magic the magic of golfing. Are you saying there's a 55 char solution? If so, I'm intrigued. Is this an anarchy golf problem? Could you please link me to the problem statement? There might be shortcuts possible from gaps in the test cases, in particular the exception with the number 4. – xnor – 2014-11-24T20:59:41.457

the problem statement is here: http://www.checkio.org/mission/prime-palindrome-golf/, and the leader board is here: http://www.checkio.org/mission/prime-palindrome-golf/publications/ . The length of a solution equals 250 minus the score in the leader board. Also, note that in the original problem statement it was specified that the function be called golf (not p, as in this thread). This explains why the best Python 3.3 solution has length 58 rather than 55.

– kjo – 2014-11-24T23:44:30.320

3

AFAIK, not really.

Range is commonly used in python golfs because it is the shortest way to generate lists of increasing/decreasing numbers.

That said, it appears to be slightly (7 bytes) shorter to avoid using range and instead call a wrapped while loop:

def p(n):
    n+=1
    while`n`*all(n%i for i in range(2,n))!=`n`[::-1]:n+=1
    return n

Thanks to @xnor (as always) for improving the while condition's logic :)

FryAmTheEggman

Posted 2014-11-23T19:51:39.723

Reputation: 16 206

@kjo No trouble :) You might want to add what you told me about it having to be a non-recursive function to the question, as otherwise this answer is rather poor ;) – FryAmTheEggman – 2014-11-23T23:55:11.700

2You can save on the condition by implicitly negating it: while(\n`!=`n`[::-1])+0in(n%i for i in range(2,n)):n+=1`. I can't seem to get rid of the first pair of parens due to operator precedence issues. – xnor – 2014-11-24T00:26:34.157

2Getting rid of the parens: while\n`*all(n%i for i in range(2,n))!=`n`[::-1]:n+=1` – xnor – 2014-11-24T00:32:47.780

@FryAmTheEggman: that's very sportsmanlike of you! done – kjo – 2014-11-24T23:35:40.433

1

When using iterative algorithms such as Newton method or calculating fractals, where you usually need to perform iterations without actually caring about the index you can save some characters by iterating over short strings instead.

for i in range(4):x=f(x)
for i in'asdf':x=f(x)

At seven iterations this breaks even with range. For more iterations use backtics and large numbers

for i in`9**9**5`:pass

This runs 56349 times which should be enough for all practical purposes. Playing around with numbers and operators lets you hardcode a wide range of numbers this way.

DenDenDo

Posted 2014-11-23T19:51:39.723

Reputation: 2 811

While this is interesting, you can see quite clearly that he does care about the index (as the candidate prime is the index). I think this fits better as a part of this answer on the tips page instead :) (Also note that '1'*4 is shorter than 'asdf')

– FryAmTheEggman – 2014-11-25T18:49:47.993