Для моего небольшого проекта мне придется много работать с переводом шрифта Брайля. Для этого я уже закончил небольшую программу, которую могу использовать для перевода шрифта Брайля на английские буквы. Чтобы сделать все это немного более модульным, я экспортировал всю свою логику Брайля в небольшой модуль:
from typing import NoReturn, List
from bitmap import BitMap
from enum import Enum
from bidict import bidict
class Dot(Enum):
TOP_LEFT = 0
MIDDLE_LEFT = 1
BOTTOM_LEFT = 2
TOP_RIGHT = 3
MIDDLE_RIGHT = 4
BOTTOM_RIGHT = 5
class BrailleGlyph(BitMap):
UNICODE_BLOCK = 10240
MAPPING = bidict(
{"00000000": " ",
"00000001": "A",
"00000011": "B",
"00001001": "C",
"00011001": "D",
"00010001": "E",
"00001011": "F",
"00011011": "G",
"00010011": "H",
"00001010": "I",
"00011010": "J",
"00000101": "K",
"00000111": "L",
"00001101": "M",
"00011101": "N",
"00010101": "O",
"00001111": "P",
"00011111": "Q",
"00010111": "R",
"00001110": "S",
"00011110": "T",
"00100101": "U",
"00100111": "V",
"00111010": "W",
"00101101": "X",
"00111101": "Y",
"00110101": "Z", }
)
def __init__(self, maxnum: int = 8) -> NoReturn:
super().__init__(maxnum)
self.history: List[Dot] = []
def __str__(self) -> str:
ones = int(self.tostring()[4:], 2)
tens = int(self.tostring()[:4], 2) * 16
return chr(BrailleGlyph.UNICODE_BLOCK + tens + ones)
@classmethod
def from_char(cls, char: str) -> "BrailleGlyph":
return BrailleGlyph.fromstring(cls.MAPPING.inverse[char.upper()])
def is_empty(self) -> bool:
return self.history == []
def click(self, index: Dot) -> NoReturn:
if not self.test(index.value):
self.set(index.value)
self.history.append(index)
def delete(self) -> NoReturn:
self.flip(self.history.pop().value)
def get_dots(self) -> List[Dot]:
return self.history
def to_ascii(self) -> str:
try:
return BrailleGlyph.MAPPING[self.tostring()]
except KeyError:
return "?"
class BrailleTranslator:
def __init__(self) -> NoReturn:
self.glyphs = [BrailleGlyph()]
def __str__(self) -> str:
return "".join(map(str, self.glyphs))
def new_glyph(self) -> NoReturn:
self.glyphs.append(BrailleGlyph())
def delete(self) -> NoReturn:
if self.glyphs[-1].is_empty():
if len(self.glyphs) != 1:
self.glyphs.pop()
else:
self.glyphs[-1].delete()
def click(self, index: Dot) -> NoReturn:
self.glyphs[-1].click(index)
def get_current_glyph(self) -> BrailleGlyph:
return self.glyphs[-1]
def translate(self) -> str:
return "".join(map(lambda x: x.to_ascii(), self.glyphs))
Основным вариантом использования будет — я нажимаю точки (или некоторые кнопки на моей клавиатуре, которые будут устанавливать точки на глифе), и модуль перевода просто запомнит, что было нажато, и отобразит результаты в режиме реального времени.
Чтобы не изобретать слишком много, я использовал внешний bitmap
а также bidict
библиотеки, которые кажутся очень подходящими для такой задачи.
Есть ли способ сделать это более питоническим? А может быть, какие-то очевидные недочеты?
Вот также пример использования:
def braille_translator(self) -> NoReturn:
graph = self.window["-GRAPH-"]
output = self.window["-BRAILLE_OUTPUT-"]
graphed_dots = []
translator = BrailleTranslator()
circle_mapping = {Dot.BOTTOM_LEFT: (50, 250),
Dot.BOTTOM_RIGHT: (150, 250),
Dot.MIDDLE_LEFT: (50, 150),
Dot.MIDDLE_RIGHT: (150, 150),
Dot.TOP_LEFT: (50, 50),
Dot.TOP_RIGHT: (150, 50), }
dot_mapping = {"1": Dot.BOTTOM_LEFT,
"2": Dot.BOTTOM_RIGHT,
"4": Dot.MIDDLE_LEFT,
"5": Dot.MIDDLE_RIGHT,
"7": Dot.TOP_LEFT,
"8": Dot.TOP_RIGHT, }
while True:
event, values = self.window.read()
if event in (None, 'Exit') or "EXIT" in event:
exit()
elif event in "124578": # Left side of numpad!
translator.click(dot_mapping[event])
elif event == " ":
translator.new_glyph()
elif "BackSpace" in event:
translator.delete()
current_dots = translator.get_current_glyph().get_dots()
for circle in [d for d in graphed_dots if d not in current_dots]:
graph.delete_figure(circle)
graphed_dots.remove(circle)
for dot in [d for d in current_dots if d not in graphed_dots]:
circle = graph.draw_circle(circle_mapping[dot],
20, fill_color=GUI.THEME_COLOR)
graphed_dots.append(circle)
output.update(translator.translate())
А вот GIF из примера использования в действии:
Это тонкая грань между чрезмерной подгонкой модуля перевода для моей текущей задачи и сохранением его модульности для будущего использования!
1 ответ
Для начала несколько мелочей:
Если вы ожидаете, что это будет использоваться в других программах, это должно быть задокументировано: комментарии для объяснения вариантов и деталей реализации и строки документации, чтобы объяснить, как использовать классы и функции в модуле.
Небольшое предложение: при моделировании часто бывает полезно, если интерфейс использует ту же нумерацию или номенклатуру, что и в реальном мире. Например, верхняя левая точка часто обозначается точкой 1, а не точкой 0. Таким образом, class Dot(enum)
может использовать значения 1-6, а не 0-5. Но, возможно, имена — это интерфейс, а значения — внутренние.
Умножение на 16 аналогично сдвигу на 4 бита. То есть это:
def __str__(self) -> str:
offset = int(self.tostring(), 2)
return chr(BrailleGlyph.UNICODE_BLOCK + offset)
Такой же как
def __str__(self) -> str:
ones = int(self.tostring()[4:], 2)
tens = int(self.tostring()[:4], 2) * 16
return chr(BrailleGlyph.UNICODE_BLOCK + tens + ones)
Использовать dict.get(key, default)
со значением по умолчанию вместо try... except
блокировать:
def to_ascii(self) -> str:
return BrailleGlyph.MAPPING.get(self.tostring(), "?")
В BitMap
библиотека избыточна и, кажется, мешает. Например, код преобразует биты в строку, а затем преобразует строку в int в базе 2. Но биты уже были сохранены в виде байта в битовой карте. Я подозреваю, что было бы проще реализовать битовые операции напрямую.
Кажется странным, что BrailleGlyph
объект должен хранить список точек в том порядке, в котором они были добавлены (history
). Это как буква «А», знающая, в каком порядке были добавлены строки. .history
есть ли там, чтобы редактор мог реализовать функцию «отмены», но, похоже, это работа редактора, а не BrailleGlyph
класс.
ты прав,
history
определенно не должно быть частью этого объекта! Что касается библиотеки BitMap, я определенно рассматривал возможность реализации этой части самостоятельно, но предполагал, что определенно существует некоторая обширная библиотека BitMap, которую я мог бы использовать. Оглядываясь назад, можно сказать, что библиотека далеко не обширна и даже не предоставляет инструментов, которые мне нужны больше всего.— Мантас Кандратавичюс