Enable Javascript in your browser and then refresh this page, for a much enhanced experience.
First solution in Clear category for Text Formatting by Sillte
from abc import abstractmethod
class Aligner:
""" Aligner.
Align the given text to the specified ``width``.
Example:
-------------------------
>>> width = 38
>>> style = "l" # ``l`` means ``Left``.
>>> aligner = Aligner.get_aligner(style, width)
>>> return aligner.align(text)
When style class is added, you derive this class and
implement ``give_spaces``.
Note:
------------------------------------------------------
For practical usage, many improvements are required.
* Clarify the prohibited characters and measures to them.
* handling long long words.
"""
"""Prohibited characters.
Though, these are insufficient
in practical usage.
"""
prohibited_chars = ["\n", "\t"]
__style_map = dict()
def __init__(self, width):
self.width = width
@classmethod
def __init_subclass__(cls, style, **kwargs):
""" Register ``style`` so that
``get_aligner`` gives the corresponding class.
"""
if style in Aligner.__style_map:
raise ValueError(f"Style {style} is already used.")
Aligner.__style_map[style] = cls
super().__init_subclass__()
@staticmethod
def get_aligner(style, width):
return Aligner.__style_map[style](width)
@abstractmethod
def give_spaces(self, line_words, **kwargs):
""" Return ``list`` of ``integer``,
which represents the number of spaces
just before each word in ``line_words``.
Args:
line_words: list`` of ``str
represents words in line.
**kwargs**: options are given as keyword arguments.
``is_last_line``: boolean.
"""
raise NotImplementedError()
def align(self, text):
""" Return the aligned the text.
"""
for prohibited_char in self.prohibited_chars:
if text.find(prohibited_char) != -1:
raise ValueError("Prohibited characters in ``text``.")
words = text.split(" ")
lines = self._split_line_words(words)
result_lines = list()
for l, line_words in enumerate(lines):
options = dict()
options["is_last_line"] = (l == len(lines) - 1)
spaces = self.give_spaces(line_words, **options)
assert len(spaces) == len(line_words)
result_lines.append("".join((" " * s + word
for s, word
in zip(spaces, line_words))))
result = "\n".join(result_lines)
return result
def _split_line_words(self, words):
""" give the words of lines as ``list`` of ``list``.
"""
lines = []
remain_words = words
while remain_words:
line_words, remain_words = self._provide_line(remain_words)
lines.append(line_words)
return lines
def _provide_line(self, words):
length = len(words[0])
for count, word in enumerate(words[1:], 1):
if self.width <= length + len(word):
return words[:count], words[count:]
else:
length += 1 + len(word)
return words, ()
# Concrete Classes of ``Aligner``.
class LeftAligner(Aligner, style="l"):
def give_spaces(self, line_words, **kwargs):
return [0] + [1 for _ in range(len(line_words) - 1)]
class CenterAligner(Aligner, style="c"):
def give_spaces(self, line_words, **kwargs):
w_length = sum(len(word) for word in line_words) + len(line_words) - 1
s_space = (self.width - w_length) // 2
return [s_space] + [1 for _ in range(len(line_words) - 1)]
class RightAligner(Aligner, style="r"):
def give_spaces(self, line_words, **kwargs):
w_length = sum(len(word) for word in line_words) + len(line_words) - 1
s_space = self.width - w_length
return [s_space] + [1 for _ in range(len(line_words) - 1)]
class JustifiedAligner(Aligner, style="j"):
def give_spaces(self, line_words, **kwargs):
if kwargs["is_last_line"]:
return [0] + [1 for _ in range(len(line_words) - 1)]
s_space = self.width - sum(len(word) for word in line_words)
interval = len(line_words) - 1
d, r = divmod(s_space, interval)
spaces = [d] * interval
for i in range(r):
spaces[i] += 1
assert sum(spaces) == s_space
return [0] + spaces
def text_formatting(text: str, width: int, style: str) -> str:
aligner = Aligner.get_aligner(style, width)
return aligner.align(text)
May 20, 2019
Comments: