patternpythonMinor
Python ASCII-Art Text Generator
Viewed 0 times
textgeneratorartasciipython
Problem
_____ _____ _____ _____ _____ _______
/\ / ____| / ____| |_ _| |_ _| /\ | __ \ |__ __|
/ \ | (___ | | | | | | / \ | |__) | | |
/ /\ \ \___ \ | | | | | | / /\ \ | _ / | |
/ ____ \ ____) | | |____ _| |_ _| |_ / ____ \ | | \ \ | |
/_/ \_\ |_____/ \_____| |_____| |_____| /_/ \_\ |_| \_\ |_|As a part of a console utilities module that I'm building, the central function (after which the module is named) is a function that generates ASCII art representation of given text.
Any comments or suggestions for improvement will be great!
default_conf = open('C://Programs//Python35//Lib//asciiart.txt').read()
...
def asciiart (text, conf=default_conf):
"""Produces an ascii art representation of text using the conf data"""
if isinstance(conf, str): conf = conf.split('\n')
height, key, data = int(conf[0]), conf[1], conf[2:]
figures = dict(map(lambda x: (x, data[key.index(x) * height : key.index(x) * height + height]), key))
art = '\n'.join(['\n'.join([' '.join([figures[x][i] for x in t.lower() if x in figures]) for i in range(height)]) for t in text.split('\n')])
return artHere is a demo:
>>> import asciiart
>>> print(asciiart.asciiart('code-review'))
_____ ____ _____ ______ _____ ______ __ __ _____ ______ __ __
/ ____| / __ \ | __ \ | ____| | __ \ | ____| \ \ / / |_ _| | ____| \ \ / /
| | | | | | | | | | | |__ ____ | |__) | | |__ \ \ / / | | | |__ \ \ /\ / /
| | | | | | | | | | | __| |____| | _ / | __| \ \/ / | | | __| \ \/ \/ /
| |____ | |__| | | |__| | | |____ | | \ \ | |____ \ / _| |_ | |____ \ /\ /
\_____| \____/ |_____/ |______| |_| \_\ |______| \/ |_____| |______| \/ \/The configuration file form
Solution
Style
You’ve already been told that by @alexwlchan in your previous question but: "whitespace is cheap" and it improves readability a lot. Compare your code to:
Again, you should also consider using generator expressions rather than list-comprehensions to feed into your
Handling font configuration
First of, when reading the file, you should remember to close it:
You may also want to name the resulting string
Second, why parse this string at each call of
The advantage being:
Obviously, this version wont work since
Better iterations
Now that we have a better API, let's focus a bit more on the code:
-
iteration over a file can be made using either the "reading" API or the iterator one, this can help better handle the separation of each part:
-
building of the
-
now that we use
You’ve already been told that by @alexwlchan in your previous question but: "whitespace is cheap" and it improves readability a lot. Compare your code to:
def asciiart(text, conf=default_conf):
"""Produces an ascii art representation of text using the conf data"""
if isinstance(conf, str):
conf = conf.split('\n')
height, key, data = int(conf[0]), conf[1], conf[2:]
figures = dict(map(
lambda x: (x, data[key.index(x) * height: (key.index(x) + 1) * height]),
key))
art = '\n'.join([
'\n'.join([
' '.join([figures[x][i] for x in t.lower() if x in figures])
for i in range(height)])
for t in text.split('\n')])
return artAgain, you should also consider using generator expressions rather than list-comprehensions to feed into your
join calls: turn those square brackets into parenthesis.Handling font configuration
First of, when reading the file, you should remember to close it:
with open('C://Programs//Python35//Lib//asciiart.txt') as configuration_file:
default_conf = configuration_file.read()You may also want to name the resulting string
DEFAULT_CONF as it is a constant.Second, why parse this string at each call of
asciiart? You should provide a function that will do the parsing and asciiart will only be responsible of using the parsed configuration:ASCII_ART_CONFIGURATION = {}
def parse_ascii_art_configuration(filename='C://Programs//Python35//Lib//asciiart.txt'):
with open(filename) as f:
conf = f.read().split('\n')
height, key, data = int(conf[0]), conf[1], conf[2:]
figures = dict(map(
lambda x: (x, data[key.index(x) * height: (key.index(x) + 1) * height]),
key))
ASCII_ART_CONFIGURATION.update(figures)
def asciiart(text):
if not ASCII_ART_CONFIGURATION:
parse_ascii_art_configuration()
art = '\n'.join(
'\n'.join(
' '.join(ASCII_ART_CONFIGURATION[x][i]
for x in t.lower() if x in ASCII_ART_CONFIGURATION)
for i in range(height))
for t in text.split('\n'))
return artThe advantage being:
- You give your users direct access to the underlying
ASCII_ART_CONFIGURATIONthat they can populate the way they want if need be;
- You give your users an easy way to provide their own configuration filepaths;
- You parse the configuration only once.
Obviously, this version wont work since
asciiart does not have access to height any more. Time to bring our old friend zip (or itertools.zip_longest) back:from itertools import zip_longest
def asciiart(text):
if not ASCII_ART_CONFIGURATION:
parse_ascii_art_configuration()
art = '\n'.join(
'\n'.join(
' '.join(row)
for row in zip_longest(
(ASCII_ART_CONFIGURATION[x]
for x in t.lower() if x in ASCII_ART_CONFIGURATION),
fillvalue=''))
for t in text.split('\n'))
return artBetter iterations
Now that we have a better API, let's focus a bit more on the code:
-
iteration over a file can be made using either the "reading" API or the iterator one, this can help better handle the separation of each part:
with open(filename) as f:
height = int(next(f))
keys = next(f)
data = [line.rstrip('\n') for line in f]-
building of the
figure dictionary can be made more efficient using the grouper recipe from itertools:def grouper(iterable, n):
"Collect data into fixed-length chunks or blocks"
# grouper('ABCDEFG', 3) --> ABC DEF G"
args = [iter(iterable)] * n
return zip_longest(*args, fillvalue='')
def parse_ascii_art_configuration(filename='C://Programs//Python35//Lib//asciiart.txt'):
with open(filename) as f:
height = int(next(f))
keys = next(f)
data = [line.rstrip('\n') for line in f]
figures = {
character: list(group)
for character, group in zip(keys, grouper(data, height))
}
ASCII_ART_CONFIGURATION.update(figures)-
now that we use
zip_longest to concatenate ascii-art letters, you can simplify handling missing symbols by turning ASCII_ART_CONFIGURATION into a collections.defaultdict:ASCII_ART_CONFIGURATION = defaultdict(list)
def asciiart(text):
if not ASCII_ART_CONFIGURATION:
parse_ascii_art_configuration()
return '\n'.join(
'\n'.join(
' '.join(row)
for row in zip_longest(
(ASCII_ART_CONFIGURATION[x] for x in line_of_text),
fillvalue=''))
for line_of_text in text.lower().splitlines())Code Snippets
def asciiart(text, conf=default_conf):
"""Produces an ascii art representation of text using the conf data"""
if isinstance(conf, str):
conf = conf.split('\n')
height, key, data = int(conf[0]), conf[1], conf[2:]
figures = dict(map(
lambda x: (x, data[key.index(x) * height: (key.index(x) + 1) * height]),
key))
art = '\n'.join([
'\n'.join([
' '.join([figures[x][i] for x in t.lower() if x in figures])
for i in range(height)])
for t in text.split('\n')])
return artwith open('C://Programs//Python35//Lib//asciiart.txt') as configuration_file:
default_conf = configuration_file.read()ASCII_ART_CONFIGURATION = {}
def parse_ascii_art_configuration(filename='C://Programs//Python35//Lib//asciiart.txt'):
with open(filename) as f:
conf = f.read().split('\n')
height, key, data = int(conf[0]), conf[1], conf[2:]
figures = dict(map(
lambda x: (x, data[key.index(x) * height: (key.index(x) + 1) * height]),
key))
ASCII_ART_CONFIGURATION.update(figures)
def asciiart(text):
if not ASCII_ART_CONFIGURATION:
parse_ascii_art_configuration()
art = '\n'.join(
'\n'.join(
' '.join(ASCII_ART_CONFIGURATION[x][i]
for x in t.lower() if x in ASCII_ART_CONFIGURATION)
for i in range(height))
for t in text.split('\n'))
return artfrom itertools import zip_longest
def asciiart(text):
if not ASCII_ART_CONFIGURATION:
parse_ascii_art_configuration()
art = '\n'.join(
'\n'.join(
' '.join(row)
for row in zip_longest(
(ASCII_ART_CONFIGURATION[x]
for x in t.lower() if x in ASCII_ART_CONFIGURATION),
fillvalue=''))
for t in text.split('\n'))
return artwith open(filename) as f:
height = int(next(f))
keys = next(f)
data = [line.rstrip('\n') for line in f]Context
StackExchange Code Review Q#145148, answer score: 7
Revisions (0)
No revisions yet.