Reducing a Python Script

4

Does anyone have any suggestions as to how I could shorten this program?

_=map(ord,raw_input());_=[10if y==88else 0if y==45else 58-_[x-1]if y==47else y-48for x,y in enumerate(_)];t=0;exec('t+=sum(_[:2+(_[0]+_[1]>=10)]);_=_[2-(_[0]==10):];'*10);print t

Run it online

I'd like to write a bowling calculator. I'm fairly proficient in python so I chose that language.

The trick is that the input string is made up of frames not split by white-space and the spares, strikes, and misses are marked by /, X, and - respectively. (note that there are no 0 in the code, - is used instead)

Here is an example line X7/9-X-88/-6XXX81, this line would have a score of 167.

As you can see, most of my characters are spent on mapping these special characters to their actual bowl scores. I do this by taking their ord values and then replacing the X and - with 10 and 0 respectively. I then find the / rolls and set their value as ten minus the previous roll.

After this, I calculate the final game score by looping over 10 frames (the size of a line of bowling), chunking the line array based on if the frame was a strike spare or normal roll, and then reducing the array going forward.

I feel like this is the best way to perform this challenge as a golf challenge, but I'm not sure if I'm reducing it enough.

BigBerger

Posted 2018-07-12T03:10:02.883

Reputation: 148

Question was closed 2018-07-12T15:52:55.267

@user202729 The TIO link, as well as the use of print strongly suggests it is not. – Post Rock Garf Hunter – 2018-07-12T03:20:43.093

@user202729 Just changed it, thanks! – BigBerger – 2018-07-12T04:00:29.207

1To be clear a spare (/) can never come after a gutter (-)? Your current code doesn't seem to handle this. – Post Rock Garf Hunter – 2018-07-12T04:05:17.187

Oh and welcome to the site by the way! – Post Rock Garf Hunter – 2018-07-12T04:08:07.750

3I'm getting a score of 176 for your example. Could you maybe write out the bowling scoring rules to make sure we're all on the same page about how they are calculated? – xnor – 2018-07-12T04:27:39.827

Here are the scores I have after each roll: `X 10; 7 24; / 30; 9 48;

  • 48;

X 58;

  • 58;

8 74; 8 82; / 84;

  • 84;

6 90; X 100; X 120; X 150; 8 174; 1 176`. – xnor – 2018-07-12T04:46:18.847

@user202729 I can use any language. – BigBerger – 2018-07-12T04:57:42.813

@xnor I added the wikipedia with the rules, it's easier than writing them out. – BigBerger – 2018-07-12T04:58:03.190

@user202729 yes it is guaranteed – BigBerger – 2018-07-12T04:58:26.343

@user202729 Feel free! But my office decided that only "real" languages are allowed to compete for the free lunch (python, perl, ruby, javascript, etc). I'm guessing it will be up for debate depending on our submissions (it's a small office). – BigBerger – 2018-07-12T05:01:54.237

I suppose the last edit is only for the bonus section? Because the question is Python only? ... – user202729 – 2018-07-12T05:01:58.463

Let us continue this discussion in chat.

– BigBerger – 2018-07-12T05:02:14.117

1OK, I was missing that if the game goes into extra frames, those frames are not scored. Are we guaranteed that the game as listed lasts exactly ten frames and those extra rolls are listed exactly if the the game goes into those extra frames? – xnor – 2018-07-12T05:03:34.800

A naive translation to J. Just for fun. Try it online!

– user202729 – 2018-07-12T06:09:16.360

This could make for an interesting challenge, assuming it hasn't been done already. – Shaggy – 2018-07-12T08:58:26.563

Closely related. – pajonk – 2018-07-12T09:47:56.497

Yeah, it's been done already

– Shaggy – 2018-07-12T11:22:39.100

Answers

4

1.

The portion 2+(_[0]+_[1]>=10) could be rewritten as 2+(_[0]+_[1]>9) to save two bytes. In a similar vein 2-(_[0]==10) can be rewritten as 2-(_[0]>9), since 10 is the only possible value greater than 9.

a=map(ord,raw_input());a=[10if y==88else 0if y==45else 58-a[x-1]if y==47else y-48for x,y in enumerate(a)]
t=0;exec('t+=sum(a[:2+(a[0]+a[1]>9)]);a=a[2-(a[0]>9):];'*10);print t

Try it online!

(I also renamed _ to a, because even though it's code-golf we don't have to make it unreadable)

2.

You can also use a dictionary instead of conditionals to saves some bytes.

a=raw_input();a=[{y:ord(y)-48,'X':10,'-':0,'/':58-ord(a[x-1])}[y]for x,y in enumerate(a)]
t=0;exec('t+=sum(a[:2+(a[0]+a[1]>9)]);a=a[2-(a[0]>9):];'*10);print t

