views:

320

answers:

5

So I've done this before and it's a surprising ugly bit of code for such a seemingly simple task.

The goal is to translate any non-printable character into a . (dot). For my purposes "printable" does exclude the last few characters from string.printable (new-lines, tabs, and so on). This is for printing things like the old MS-DOS debug "hex dump" format ... or anything similar to that (where additional whitespace will mangle the intended dump layout).

I know I can use string.translate() and, to use that, I need a translation table. So I use string.maketrans() for that. Here's the best I could come up with:

filter = string.maketrans(
   string.translate(string.maketrans('',''),
   string.maketrans('',''),string.printable[:-5]),
   '.'*len(string.translate(string.maketrans('',''),
   string.maketrans('',''),string.printable[:-5])))

... which is an unreadable mess (though it does work).

From there you can call use something like:

for each_line in sometext:
    print string.translate(each_line, filter)

... and be happy. (So long as you don't look under the hood).

Now it is more readable if I break that horrid expression into separate statements:

ascii = string.maketrans('','')   # The whole ASCII character set
nonprintable = string.translate(ascii, ascii, string.printable[:-5])  # Optional delchars argument
filter = string.maketrans(nonprintable, '.' * len(nonprintable))

And it's tempting to do that just for legibility.

However, I keep thinking there has to be a more elegant way to express this!

+1  A: 

for actual code-golf, I imagine you'd avoid string.maketrans entirely

s=set(string.printable[:-5])
newstring = ''.join(x for x in oldstring if x in s else '.')

or

newstring=re.sub('[^'+string.printable[:-5]+']','',oldstring)
Jimmy
regex is a nice answer for golfing, but would be *much* slower than translate.
gnibbler
this question shouldn't be tagged [code-golf] if it wasn't meant to be golfed ;) but I did have a sneaking suspicion OP actually wanted an answer using maketrans.
Jimmy
yes it should probably should be tagged *elegant* instead of *code-colf* ;)
gnibbler
I love the .join with a conditional expression, though I can't use it for now (too much of my target base is still running Python 2.4.x).
Jim Dennis
+1  A: 

I don't find this solution ugly. It is certainly more efficient than any regex based solution. Here is a tiny bit shorter solution. But only works in python2.6:

nonprintable = string.maketrans('','').translate(None, string.printable[:-5])
filter = string.maketrans(nonprintable, '.' * len(nonprintable))
Nadia Alramli
+3  A: 

Broadest use of "ascii" here, but you get the idea

>>> import string
>>> ascii="".join(map(chr,range(256)))
>>> filter="".join(('.',x)[x in string.printable[:-5]] for x in ascii)
>>> ascii.translate(filter)
'................................ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~.................................................................................................................................'

If I were golfing, probably use something like this:

filter='.'*32+"".join(map(chr,range(32,127)))+'.'*129
gnibbler
+2  A: 

Here's another approach using a list comprehension:

filter = ''.join([['.', chr(x)][chr(x) in string.printable[:-5]] for x in xrange(256)])
ataylor
Oh, that's evil! Relying on the 0/1 numeric value of the condition as an index off a two item list containing '.' and each character in turn. I like it!
Jim Dennis
A: 

hi, i am a newbie in python.would u please describe your second line of code "gnibbler":

filter="".join(('.',x)[x in string.printable[:-5]] for x in ascii)

how does it work?

nima