16
\$\begingroup\$

A great Python golfing tip is to omit needless spaces. But if you are like me, you don't always have the time to search for which spaces to remove. If only there was a way to speed up the process...

Task

Given a single line of Python, as input, return/output a new line which omits all needless spaces. The table below shows which spaces are needless, and must be removed for maximum golfage.

   | L D S
---+-------
 L | s s n
 D | n - n
 S | n n n

First token is row, second token is column
L: Letter
D: Digit
S: Symbol

s: space
n: no space
-: never happens (except multidigit numbers)

Clarifications

  • What is considered a letter, digit, and symbol?
    • Letter: _ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
    • Digit: 0123456789
    • Symbol: !"\#$%&'()*+,-./:;<=>?@[]^`{|}~
  • You can assume that all characters in the input are either a Letter, Digit, Symbol, or Space.
  • You can assume that there will be no such test case where two digits are separated by one or more spaces
  • You can assume that all test cases contain valid Python syntax
  • You can assume that there will be no leading or trailing spaces.
  • Follow the above rules for omitting spaces, regardless of whether omitting them will produce incorrect or different code

Test Cases

Input

print ( "Hello, World!" )
eval("eval('eval(`1`)')")
_  =  '_  =  %  r  ;  print _  %%  _'  ;  print _  %  _
[ 1for _ in range(10, 6 , -1)     ]
print 16

Output

print("Hello,World!")
eval("eval('eval(`1`)')")
_='_=%r;print _%%_';print _%_
[1for _ in range(10,6,-1)]
print 16

This is , so the shortest code in bytes wins!

\$\endgroup\$
4
  • 3
    \$\begingroup\$ Related fact: you can delete any space after a complex number literal (which ends with j); Python tokenizes e.g. 1jfor (1j for) and 1jor (1j or) without problem. \$\endgroup\$
    – Bubbler
    Commented May 18, 2020 at 5:21
  • 4
    \$\begingroup\$ You can assume that all characters in the input are either a Letter, Digit, or Symbol - for completeness you may want to add Space. Code that does nothing is valid otherwise. \$\endgroup\$
    – Noodle9
    Commented May 18, 2020 at 9:38
  • 1
    \$\begingroup\$ @JonathanAllan Now clarified that there will be no leading or trailing spaces in test cases. \$\endgroup\$ Commented May 18, 2020 at 18:27
  • \$\begingroup\$ Nice problem, but would have been much more fun if we had to skip strings. \$\endgroup\$
    – Adám
    Commented May 19, 2020 at 14:04

8 Answers 8

9
\$\begingroup\$

Retina 0.8.2, 22 21 bytes

Saved a byte thanks to a mysterious hint from @Neil :P

(\d|\W) +
$1
 (\W)
$1

Try it online!

\$\endgroup\$
5
  • 3
    \$\begingroup\$ I can actually shave a byte off that... I'll keep you in suspense for a bit. \$\endgroup\$
    – Neil
    Commented May 17, 2020 at 23:46
  • \$\begingroup\$ I think @Neil should just randomly post that exact comment on people's answers (with no idea in mind) just to inspire them :D \$\endgroup\$ Commented May 18, 2020 at 16:28
  • \$\begingroup\$ @GregMartin Eerie, as I had a completely different idea in mind anyway. I like his approach though, since the first stage ensures that there are no runs of spaces remaining. \$\endgroup\$
    – Neil
    Commented May 18, 2020 at 19:01
  • \$\begingroup\$ I like his/her approach as well (and the attribution phrasing) :) \$\endgroup\$ Commented May 18, 2020 at 21:29
  • 1
    \$\begingroup\$ JFTR, my 21-byte answer was +`(\d|\W) | (\W) $1$2 \$\endgroup\$
    – Neil
    Commented May 19, 2020 at 12:25
5
\$\begingroup\$

JavaScript (ES9),  60  43 bytes

s=>s.replace(/(?<![a-z_] *) | (?=\W)/gi,'')

Try it online!

\$\endgroup\$
1
  • \$\begingroup\$ I think porting @Abigail's Perl answer saves you a byte, even after using [^\d\W]. \$\endgroup\$
    – Neil
    Commented May 18, 2020 at 19:11
4
\$\begingroup\$

Java (JDK), 48 bytes

s->s.replaceAll("(?<![a-zA-Z_] *) | (?=\\W)","")

Try it online!

Port of Arnauld's JS answer.

\$\endgroup\$
3
\$\begingroup\$

perl -p, 24 27 26 bytes

s/([_\pL] ) +(?=\w)| /$1/g

Try it online!

This replaces any letter or underscore + space combo, which is followed by letter, digit or underscore, by itself, and removes any other space. In effect, it keeps any space which is proceeded by a letter or an underscore, and is followed by a letter, digit or underscore, and removes any other space. The (?=\w) is a lookahead, and won't be consumed by the matcher; this makes cases like _ _ _ _ _ keep all the spaces.

This will not work correctly for input containing characters outside of the ASCII range.

\$\endgroup\$
4
  • \$\begingroup\$ This seems to break if you add a few extra spaces betweenin and range in the 4th test case \$\endgroup\$ Commented May 18, 2020 at 14:50
  • \$\begingroup\$ Indeed. Should be fixed now. \$\endgroup\$
    – Abigail
    Commented May 18, 2020 at 15:35
  • \$\begingroup\$ Can't you just use a space instead of \s? \$\endgroup\$
    – Neil
    Commented May 18, 2020 at 19:12
  • \$\begingroup\$ Of course. I probably just wrote \s* out of habit. \$\endgroup\$
    – Abigail
    Commented May 18, 2020 at 19:48
2
\$\begingroup\$

Charcoal, 41 bytes

≔ωη≔ωζFθ≡ι ≔ηζ«×ζ№⁺_⭆⁶²⍘κφιι≔ωζ≔× №⁺_α↥ιη

Try it online! Link is to verbose version of code. Explanation:

≔ωη

Set h to the empty string. This variable is a space if the last non-space character was a letter.

≔ωζ

Set z to the empty string. This variable is a space if the last character was a space and the last non-space character was a letter.

Fθ

Loop over the characters of the input.

≡ι ≔ηζ«

If the current character is a space then copy h to z, otherwise:

×ζ№⁺_⭆⁶²⍘κφι

If the current character is a letter or digit then output z.

ι

Output the current character.

≔ωζ

Set z to the empty string.

≔× №⁺_α↥ιη

Set h to a space if the current character is a letter, otherwise set it to the empty string.

\$\endgroup\$
2
\$\begingroup\$

Befunge-93, 285 bytes

>~:1+!#@_11g\v>>,>>>>0>11p
v #          <>  ^
>:" "-#v_ \1` |
v      <      >      2^
>:"/"`#v_>    >^
v      <
>:"9"`#v_ \2- |
v      <      >" ",,^
>:"@"`#v_^
v      <      >    >,1^
>:"Z"`#v_ >\2-|
v      <      >" ",^
>:"_"-#v_ ^
v      <
>:"`"`#v_^
v      <
>:"z"`#v_ ^
       > ^

