Ada中如何正确约束二维数组类型为方阵?
我正在开发一个用于定义图的通用邻接矩阵包,首先实现了基于二维数组的静态版本:
初始代码实现
包规范
with Ada.Containers.Vectors; generic type Weight_Type is private; with function "=" (Left, Right : Weight_Type) return Boolean; No_Edge_Value : Weight_Type; package Generic_Adjacency_Matrices is type Static_Adjacency_Matrix is array (Positive range <>, Positive range <>) of Weight_Type; function Create (Vertices : Positive) return Static_Adjacency_Matrix; end Generic_Adjacency_Matrices;
包体
package body Generic_Adjacency_Matrices is function Create (Vertices : Positive) return Static_Adjacency_Matrix is AM : Static_Adjacency_Matrix (1 .. Vertices, 1 .. Vertices); begin for I in AM'Range(1) loop for J in AM'Range(2) loop AM(I, J) := No_Edge_Value; end loop; end loop; return AM; end Create; end Generic_Adjacency_Matrices;
需求:确保矩阵为方阵
我希望强制约束Static_Adjacency_Matrix必须是方阵(两个维度的范围完全一致)。根据Ada手册的说明:
Static_Predicate的作用类似约束,除未初始化变量或无效值外,子类型的所有对象始终满足断言;而Dynamic_Predicate仅在指定时机检查,其他情况下可能失效(比如修改记录子组件时不会触发检查)。
因为矩阵的维度在声明时就已确定,我认为使用Static_Predicate是更合理的选择,于是尝试修改类型声明:
with Ada.Containers.Vectors; generic type Weight_Type is private; with function "=" (Left, Right : Weight_Type) return Boolean; No_Edge_Value : Weight_Type; package Generic_Adjacency_Matrices is type Static_Adjacency_Matrix is array (Positive range <>, Positive range <>) of Weight_Type with Static_Predicate => Static_Adjacency_Matrix'First(1) = Static_Adjacency_Matrix'First(2) and Static_Adjacency_Matrix'Last(1) = Static_Adjacency_Matrix'Last(2); function Create (Vertices : Positive) return Static_Adjacency_Matrix; end Generic_Adjacency_Matrices;
但这种写法是非法的:Static_Predicate无法使用'Range、'First和'Last属性。
虽然Dynamic_Predicate支持这种写法,但我想寻找更合适的替代方案。比如将类型声明为私有,强制通过Create函数生成方阵,但我不喜欢这种方式——类型本身没有私有必要,还需要额外定义访问过程。
可行的替代方案
方案1:使用一维数组模拟方阵
将方阵存储为一维数组,通过索引转换函数实现二维访问,从类型层面保证存储的是方阵(数组长度必须是整数的平方):
包规范修改
with Ada.Containers.Vectors; generic type Weight_Type is private; with function "=" (Left, Right : Weight_Type) return Boolean; No_Edge_Value : Weight_Type; package Generic_Adjacency_Matrices is type Static_Adjacency_Matrix is array (Positive range <>) of Weight_Type; function Create (Vertices : Positive) return Static_Adjacency_Matrix; -- 二维访问接口 function Get (Matrix : Static_Adjacency_Matrix; I, J : Positive) return Weight_Type; procedure Set (Matrix : in out Static_Adjacency_Matrix; I, J : Positive; Value : Weight_Type); -- 获取矩阵的顶点数 function Vertex_Count (Matrix : Static_Adjacency_Matrix) return Positive; end Generic_Adjacency_Matrices;
核心实现示例
package body Generic_Adjacency_Matrices is function Create (Vertices : Positive) return Static_Adjacency_Matrix is Size : constant Positive := Vertices * Vertices; AM : Static_Adjacency_Matrix (1 .. Size); begin for Index in AM'Range loop AM(Index) := No_Edge_Value; end loop; return AM; end Create; function Vertex_Count (Matrix : Static_Adjacency_Matrix) return Positive is use Ada.Numerics.Elementary_Functions; Count : constant Float := Sqrt(Float(Matrix'Length)); begin if Count /= Float(Integer(Count)) then raise Constraint_Error with "Not a valid square matrix"; end if; return Positive(Integer(Count)); end Vertex_Count; function Get (Matrix : Static_Adjacency_Matrix; I, J : Positive) return Weight_Type is Count : constant Positive := Vertex_Count(Matrix); begin if I not in 1 .. Count or J not in 1 .. Count then raise Constraint_Error with "Index out of bounds"; end if; return Matrix((I - 1) * Count + J); end Get; procedure Set (Matrix : in out Static_Adjacency_Matrix; I, J : Positive; Value : Weight_Type) is Count : constant Positive := Vertex_Count(Matrix); begin if I not in 1 .. Count or J not in 1 .. Count then raise Constraint_Error with "Index out of bounds"; end if; Matrix((I - 1) * Count + J) := Value; end Set; end Generic_Adjacency_Matrices;
这种方式从存储结构上保证了方阵特性,同时通过封装的访问函数提供二维数组的使用体验。
方案2:使用Dynamic_Predicate做动态检查
虽然Dynamic_Predicate是运行时检查,但由于矩阵的维度一旦声明就不会改变(数组的范围是静态约束的),因此只要在创建和赋值时检查一次,就能长期保证方阵特性:
with Ada.Containers.Vectors; generic type Weight_Type is private; with function "=" (Left, Right : Weight_Type) return Boolean; No_Edge_Value : Weight_Type; package Generic_Adjacency_Matrices is type Static_Adjacency_Matrix is array (Positive range <>, Positive range <>) of Weight_Type with Dynamic_Predicate => Static_Adjacency_Matrix'First(1) = Static_Adjacency_Matrix'First(2) and Static_Adjacency_Matrix'Last(1) = Static_Adjacency_Matrix'Last(2); function Create (Vertices : Positive) return Static_Adjacency_Matrix; end Generic_Adjacency_Matrices;
当用户尝试手动创建非方阵的实例时,会触发运行时Constraint_Error;而通过Create函数生成的实例天然是方阵,不会有问题。这种方案最简单,不需要额外封装,适合大部分场景。
方案3:私有类型+数组视图(折中方案)
如果既想强制方阵约束,又想保留二维数组的直接访问体验,可以将类型声明为私有,同时提供数组视图的访问接口:
包规范
with Ada.Containers.Vectors; generic type Weight_Type is private; with function "=" (Left, Right : Weight_Type) return Boolean; No_Edge_Value : Weight_Type; package Generic_Adjacency_Matrices is type Static_Adjacency_Matrix is private; function Create (Vertices : Positive) return Static_Adjacency_Matrix; -- 提供常量视图支持下标访问 type Constant_Matrix_View is array (Positive range <>, Positive range <>) of Weight_Type with Constant_Indexing => Get; function Get (Matrix : Static_Adjacency_Matrix; I, J : Positive) return Weight_Type; function View (Matrix : Static_Adjacency_Matrix) return Constant_Matrix_View; -- 可变版本的访问接口 procedure Set (Matrix : in out Static_Adjacency_Matrix; I, J : Positive; Value : Weight_Type); private type Static_Adjacency_Matrix is array (Positive range <>, Positive range <>) of Weight_Type; end Generic_Adjacency_Matrices;
包体核心实现
package body Generic_Adjacency_Matrices is function Create (Vertices : Positive) return Static_Adjacency_Matrix is AM : Static_Adjacency_Matrix (1 .. Vertices, 1 .. Vertices); begin for I in AM'Range(1) loop for J in AM'Range(2) loop AM(I, J) := No_Edge_Value; end loop; end loop; return AM; end Create; function Get (Matrix : Static_Adjacency_Matrix; I, J : Positive) return Weight_Type is begin return Matrix(I, J); end Get; function View (Matrix : Static_Adjacency_Matrix) return Constant_Matrix_View is begin return Matrix; end View; procedure Set (Matrix : in out Static_Adjacency_Matrix; I, J : Positive; Value : Weight_Type) is begin Matrix(I, J) := Value; end Set; end Generic_Adjacency_Matrices;
这种方案既阻止了用户直接创建非方阵实例,又通过View函数和Constant_Indexing特性,让用户可以像使用普通二维数组一样访问矩阵元素。
内容的提问来源于stack exchange,提问作者tymurmchyk

