【问题标题】:Parsing ASCII floor plan image in python?在 python 中解析 A​​SCII 平面图图像?
【发布时间】:2022-10-16 21:40:29
【问题描述】:

我正在尝试确定 ASCII 平面图中的房间和家具数量(S、C、W、P)。一个典型的平面图看起来像这样,有不同的房间和布局。解决这个问题的最佳方法是什么?

+---------------+-------------------+           +----------+
|               |                   |           |          |
|  (office)     |            C      |           |   C      |
|               |                   |           |          |
|           W   |                   +-----------+          |
|               |                   |           |          |
|   S           |   (bathroom)     S|      S    |          |
|           +---+--------+----------+           |          |
|          /P           S|                      |          |
|         /              |                      |          |
|        /   (kitchen)   |      (bedroom)       |  P       |
+-------+                |                      |          |
|        \               |                      |          |
|         \   SSWP       |   W              W   |          |
|          +-------------+----------------------+          |
|                                                          |
|             (hallway)                                    |
|    W                                                     |
+--------------+-------------+-------------+               |
               |             |              \              |
               |             |               \        C    |
               | P           |                \            |
               |             |                 \           |
        +------+           P |                  +----------+
        |S                   |                              
        |    (balcony)   C   |                              
        +--------------------+      

【问题讨论】:

  • 平面图是一堆线(字符串)。只需查找相关的房间描述作为子字符串,例如line.find('(bedroom)') 找到一条线上的所有卧室?如果它们是行内唯一的大写字母,则可以以类似的方式计算家具。
  • 您对每个房间的家具数量的一般(总)家具数量感兴趣吗?
  • 每个房间的不同家具数量。例如:办公室 - 1S 1W 和厨房 - 3S 2P 1W

标签: python ascii


【解决方案1】:

我的方法是:

  • 将计划视为网格
  • 将每一行分割成“空间”段(非墙壁)
  • 连接至少共享一列的空间
  • 查找名称,如果找到,将关节空间保存为 roon
  • 清点房间里的家具

这导致以下代码(我定义了几个类以提高可读性):

from dataclasses import dataclass, field
from re import finditer, search
from typing import Dict, List

@dataclass
class RowSegment():
    row_no : int
    col_start : int
    col_end : int

    def __repr__(self):
        return f'{self.row_no}:{self.col_start}-{self.col_end}'

@dataclass
class Space():
    name : str = 'noname'
    furn_count : Dict[str, int] = field(default_factory = dict)
    segs : List[RowSegment] = field(default_factory = list)
    complete : bool = False

    def __repr__(self):
        return f'{self.name}: {[i for i in self.furn_count.items()]}
{[s for s in self.segs]}'

plan = """
+---------------+-------------------+           +----------+
|               |                   |           |          |
|  (office)     |            C      |           |   C      |
|               |                   |           |          |
|           W   |                   +-----------+          |
|               |                   |           |          |
|   S           |   (bathroom)     S|      S    |          |
|           +---+--------+----------+           |          |
|          /P           S|                      |          |
|         /              |                      |          |
|        /   (kitchen)   |      (bedroom)       |  P       |
+-------+                |                      |          |
|                       |                      |          |
|            SSWP       |   W              W   |          |
|          +-------------+----------------------+          |
|                                                          |
|             (hallway)                                    |
|    W                                                     |
+--------------+-------------+-------------+               |
               |             |                            |
               |             |                       C    |
               | P           |                            |
               |             |                            |
        +------+           P |                  +----------+
        |S                   |                              
        |    (balcony)   C   |                              
        +--------------------+                              """