Try it online!

This is a simple statement machine, using a single 3 valued state (written to position (1,1)). The state is 1 if the previous character was a letter or underscore, 2 if the previous character was a space, and the last non-space character was a letter or underscore, and 0 otherwise.

The program reads one character at a time, ending the program if there is no more input; the read character is pushed on the stack. We then push the current state on the stack, and swap the two elements on the stack. We go through a bunch of if/then/else statements, to find out what kind of character we have (digit, letter or underscore, space, other symbol). If the current character is a digit, letter or underscore, and the current state is 2, we print a space. We then print the current character, unless that character is a space. Finally, we push the new state on the stack, which we then store on position (1,1), and we enter the loop again.

\$\endgroup\$
1
\$\begingroup\$

Jelly, 27 bytes

eþØW,ØD¤
+⁹n⁶¤Tị
Ñ>/ŻçµÑSḊç

A full program which accepts a Python formatted string of Python code which prints the result.

Try it online! (Do note that Jelly evaluates its arguments as Python before using them.)

How?

eþØW,ØD¤   - Link 1: list of characters, A          e.g. "_ 5 ;"
       ¤   - nilad followed by link(s) as a nilad:
  ØW       -   word characters                           "A..Za..z0...9_"
     ØD    -   digit characters                          "0...9"
    ,      -   pair                                      ["A..Za..z0...9_, "0...9"]
 þ         - outer product using:
e          -   exists in?                                [[1,0,1,0,0], [0,0,1,0,0]]

+⁹n⁶¤Tị    - Link 2: list of integers, X; list of characters, Y
    ¤      - nilad followed by link(s) as a nilad:
 ⁹         -   chain's right argument, Y
   ⁶       -   the space character
  n        -   not equal? (vectorises)
+          - add (to X)
     T     - truthy (non-zero) indices
      ị    - index into (Y)

Ñ>/ŻçµÑSḊç - Main Link: list of characters, S
Ñ          - call next Link as a monad -> Link_1(S)
  /        - reduce by:
 >         -   is greater than (gives 1s where letters are, 0s elsewhere)
   Ż       - prepend a zero
    ç      - call last Link as a dyad -> Link_2(that, S)
     µ     - start a new monadic Link:  (call that R)
      Ñ    - call next Link as a monad -> Link_1(R)
       S   - sum (gives 1s at letters, 2s at digits, 0s elsewhere)
        Ḋ  - dequeue
         ç - call last Link as a dyad -> Link_2(that, R)
           - implicit print

First removes spaces which do not directly follow letters, then removes remaining spaces which are not directly before letters or digits.

\$\endgroup\$
1
\$\begingroup\$

Python 3, 60 bytes

lambda s:re.sub(r' +(?=\W)|(?<![a-zA-Z_]) +','',s)
import re

Try it online!

Port of Arnauld's JavaScript answer.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.