Отрисовка пиксельного экрана в терминале
Build a screen representation with colored pixels, update operations, arbitrary line drawing and triangle edge drawing. Return deterministic symbolic pixels for the trainer version.
Решение прямо на странице
Напишите код, запустите проверки и только потом открывайте разбор.
Нажмите «Запустить проверки» или Ctrl+Enter.
Показать разбор
Подсказки
- Разделите модель и вывод
Храните цвета как значения в сетке, а в символы превращайте только на финальном выводе.
- Главная сложность — линия
Линия должна работать для крутых наклонов и обратного порядка концов, а не только для пологих диагоналей слева направо.
Идея решения
Удобно хранить экран как двумерный список символов цветов. Команда set обновляет одну ячейку. Команда triangle — это три команды рисования линии.
Для произвольных наклонов линии используйте алгоритм Брезенхэма или эквивалентный целочисленный шаговый алгоритм. Важная часть — одинаково корректно обработать пологие, крутые, горизонтальные, вертикальные и обратные линии, рисуя оба конца.
На исходном собеседовании использовались ANSI-цвета терминала. Версия для тренажёра сохраняет идею экрана, пикселей и линий, но рендерит в детерминированные символы, чтобы задачу можно было проверять в браузере.
Эталонный код
def render_pixel_screen(height: int, width: int, commands: list[dict]) -> list[str]:
palette = {"black": ".", "blue": "B", "yellow": "Y"}
screen = [[palette["black"] for _ in range(width)] for _ in range(height)]
def set_pixel(row: int, col: int, color: str) -> None:
if 0 <= row < height and 0 <= col < width:
screen[row][col] = palette[color]
def draw_line(start, end, color: str) -> None:
r1, c1 = start
r2, c2 = end
dr = abs(r2 - r1)
dc = abs(c2 - c1)
sr = 1 if r1 < r2 else -1
sc = 1 if c1 < c2 else -1
err = dc - dr
row, col = r1, c1
while True:
set_pixel(row, col, color)
if row == r2 and col == c2:
break
twice_err = 2 * err
if twice_err > -dr:
err -= dr
col += sc
if twice_err < dc:
err += dc
row += sr
for command in commands:
kind = command["type"]
color = command["color"]
if kind == "set":
set_pixel(command["row"], command["col"], color)
elif kind == "line":
draw_line(command["start"], command["end"], color)
elif kind == "triangle":
p1, p2, p3 = command["points"]
draw_line(p1, p2, color)
draw_line(p2, p3, color)
draw_line(p3, p1, color)
else:
raise ValueError(f"unknown command type: {kind}")
return ["".join(row) for row in screen]Вопрос про production ML
Multiple threads update individual pixels of the same screen. What can go wrong, and how would you design synchronization?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Показать разбор
Короткий ответ
The main risks are race conditions, lost updates and inconsistent intermediate state. Use a single writer, a queue, per-region locks or atomic updates depending on ordering and throughput needs.
Подробный разбор
If several threads mutate the same screen array, two updates can interleave unpredictably. A pixel may end in the wrong state, a reader can observe a partially updated frame, and coarse rendering operations such as a line can be torn across frames.
The simplest robust design is a single writer: worker threads submit update commands to a queue, and one render loop applies them in order. This gives deterministic ordering and avoids fine-grained locks. If throughput requires parallel writes, use lock striping by row/tile, per-pixel atomics for very small writes, double buffering, or transactional frame batches.
Lock granularity is a tradeoff. A global screen lock is easy but serializes everything. Per-pixel locks are expensive and can deadlock if multi-pixel operations acquire several locks. Tiled locks or a command queue are often a better engineering compromise.
Типичные ошибки
- Use one global lock without discussing throughput.
- Use per-pixel locks for line/triangle updates without a lock ordering rule.
- Forget that readers can observe partially drawn frames.
Как сказать на собеседовании
- State whether updates must be ordered exactly.
- Offer a simple single-writer design before optimizing.
Вопрос про production ML
Multiple clients send pixel updates to a central server over the internet. What transport/protocol would you use and what tradeoffs matter?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Показать разбор
Короткий ответ
For reliable ordered UI state, WebSocket over TCP is the default. For lossy high-rate rendering, UDP/WebRTC-style transport can work if updates are idempotent or sequence-numbered.
Подробный разбор
For a browser or app that sends small pixel-update messages to a central server, a WebSocket over TCP is a practical default. It gives a persistent bidirectional connection, reliable delivery and in-order messages. The payload can be compact JSON at low volume or a binary format when update rate matters.
The tradeoff is latency under packet loss. TCP preserves order, so a lost packet can block later updates. If the screen is a real-time stream where stale updates can be dropped, UDP or WebRTC data channels may be better, but then the application needs sequence numbers, idempotent commands, snapshots or reconciliation.
The server should also define conflict semantics: last-write-wins by server timestamp, client sequence numbers, per-client authority or command batching. Transport alone does not solve state consistency.
Типичные ошибки
- Say TCP without explaining ordering and head-of-line blocking.
- Say UDP without explaining loss, reordering and recovery.
- Ignore conflict resolution when several clients update the same pixel.
Как сказать на собеседовании
- Start with WebSocket for product simplicity.
- Mention binary payloads only after correctness semantics are clear.