Implement a basic ASCII art grid parser for the format described in https://github.com/Phyks/replot/issues/5
This commit is contained in:
parent
dfeed6f526
commit
a373f6f106
138
replot/grid_parser.py
Normal file
138
replot/grid_parser.py
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
"""
|
||||||
|
This module implements functions to easily translate an ASCII art matrix into
|
||||||
|
subplots commands, to create subplots easily.
|
||||||
|
|
||||||
|
Thanks to Laurent Dardelet for writing this code.
|
||||||
|
"""
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def parse_ascii(M):
|
||||||
|
"""
|
||||||
|
Parse an ASCII art grid into subplots commands.
|
||||||
|
|
||||||
|
:param M: A list of strings, each string representing a row.
|
||||||
|
:returns: A dict containing the width and height of the grid, and a \
|
||||||
|
description of the grid as a list of subplots. Each subplot is a \
|
||||||
|
tuple of ((y_position, x_position), symbol, (rowspan, colspan)). \
|
||||||
|
Returns ``None`` if the matrix could not be parsed.
|
||||||
|
|
||||||
|
.. note:: This function expects ``M`` to represent a valid rectangular \
|
||||||
|
matrix.
|
||||||
|
|
||||||
|
.. note:: Position starts from 0 and origin is the top left corner.
|
||||||
|
|
||||||
|
>>> parse_ascii(["AAA",
|
||||||
|
"BBC",
|
||||||
|
"DEC"]
|
||||||
|
{
|
||||||
|
"width": 3,
|
||||||
|
"height": 3,
|
||||||
|
"grid": [
|
||||||
|
((0, 0), "A", (1, 3)),
|
||||||
|
((1, 0), "B", (1, 2)),
|
||||||
|
((1, 2), "C", (2, 1))
|
||||||
|
((2, 0), "D", (1, 1)),
|
||||||
|
((2, 1), "E", (1, 1)),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
# Get the dimensions of the matrix
|
||||||
|
height, width = len(M), len(M[0])
|
||||||
|
|
||||||
|
# Store the list of found symbols
|
||||||
|
symbols_found = []
|
||||||
|
# Keep track of the elements which have already been assigned to a zone
|
||||||
|
elements_done = np.zeros([height, width])
|
||||||
|
# List of the output subplots commands
|
||||||
|
subplot_list = []
|
||||||
|
|
||||||
|
# Iterate through M, starting from top left corner
|
||||||
|
# Going from left to right and from top to bottom
|
||||||
|
for n_x in range(width):
|
||||||
|
for n_y in range(height):
|
||||||
|
if elements_done[n_y, n_x] == 0:
|
||||||
|
# If this location in the matrix has never been assigned to so
|
||||||
|
# far, it is the current symbol we'll try to match with its
|
||||||
|
# neighbours.
|
||||||
|
current_symbol = M[n_y][n_x]
|
||||||
|
if current_symbol in symbols_found:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
# By default, subplot is of size (1, 1)
|
||||||
|
colspan = 1
|
||||||
|
rowspan = 1
|
||||||
|
# Keep track of the current symbol as having already been
|
||||||
|
# seen
|
||||||
|
symbols_found.append(current_symbol)
|
||||||
|
|
||||||
|
# Look at neighbouring positions, to find the limits of a
|
||||||
|
# possible subplot
|
||||||
|
# Start looking by increasing X coordinate
|
||||||
|
for n_x_tmp in range(n_x + 1, width):
|
||||||
|
if M[n_y][n_x_tmp] == current_symbol:
|
||||||
|
colspan += 1
|
||||||
|
# Then, do the same by increasing Y coordinate
|
||||||
|
for n_y_tmp in range(n_y + 1, height):
|
||||||
|
if M[n_y_tmp][n_x] == current_symbol:
|
||||||
|
rowspan += 1
|
||||||
|
|
||||||
|
# We have found an area with the current symbol. Check that
|
||||||
|
# it is a rectangle containing only the current symbol.
|
||||||
|
is_valid_rectangle = _check_rect(n_x, n_y,
|
||||||
|
colspan, rowspan,
|
||||||
|
current_symbol,
|
||||||
|
M)
|
||||||
|
if is_valid_rectangle:
|
||||||
|
# Mark all these elements as processed
|
||||||
|
_set_as_done(n_x, n_y, colspan, rowspan, elements_done)
|
||||||
|
# And store the associated subplot command
|
||||||
|
subplot_list.append(
|
||||||
|
((n_y, n_x),
|
||||||
|
current_symbol,
|
||||||
|
(rowspan, colspan)))
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
return {"width": width,
|
||||||
|
"height": height,
|
||||||
|
"grid": subplot_list}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _check_rect(n_x, n_y, dx, dy, symbol, M):
|
||||||
|
"""
|
||||||
|
Check that for a rectangle defined by two of its sides, every element \
|
||||||
|
within it is the same.
|
||||||
|
|
||||||
|
.. note:: This method is called once the main script has reached the \
|
||||||
|
limits of a rectangle.
|
||||||
|
|
||||||
|
:param n_x: Starting position of the rectangle (top left corner abscissa).
|
||||||
|
:param n_y: Starting position of the rectangle (top left corner ordonate).
|
||||||
|
:param dx: Width of the rectangle.
|
||||||
|
:param dy: Height of the rectangle.
|
||||||
|
:param symbol: Symbol which should be in the rectangle.
|
||||||
|
:param M: Input matrix, as a list of strings.
|
||||||
|
:returns: Boolean indicated whether the rectangle is correct or not.
|
||||||
|
"""
|
||||||
|
for x in range(dx):
|
||||||
|
for y in range(dy):
|
||||||
|
if M[n_y + y][n_x + x] != symbol:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _set_as_done(n_x, n_y, dx, dy, elements_done):
|
||||||
|
"""
|
||||||
|
Mark some elements as having been processed, to keep track of them.
|
||||||
|
|
||||||
|
:param n_x: Starting position of the rectangle (top left corner abscissa).
|
||||||
|
:param n_y: Starting position of the rectangle (top left corner ordonate).
|
||||||
|
:param dx: Width of the rectangle.
|
||||||
|
:param dy: Height of the rectangle.
|
||||||
|
:param elements_done: A matrix of the same shape as the input matrix, \
|
||||||
|
updated in place.
|
||||||
|
"""
|
||||||
|
for x in range(dx):
|
||||||
|
for y in range(dy):
|
||||||
|
elements_done[n_y+y, n_x+x] = 1
|
Loading…
Reference in New Issue
Block a user