Haskell 按合并单元格的构造表UI组件数据结构有效

Haskell 按合并单元格的构造表UI组件数据结构有效,haskell,data-structures,html-table,spreadsheet,Haskell,Data Structures,Html Table,Spreadsheet,TL;DR:我正在寻找一组不重叠的轴对齐整数矩形的数据结构 我正在制作一个终端用户界面†,其中包括渲染表。它们类似于HTML表格或电子表格单元格,因为相邻单元格可以合并成行和/或列。一旦我积累了所有的约束条件,我已经知道如何根据单元格维度的内容来求解它们,并将结果呈现为UI组件。但是,约束生成器的输入当前是一个基于HTML表的数据结构:一个单元格行列表,其中每个单元格都有一个“row span”和“column span”属性 data Table1 a = Table1 { table1Row

TL;DR:我正在寻找一组不重叠的轴对齐整数矩形的数据结构

我正在制作一个终端用户界面†,其中包括渲染表。它们类似于HTML表格或电子表格单元格,因为相邻单元格可以合并成行和/或列。一旦我积累了所有的约束条件,我已经知道如何根据单元格维度的内容来求解它们,并将结果呈现为UI组件。但是,约束生成器的输入当前是一个基于HTML表的数据结构:一个单元格行列表,其中每个单元格都有一个“row span”和“column span”属性

data Table1 a = Table1 { table1Rows :: [Row1 a] }

data Row1 a = Row1 { row1Cells :: [Cell1 a] }

data Cell1 a = Cell1
  { cell1Rowspan :: !Rowspan
  , cell1Colspan :: !Colspan
  , cell1Contents :: !a
  }

newtype Rowspan = Rowspan Int

newtype Colspan = Colspan Int
这不仅有点笨拙,而且还允许出现各种我无法呈现的无效表(或与我的目的无关的表)。如果有一种相对简单的方法来使用类型系统强制执行结构,那么对于正确性保证和自动化测试将非常有用<代码>表1有一些问题,例如:

  • 每行或每列中的单元格位置总数可能不同,因此表格可能是锯齿状的

  • 计算单元格位置需要对整个表进行线性遍历,因为前一行中的合并单元格可能占据当前行中的单元格原本所在的位置

  • 我不能简单地遍历一行或一列中的所有单元格来累积高度/宽度约束

因此,我的下一个想法是使用一个二维单元格数组,其中每个位置要么有一些内容,要么标记为与上面或左边的单元格“合并”,或者两者都有:

data Table2 a = Table2
  { table2Cells :: Array (RowIndex, ColumnIndex) (Cell2 a) }

data Cell2 a
  = Cell2Content !a
  | Cell2MergeUp
  | Cell2MergeLeft
  | Cell2MergeUpLeft

newtype RowIndex = RowIndex Int

newtype ColumnIndex = ColumnIndex Int
不幸的是,这仍然允许使用一些无效的表,因为它不要求合并的单元格为正方形:

render $ Table2 $ array ((0, 0), (1, 1))
  [ ((0, 0), Cell2Content "...")
  , ((0, 1), Cell2MergeLeft)
  , ((1, 0), Cell2MergeUp)
  , ((1, 1), Cell2Content "...")
  ]

接下来,我想到了从单元格范围到单元格的映射,但我意识到我仍然需要按位置进行索引,并使用rowspan和colspan进行注释

data Table3 = Table3
  { table3Cells :: Map (RowIndex, ColumnIndex) (Cell1 a) }
  deriving stock (Show)
这允许表是稀疏的,但严格来说这不是一个问题,因为我所有的表都是完全压缩/矩形的,我可以将不存在的单元格渲染为空。它还将合并的单元约束为矩形,这是需要的。不幸的是,它允许单元格错误地重叠,例如,(0,0)处的2×2单元格将与(1,1)处的2×2单元格碰撞。我不能把宽度和高度输入地图键

我认为某种集合或关联结构是有意义的,但我有一个“复合”键,而不是一个单一的“主键”(位置,在上面的
表3
中),因为没有两个单元格可以重叠相同的位置。换言之,如果一个单元格与另一个单元格重叠同一行或列,则该单元格不得分别在其中的任何列或行中重叠。(显然,许多单元格将具有相同的行或列:同一行中的每个单元格都具有相同的行索引!)

那么,有没有一种简单的方法可以强制执行?如果我不能使用例如
数组
/
映射
/
IntMap
,是否有可用的库可以让我以这样的多个维度存储一组不重叠的“占用范围”?只要我可以对运行时指定的数据强制执行约束,我就可以使用“高级”类型的系统特性。我可以在每次输入更改时从头开始生成表,只要它是有效的,但如果在继续强制执行约束的同时很容易修改表(即插入新行/单元格),则这是一个优点

如果没有合适的数据结构,我可能只制作一个在运行时使用智能构造函数强制执行约束的数据结构,但这并不有趣。:)

我发现一个简单的运行时选项是从单元格ID到单元格维度的
IntMap
(或者将行维度、列维度和单元格内容的
IntMap
s分开);然后,安全插入可以实现为直接不安全地插入到映射中,然后检查重叠,即冲突行和列的映射的交集(通过集合并集),由每行(对应列)中的(非空)单元格集和每列(对应行)中重叠的不同单元格给出


†使用,但这不应该是相关的。

我强烈怀疑,除了运行时强制之外,任何东西都会把你的生命力吸引到第二维度。@DanielWagner:是的,似乎是这样。我想知道是否已经编写了足够相似的数据结构,这样我就不必自己编写所有的不变量了。类似于间隔树或空间分区结构。似乎“一组不重叠的n-D整数范围,并附上数据”在某个时候应该是有人需要的。这或是映射键上的一个非常聪明的
Ord
实例。我突然想到,由于我的表很密集,我可以使用一个二元(或n元)空间分区树:通过第一个“切分”进行细分,你可以找到这个切分贯穿整个表(或子表),一路存储大小。我不确定它是否值得,因为它显然不比行列表或单元格数组简单,但如果可行,我会自我回答。我认为它不可行。想象一下四个多米诺骨牌,描述一个“圆”——好的,矩形——中间有一个单元格洞。桌子上没有垂直或水平的切口。@DanielWagner:啊,说得对,你说得对。这是一种分区,但我不知道是什么。
data Table3 = Table3
  { table3Cells :: Map (RowIndex, ColumnIndex) (Cell1 a) }
  deriving stock (Show)