furn_types = 'CPSW'
workspaces = []
rooms = []
rows = plan.split('
')
rows.pop(0)

for no, row in enumerate(rows):
    found = list(finditer(r'[^/\|+-]+', row))
    if found:
        rsegs = [RowSegment(no, rs.start(), rs.end()) for rs in found]
        for ws in workspaces:
            for rs in rsegs:
                if max(ws.segs[-1].col_start, rs.col_start) < min(ws.segs[-1].col_end, rs.col_end): #add rs to ws
                    ws.segs.append(rs)
                    rsegs.remove(rs)
                    break
            else: #no rs added => complete
                text = ''.join([rows[s.row_no][s.col_start:s.col_end] for s in ws.segs])
                name = search(r'(w+)', text)
                if name:
                    ws.name = name[0][1:-1]
                    ws.furn_count = {f: text.count(f) for f in furn_types}
                    rooms.append(ws)
                ws.complete = True
        #reset ws list to only not complete
        workspaces = [ws for ws in workspaces if ws.complete == False]
        #create new wss with remaining rss
        for rs in rsegs:
            newws = Space()
            newws.segs.append(rs)
            workspaces.append(newws)
for r in rooms:
    print(r)

输出(我也在打印组成每个房间的“空间”)是:

bathroom: [('C', 1), ('P', 0), ('S', 1), ('W', 0)]
[1:17-36, 2:17-36, 3:17-36, 4:17-36, 5:17-36, 6:17-36]
office: [('C', 0), ('P', 0), ('S', 1), ('W', 1)]
[1:1-16, 2:1-16, 3:1-16, 4:1-16, 5:1-16, 6:1-16, 7:1-12, 8:1-11, 9:1-10, 10:1-9]
bedroom: [('C', 0), ('P', 0), ('S', 1), ('W', 2)]
[5:37-48, 6:37-48, 7:37-48, 8:26-48, 9:26-48, 10:26-48, 11:26-48, 12:26-48, 13:26-48]
kitchen: [('C', 0), ('P', 2), ('S', 3), ('W', 1)]
[8:12-25, 9:11-25, 10:10-25, 11:9-25, 12:10-25, 13:11-25]
hallway: [('C', 2), ('P', 1), ('S', 0), ('W', 1)]
[1:49-59, 2:49-59, 3:49-59, 4:49-59, 5:49-59, 6:49-59, 7:49-59, 8:49-59, 9:49-59, 10:49-59, 11:49-59, 12:49-59, 13:49-59, 14:49-59, 15:1-59, 16:1-59, 17:1-59, 18:44-59, 19:45-59, 20:46-59, 21:47-59, 22:48-59]
balcony: [('C', 1), ('P', 2), ('S', 1), ('W', 0)]
[19:16-29, 20:16-29, 21:16-29, 22:16-29, 23:16-29, 24:9-29, 25:9-29]

【讨论】:

    【解决方案2】:

    我会建议类似:

    • 将计划视为网格

    • 用空格替换所有字母(和括号)

    • 将每个带有非空格字符的单元格设置为墙

    • 将第一个非墙单元设置为房间 1

      • 循环遍历该单元附近的“空气”单元并将它们设置为同一个房间,然后一次又一次地遍历相邻单元,直到找到墙壁
      • 完成后,取出剩余的单元格并将其设置为房间 X

    一遍又一遍地做,直到所有单元格都归属于不同的房间

    • 将原始计划(带有字母)与您找到的给定房间进行比较

    【讨论】:

      【解决方案3】:

      假设我从(非常简短且不清楚)问题陈述中推断出以下内容(请告知这些是否属实)。答案充其量是有方向的。

      1. ASCII计划存储在本地机器的txt文件中,需要从软件界面读取
      2. 平面图将始终为矩形,因为解决方案不包含任何其他形状
      3. 表示墙壁(| 和 / 和 -)角 (+) 的 ASCII 字符
      4. 房间将始终以圆括号命名,不会溢出到房间外,也不会写在与墙壁或房间外重叠的地方

        启发式

        房间和房间名称的搜索是基于 BFS 算法,使用这篇文章可以理解 https://medium.com/geekculture/breadth-first-search-bfs-algorithm-with-python-952aea707e93

        Python 解决方案

        导入 numpy 并从本地机器上的位置读取文件。 Maxlength 是矩形平面的长度

        floor_plan = []; 
        maxlength = 0;
        

        现在我们需要阅读整个计划,以便拥有一个我们将在其上运行 BFS 的对象

        
        with open('floorplan01.txt',encoding='utf-8-sig') as file:
            for line in file.read().splitlines():
                line_list = list(line)
                if(len(line_list) > maxlength):
                    maxlength = len(line_list)
                floor_plan.append(line_list)
        
        

        立即创建对象

        
        for _,row in enumerate(floor_plan):
            row.extend(list(' '*(maxlength - len(row))))
        
        

        创建要在 BFS 中使用的变量,如下所述。评论是自我解释的

        # seperator characters
        seperator = {'+', '-', '|', '/', '\'}
        
        #Number of Rows
        number_of_rows = len(floor_plan)
        
        #Number of Columns
        number_of_columns = len(floor_plan[0])
        
        #Matrix to keep track of points visited
        visited = np.zeros(shape=(number_of_rows, number_of_columns), dtype=bool)
        
        # contains a list of tuples; where the first element in tuple is room name and the second element is list of chairs in that room
        room_with_chairs = []
        
        # we store all the chairs present in the whole floor plan
        list_of_all_letters = []
        

        不要访问访问过的单元格或分隔符

        def shouldvisit(cell):
            x = cell[0]
            y = cell[1]
            return not(visited[x][y] or (floor_plan[x][y] in seperator))
        

        检查我们是否在对角线的角落,这本质上是检查两个给定的单元格是否包含分隔符

        def are_both_separators(cell1, cell2):
            return (floor_plan[cell1[0]][cell1[1]] in seperator) and (floor_plan[cell2[0]][cell2[1]] in seperator)
        

        BFS 需要知道它的邻居,我们在这里收集它们

        # gets all the valid adjacent cells which can be at most 8
        def get_neighbours(cell):
            x = cell[0]
            y = cell[1]
            neighbours = []
        
            # check for floor plan boundary
            y_0 = y > 0 #check if current position is not along left-most verticle edge of floor plan
            y_max = y < number_of_columns - 1 #check if current position is not along right-most verticle edge of floor plan
            x_0 = x > 0 #check if current position is not along top-most horizontal edge of floor plan
            x_max = x < number_of_rows - 1 #check if current position is not along bottom-most horizontal edge of floor plan
        
            # Below is the check for valid neighbours
            # The second condition is necessary because while checking a diagonal neighbour, we must make sure that
            # the cells along the other diagonal do not contain separators as in that case the diagonal neighbour would be on the other side of the boundary
            
            #check validity of i-1,j-1 & i-1,j+1
            if(x_0):
        
                if(y_0 and not are_both_separators((x-1, y), (x, y-1))):
                    neighbours.append((x-1, y-1))
                neighbours.append((x-1, y))
                if(y_max) and not are_both_separators((x-1, y), (x, y+1)):
                    neighbours.append((x-1, y+1))
        
            #check validity of i,j-1
            if(y_0):
                neighbours.append((x, y-1))
        
            #check validity of i,j+1
            if(y_max):
                neighbours.append((x, y+1))
        
            ##check validity of i+1,j-1 & i+1,j & i+1,j+1
            if(x_max):
                if(y_0 and not are_both_separators((x+1, y), (x, y-1))):
                    neighbours.append((x+1, y-1))
                neighbours.append((x+1, y))
                
                if(y_max and not are_both_separators((x+1, y), (x, y+1))):
                    neighbours.append((x+1, y+1))
            
            return neighbours
        

        现在我们搜索房间的名称

        def get_room_name(cell):
            x = cell[0]
            y = cell[1]
            row = ''.join(floor_plan[x]) #for string conversion
            l=y;
            r=y;
            while(l>0 and row[l]!='('):
                l-=1;
            while(r<len(row)-1 and row[r]!=')'):
                r+=1;
            return row[l+1:r]
        

        BFS

        def bfs(cell):
            queue = [cell]
            chairs = []
            area_name = None
            visited[cell[0]][cell[1]] = True    
            
            while(queue != []):
                curr_cell = queue.pop()
                x = curr_cell[0]
                y = curr_cell[1]
        
                curr_char = floor_plan[x][y]
                if(curr_char.isupper()):
                    chairs.append(curr_char)
        
                elif((curr_char.islower() or curr_char == '(' or curr_char == ')') and area_name == None):
                    area_name = get_room_name(curr_cell)
        
                for neighbour in get_neighbours(curr_cell):
                    if(shouldvisit(neighbour)):
                        visited[neighbour[0]][neighbour[1]] = True
                        queue.append(neighbour)
            return (area_name, chairs)
        

        现在按计划运行 BFS

        # Doing BFS over every unvisited non-separator cell
        for i, row in enumerate(floor_plan):
            for j, _ in enumerate(row):
                if(shouldvisit((i, j))):
                    local_outcome = bfs((i, j))
                    if(local_outcome[0] != None):
                        room_with_chairs.append(local_outcome)
                        list_of_all_letters.extend(local_outcome[1])
        

        这样就完成了识别

      【讨论】:

        猜你喜欢
        • 2017-12-31
        • 1970-01-01
        • 2015-08-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-01-15
        相关资源
        最近更新 更多