Try it online!

3.

Don't enumerate. enumerate tends to be too long for it's own good. Here you want to get the last character, which can better be acheived with a zip.

a=raw_input();a=[{y:ord(y)-48,'X':10,'-':0,'/':58-ord(x)}[y]for x,y in zip('-'+a,a)]
t=0;exec('t+=sum(a[:2+(a[0]+a[1]>9)]);a=a[2-(a[0]>9):];'*10);print t

Try it online!


If you combine this with @user202729's two tips you get 149 bytes.

a=raw_input();a=[[10,0,58-ord(x),ord(y)-48]['X-/'.find(y)]for x,y in zip('-'+a,a)]
t=0;exec't+=sum(a[:2+(a[0]+a[1]>9)]);a=a[2-(a[0]>9):];'*10;print t

Try it online!


Here's another version that is also 149 bytes:

a=raw_input();a=[['-123456789X'.find(y),58-ord(x)][y=='/']for x,y in zip('-'+a,a)]
t=0;exec't+=sum(a[:2+(a[0]+a[1]>9)]);a=a[2-(a[0]>9):];'*10;print t

Try it online!

Post Rock Garf Hunter

Posted 2018-07-12T03:10:02.883

Reputation: 55 382

Wow, fantastic response! And here I thought I was being clever with my use of enumerate to save some time. Thanks! – BigBerger – 2018-07-12T04:10:44.747

@Swiper-CCCVI Glad that helps. I would suggest waiting a little while before accepting an answer, there are definitely some people that are much better at python golfing than I am, and this question has only been viewed 18 times so far. – Post Rock Garf Hunter – 2018-07-12T04:13:14.100

Sure can do, I wish it was possible to accept multiple! – BigBerger – 2018-07-12T04:24:23.007

3

Shorten the conditional using str.find

Compare:

Mine:        [10,0,58-_[x-1],y-48]['X-/'.find(chr(y))]
Mine (char): [10,0,58-ord(a[x-1]),ord(y)-48]['X-/'.find(y)]
WW's:        {y:ord(y)-48,'X':10,'-':0,'/':58-ord(a[x-1])}[y]
Original:    10if y==88else 0if y==45else 58-_[x-1]if y==47else y-48

user202729

Posted 2018-07-12T03:10:02.883

Reputation: 14 620

1@CatWizard ... That's because you use characters and I use character codes... – user202729 – 2018-07-12T03:50:59.680

Yeah, looks like I confused myself, if you implement this with characters rather than codes you save 2 bytes. Try yours

– Post Rock Garf Hunter – 2018-07-12T03:53:00.243

3

146 bytes

Based on Cat Wizard's "another version".

Basically I include / into the string so find returns 0 (falsy value), then use or to replace that 0 with the desired result.

a=raw_input();a=[('/-123456789X'.find(y)or 59-ord(x))-1for x,y in zip('-'+a,a)]
t=0;exec't+=sum(a[:2+(a[0]+a[1]>9)]);a=a[2-(a[0]>9):];'*10;print t

Try it online!

Explanation: x or y == x if x else y. So:

   '/-123456789X'.find(y)or 59-ord(x)
== '/-123456789X'.find(y) if '/-123456789X'.find(y) else 59-ord(x)
== '/-123456789X'.find(y) if '/-123456789X'.find(y) != 0 else 59-ord(x)
== '/-123456789X'.find(y) if y != '/' else 59-ord(x)

user202729

Posted 2018-07-12T03:10:02.883

Reputation: 14 620

Could you elaborate on how the .find(y)or section is working? I'm having a hard time understanding how this is checking if the character is a spare or not. – BigBerger – 2018-07-12T04:40:41.303

@Swiper-CCCVI If the character is a spare '/-123456789X'.find(y) will be zero. This short circuits the or, because 0 is the same as false, so it returns the second part of the or which is the 59-ord(x). – Post Rock Garf Hunter – 2018-07-12T04:42:13.323

Gotcha, makes sense now. Thanks! And very nifty. – BigBerger – 2018-07-12T04:46:12.630

2

Remove () around parameter of exec

exec is a command, not a function. This saves 2 bytes.

user202729

Posted 2018-07-12T03:10:02.883

Reputation: 14 620

Nice catch! I did not know this. – BigBerger – 2018-07-12T04:17:30.860

1

Is there a reason why you need to use raw_input() and not replace it with input() and wrap the input string in quotes e.g. "X7/9-X-88/-6XXX81"? That would save 4.

ElPedro

Posted 2018-07-12T03:10:02.883

Reputation: 5 301