【TSP数据集】用于 加载ATSP和TSP 测试样例的python加载器【使用所有类型数据】【使用数据给出方法计算邻接矩阵和邻接表】

用于 加载ATSP和TSP 测试样例的python加载器【使用所有类型数据】【使用数据给出方法计算邻接矩阵和邻接表】。全部文档和代码开源在github,pypi也有。

class TSP_DATA:
  '''用于读取数据、存放图的数据结构

  类内变量:
    必有:
      self.NAME         测试样例名称
      self.TYPE         测试样例类型  TSP ATSP
      self.DIMENSION.      维度
      self.EDGE_WEIGHT_TYPE   边权值计算方式 决定读取方式 
      self.matrix        矩阵形式数据
      self.table .       邻接表形式数据

    可能有:
      self.EDGE_WEIGHT_FORMAT
      self.EDGE_DATA_FORMAT
      self.NODE_COORD_TYPE required if EDGE_WEIGHT_TYPE is not WeightKind::Explicit
  
  '''
  # 初始化,读取数据,并获取表头的数据规约
  def __init__(self, path):
    """传入单个数据地址,读取并且加载数据的表头
    path:
      单个测试样例的数据地址
    """
    self.debug = True
    # 读取数据文件
    raw_data, name = self.read_any_file(path)
    # # 无论如何名字都是第0行
    # self.name = raw_data.split(':')[1]
    self.get_headline(raw_data)
    # 保证数据名称和文件名称相同
    assert self.NAME == name
    # 保证是TSP问题的文件
    assert 'TSP' in self.TYPE
    # 有数据区块 长条list
    self.rawnum = self.get_data_list(raw_data)
    # 计算邻接矩阵 和 邻接表
    if self.debug:
      print("原始数据:",self.rawnum)
    if self.EDGE_WEIGHT_TYPE == "EXPLICIT":
      self.matrix = self.get_adjacency_matrix()
      if self.debug:
        print("矩阵",self.matrix)
      # self.table = self.matrix_2_table()
    # else:
    #   coorodinate_table = self.get_coorodinate_table_from_raw()
    #   self.table = self.get_adjacency_table()
    #   self.matrix = self.table_2_matrix()


  def get_data_list(self, data_all):
    """ 返回数字数据,被关键字和EOF包裹。不做其他处理
    EXPLICIT EDGE_WEIGHT_SECTION 
      DGE_WEIGHT_SECTION
      0  273    0 1272  999    0  744  809 1519    0 1138  866  140 1425

    EUC_2D  NODE_COORD_SECTION
      NODE_COORD_SECTION
      1 4.35841e+02 5.87522e+02
      2 6.02539e+02 8.01704e+02
      有丢失城市的情况,补无穷
    
    return:
      EXPLICIT 一长条int的list,空格隔开
      else 三列 n行 list(划掉)
      else 一长条int的list,空格隔开
    
    update:
      数据还有浮点数,得改一下
      可能有‘  699'这样的数据,不能用数字判别,应该用 非字母逻辑
      
    """
    end_line = len(data_all)-2 # 去掉EOF -1 从零开始 -1
    print(data_all)

    # 距离矩阵
    if self.EDGE_WEIGHT_TYPE == 'EXPLICIT':
      # 此时是n*1的
      beging_line = 0
      
      for i in range(10):
        if 'EDGE_WEIGHT_SECTION' in data_all[0][i]:
          # 下一位开始是数据
          beging_line = i+1
          break
        else:
          pass
          #raise Exception("EDGE_WEIGHT_SECTION 关键字未找到")
      if self.debug:
        print("发现数据开头标志位置",beging_line)

      string_data = data_all[0][beging_line]
      for h in range(beging_line+1,end_line+1):
        # 清洗掉其他标签,如可视化等
        if data_all[0][h][0].isalpha():
          # 如果这条数据是字母,那么中断循环。由于数字有空格,不能用数字判别
          break
        # todo 中间是不是要人为加空格
        string_data = string_data + " " + data_all[0][h]
      string_data_split = string_data.strip().split(' ')
      list_data =  [float(i) for i in string_data_split if i.isdigit()]
      if self.debug:
        print(string_data_split)
        print(list_data)
    # 坐标
    else: 
      beging_line = 0
      # if not 'NODE_COORD_SECTION' in data_all[0]:
      #   raise Exception("NODE_COORD_SECTION 关键字未找到")
      for i in range(10):
        beging_line = beging_line+1
        if 'NODE_COORD_SECTION' in data_all[0][i]:
          break
              
      # 第一条单独获取
      string_data = data_all[0][beging_line]
      # 获取其余数字
      for h in range(beging_line+1,end_line+1):
        # 清洗掉其他标签,如可视化等
        if data_all[0][h][0].isalpha():
          # 如果这条数据是字母,那么中断循环。由于数字有空格,不能用数字判别
          break
        # todo 中间是不是要人为加空格
        string_data = string_data + ' ' + data_all[0][h]

      if self.debug:
        print("发现数据开头标志位置",beging_line)
        print("数据")
        print(data_all[0][0])
      string_data_split = string_data.strip().split(' ')
      # 使用浮点数 1e2就不用手动转换了      
      list_data =  [float(i) for i in string_data_split if i.isdigit()]
      if self.debug:
        print(string_data_split)
        print(list_data)

    # 这里取了简便 #todo
  
    return list_data

  # 获取表头数据规约
  def get_headline(self, raw_data):
    head_dict = {}
    head_index = 0
    # 获取字母表头(好像没啥用,但是逻辑更通畅了)
    for h in range(len(raw_data)):
      if not raw_data[0][h][0].isalpha():
        break
      else:
        head_index = head_index + 1
      
    # 最多只有10个类型的表头
    for i in range(head_index):
      if ':' not in raw_data[0][i]:
        break
      # 构建字典
      head_dict[raw_data[0][i].split(':')[0]] = raw_data[0][i].split(':')[1]
    
    # 从字典获取信息,去掉左右空格
    self.NAME = head_dict['NAME'].strip()
    self.TYPE = head_dict['TYPE'].strip()
    self.DIMENSION = int(head_dict['DIMENSION'].strip())
    self.EDGE_WEIGHT_TYPE = head_dict['EDGE_WEIGHT_TYPE'].strip()
    if self.TYPE == "CVRP": 
      # 载货问题时,需要卡车容量
      self.CAPACITY = head_dict['CAPACITY'].strip()
    else:
      pass

    if self.EDGE_WEIGHT_TYPE == "EXPLICIT": 
      # 距离在raw code中指定的情况下,要给出邻接矩阵存放方式
      # 数据开头  EDGE_WEIGHT_SECTION
      # 比如 UPPER_DIAG_ROW 上三角矩阵
      self.EDGE_WEIGHT_FORMAT = head_dict['EDGE_WEIGHT_FORMAT'].strip()
    else:
      if 'NODE_COORD_TYPE' in head_dict.keys():
        self.NODE_COORD_TYPE = head_dict['NODE_COORD_TYPE'].strip()
      # 但实际很多都没有
      # 理论上要满足: NODE_COORD_TYPE (required if EDGE_WEIGHT_TYPE is not WeightKind::Explicit): specifies how the coordinate for each node is given in the file. Represented by the enum CoordKind.
      # 数据开头:  NODE_COORD_SECTION
      pass
    
  # 读取文件,不予处理
  def read_any_file(self,thispath):
    # 判断是否是数据集
    if not os.path.isfile(thispath):
      raise Exception('数据读取报错,地址非文件')
    # 读取文件 raw数据
    df = pd.read_csv(thispath,header=None)
    # 文件名
    if check_sys == 'Linux':   # colab address
      name = thispath.split('/')[-1].split('.')[0]
    elif check_sys == 'Windows': # local address 
      name = thispath.split('\\')[-1].split('.')[0]
    else:
      raise EnvironmentError
    return df,name
  
  # 根据 EDGE_WEIGHT_TYPE 距离计算方式,返回距离
  def cal_a_distance_by_coordinate(self,a,b):
    '''
    按照 城市编号 a -> b ,给出两点之间的距离
    可能的距离定义式:https://docs.rs/tspf/0.3.0/tspf/enum.WeightKind.html
      Explicit   权重在数据文件中明确给出。直接读出来就是距离。
      Euc2d    二维欧几里得距离。
      Euc3d    三维欧几里得距离。
      Max2d    二维马氏距离。????这是衡量矩阵的????
      Max3d    三维马氏距离。
      Man2d    二维曼哈顿距离。
      Man3d    三维曼哈顿距离。
      Ceil2d    向上取整的二维欧几里得距离。
      Geo     地理距离。 既欧氏距离
      Att     问题的特殊距离函数att48和att532。
      Xray1    版本 1 晶体学问题的特殊距离函数。
      Xray2    版本 2 晶体学问题的特殊距离函数。
      Custom    用户定义的距离函数。
      Undefined  没有给出距离函数。
    '''
    if self.EDGE_WEIGHT_TYPE == "EUC_2D" or self.EDGE_WEIGHT_TYPE =="GEO":
      distance = (self.adjacency_table(a)[0]-self.adjacency_table(b)[0])**2+\
      (self.adjacency_table(a)[1]-self.adjacency_table(b)[1])**2

    elif self.EDGE_WEIGHT_TYPE == "EUC_3D":
      distance = abs((self.adjacency_table(a)[0]-self.adjacency_table(b)[0])**3+\
      (self.adjacency_table(a)[1]-self.adjacency_table(b)[1])**3)

    elif self.EDGE_WEIGHT_TYPE == "EXPLICIT":
      raise Exception("EXPLICIT距离必须由raw data的邻接矩阵给出")

    elif self.EDGE_WEIGHT_TYPE == "MAN_2D":
      distance = abs((self.adjacency_table(a)[0]-self.adjacency_table(a)[1])**2-\
      (self.adjacency_table(b)[0]-self.adjacency_table(b)[1])**2)

    elif self.EDGE_WEIGHT_TYPE == "MAN_3D":
      distance = abs((self.adjacency_table(a)[0]-self.adjacency_table(a)[1])**3-\
      (self.adjacency_table(b)[0]-self.adjacency_table(b)[1])**3)

    elif self.EDGE_WEIGHT_TYPE == "CEIL_2D":
      distance = int((self.adjacency_table(a)[0]-self.adjacency_table(b)[0])**2+\
      (self.adjacency_table(a)[1]-self.adjacency_table(b)[1])**2) + 1
    else: # 都不符合
      raise Exception("此类型距离未定义")
    return distance

  # 根据 TYPE 进行判断,返回邻接矩阵
  def get_adjacency_matrix(self):
    '''专用于TSP问题的原始数据转矩阵函数(由于ATSP距离不对称,优化不能用)
          
      当确认矩阵储存方式 EDGE_WEIGHT_TYPE 为 邻接矩阵 EXPLICIT 时
      self.EDGE_WEIGHT_FORMAT 保存矩阵储存格式
      self.rawnum 保存raw data中的所有数字
    矩阵存放形式
      Function    权重由WeightKind中所述的函数计算。
    FullMatrix   权重以完整矩阵形式给出。对应FULL_MATRIX于 TSPLIB 中的值。
    UpperRow    权重在上三角矩阵中按行给出,没有对角线条目。  对应UPPER_ROW于 TSPLIB 中的值。
    LowerRow    权重在下三角矩阵中给出,按行排列,没有对角线条目。 对应LOWE_ROW于 TSPLIB 中的值。
    UpperDiagRow  权重在上三角矩阵中给出,按行与对角线条目。  对应UPPER_DIAG_ROW于 TSPLIB 中的值。
    LowerDiagRow  权重在下三角矩阵中给出,按行与对角线条目。  对应LOWER_DIAG_ROW于 TSPLIB 中的值。
      UpperCol    权重在上三角矩阵中给出,没有对角线条目。  对应UPPER_COL于 TSPLIB 中的值。
      LowerCol    权重在下三角矩阵中给出,没有对角线条目。  对应LOWER_COL于 TSPLIB 中的值。
      UpperDiagCol  权重在上三角矩阵中给出,与对角线条目并列。  对应UPPER_DIAG_COL于 TSPLIB 中的值。
      LowerDiagCol  权重在下三角矩阵中给出,与对角线条目并列。  对应LOWER_DIAG_COL于 TSPLIB 中的值。
    Undefined    没有说明如何存储权重。
    '''
    # if self.TYPE == "TSP":
    matrix = []
    # 上三角,包含对角全0
    if self.EDGE_WEIGHT_FORMAT == "UPPER_DIAG_ROW":
      # 数据长度应该是:d + d-1 + d-2 +...+ 1 = (1+d)*d/2
      d = self.DIMENSION
      if not len(self.rawnum) == int((1+d)*d/2):
        raise Exception("获取的raw num数据量不对")
      for i in range(d):
        # 由于自己有0,第一行不填0,第二行补一个,第n行补n-1个
        row = [0 for j in range(i)]
        # left = sum 0 d d-1 \
        left = int( i*d-(0+i-1)*i/2 )
        right = int( (i+1)*d-(0+i)*(i+1)/2 )
        # right = sum d d-1 d-2 ... = i*d-(0+i-1)*i/2
        row = row + self.rawnum[left : right]
        matrix.append(row)
      # 上三角补全到完整矩阵
      m = np.array(matrix)
      matrix = (np.triu(m, k=1).T + np.triu(m, k=0)).tolist()

    # 下三角 包括对角线 LOWER_DIAG_ROW
    if self.EDGE_WEIGHT_FORMAT == "LOWER_DIAG_ROW":
      # 数据长度应该是:d + d-1 + d-2 +...+ 1 = (1+d)*d/2
      d = self.DIMENSION
      # 转化为上三角
      self.rawnum = self.rawnum.reverse()
      if not len(self.rawnum) == int((1+d)*d/2):
        raise Exception("获取的raw num数据量不对")
      for i in range(d):
        # 由于自己有0,第一行不填0,第二行补一个,第n行补n-1个
        row = [0 for j in range(i)]
        # left = sum 0 d d-1 \
        left = int( i*d-(0+i-1)*i/2 )
        right = int( (i+1)*d-(0+i)*(i+1)/2 )
        # right = sum d d-1 d-2 ... = i*d-(0+i-1)*i/2
        row = row + self.rawnum[left : right]
        matrix.append(row)
      # 下三角补全到完整矩阵
      m = np.array(matrix)
      matrix = (np.tril(m, k=1).T + np.tril(m, k=0)).tolist()

    # 上三角,不包含对角全0
    elif self.EDGE_WEIGHT_FORMAT == "UPPER_ROW":
      # 数据长度应该是:d + d-1 + d-2 +...+ 1 = (1+d)*d/2
      d = self.DIMENSION
      if not len(self.rawnum) == int((1+d)*d/2-d):
        raise Exception("获取的raw num数据量不对")
      for i in range(d):
        # 由于自己有0,第一行填1个0,第二行补2个,第n行补n个
        row = [0 for j in range(i+1)]
        # left = sum 0 d-1 d-2 
        if i == 0:
          left = 0
        else:
          left = int( d*(i-1) -(i-1)*i/2 )
        # right = sum d-1 d-2 d-3 ... = int(d*i -(1+i)i/2)
        right = int( d*i -(1+i)*i/2 )
        row = row + self.rawnum[left : right]
        matrix.append(row)
      # 上三角补全到完整矩阵
      m = np.array(matrix)
      matrix = (np.triu(m, k=1).T + np.triu(m, k=0)).tolist()
    
    # 下三角,不包含对角全0
    elif self.EDGE_WEIGHT_FORMAT == "LOWER_ROW":
      # 数据长度应该是:d + d-1 + d-2 +...+ 1 = (1+d)*d/2
      d = self.DIMENSION
      self.rawnum = self.rawnum.reverse()
      if not len(self.rawnum) == int((1+d)*d/2-d):
        raise Exception("获取的raw num数据量不对")
      for i in range(d):
        # 由于自己有0,第一行填1个0,第二行补2个,第n行补n个
        row = [0 for j in range(i+1)]
        # left = sum 0 d-1 d-2 
        if i == 0:
          left = 0
        else:
          left = int( d*(i-1) -(i-1)*i/2 )
        # right = sum d-1 d-2 d-3 ... = int(d*i -(1+i)i/2)
        right = int( d*i -(1+i)*i/2 )
        row = row + self.rawnum[left : right]
        matrix.append(row)
      # 下三角补全到完整矩阵
      m = np.array(matrix)
      matrix = (np.tril(m, k=1).T + np.tril(m, k=0)).tolist()

    elif self.EDGE_WEIGHT_FORMAT == "FULL_MATRIX":
      # 数据长度应该是:d**2
      d = self.DIMENSION
      if not len(self.rawnum) == int(d**2):
        raise Exception("获取的raw num数据量不对")
      for i in range(d):
        row = self.rawnum[i:i+d]
        matrix.append(row)

    elif self.EDGE_WEIGHT_FORMAT == "UNDIFINED":
      d = self.DIMENSION
      # 进行辨识
      #  "FULL_MATRIX"
      if len(self.rawnum) == int(d**2):
        for i in range(d):
          row = self.rawnum[i:i+d]
          matrix.append(row)
      # 下三角,不包含对角全0
      elif len(self.rawnum) == int((1+d)*d/2-d):
        for i in range(d):
          # 由于自己有0,第一行填1个0,第二行补2个,第n行补n个
          row = [0 for j in range(i+1)]
          # left = sum 0 d-1 d-2 
          if i == 0:
            left = 0
          else:
            left = int( d*(i-1) -(i-1)*i/2 )
          # right = sum d-1 d-2 d-3 ... = int(d*i -(1+i)i/2)
          right = int( d*i -(1+i)*i/2 )
          row = row + self.rawnum[left : right]
          matrix.append(row)
        # 上三角补全到完整矩阵
        m = np.array(matrix)
        matrix = (np.triu(m, k=1).T + np.triu(m, k=0)).tolist()
      # 上下三角,不包含对角全0
      elif len(self.rawnum) == int((1+d)*d/2-d):
        if self.rawnum[2] == 0:
          # 下三角
          for i in range(d):
            # 由于自己有0,第一行填1个0,第二行补2个,第n行补n个
            row = [0 for j in range(i+1)]
            # left = sum 0 d-1 d-2 
            if i == 0:
              left = 0
            else:
              left = int( d*(i-1) -(i-1)*i/2 )
            # right = sum d-1 d-2 d-3 ... = int(d*i -(1+i)i/2)
            right = int( d*i -(1+i)*i/2 )
            row = row + self.rawnum[left : right]
            matrix.append(row)
          # 上三角补全到完整矩阵
          m = np.array(matrix)
          matrix = (np.tril(m, k=1).T + np.tril(m, k=0)).tolist()
        else:
          for i in range(d):
            # 由于自己有0,第一行不填0,第二行补一个,第n行补n-1个
            row = [0 for j in range(i)]
            # left = sum 0 d d-1 \
            left = int( i*d-(0+i-1)*i/2 )
            right = int( (i+1)*d-(0+i)*(i+1)/2 )
            # right = sum d d-1 d-2 ... = i*d-(0+i-1)*i/2
            row = row + self.rawnum[left : right]
            matrix.append(row)
          # 上三角补全到完整矩阵
          m = np.array(matrix)
          matrix = (np.triu(m, k=1).T + np.triu(m, k=0)).tolist()
      else:
        raise Exception("储存矩阵形式为定义,且未辨识成功,长度不符合已有类型")

    return matrix
  
  # 坐标表转换邻接表
  # 根据数据类型,返回数据的邻接表,有的算法需要这个
  def coordinate_2_table(self, optimize=True):
    """
    当数据为坐标地图:
      坐标表方式储存,每个x->y都需要计算。
      规定城市编号从一开始
    进一步:
      #todo
      优化计算,只用算一半
    retrun:
      字典好查询
      或者是表
    optimize:
      是否使用优化计算方法获取矩阵
    """
    whole_table = {}
    # ATSP 问题邻接矩阵不对称
    if optimize and self.TYPE != 'ATSP':
      for i in range(1,1+self.DIMENSION):
        for j in range(1,1+self.DIMENSION):
          if j >= i :
            whole_table[(i,j)] = self.cal_a_distance_by_coordinate(i,j)
      # 减小计算量(#todo能不能用矩阵对称算法?矩阵转置,然后相加)
      for i in range(1,1+self.DIMENSION):
        for j in range(1,1+self.DIMENSION):
          if j < i :
            whole_table[(i,j)] = whole_table[(j,i)]
    else:
      for i in range(1,1+self.DIMENSION):
        for j in range(1,1+self.DIMENSION):
          whole_table[(i,j)] = self.cal_a_distance_by_coordinate(i,j)

    return whole_table

  def coordinate_2_matrix(self, optimize=True):
    """
    当数据为坐标地图:
      坐标表方式储存,每个x->y都需要计算。
      规定城市编号从一开始
      第一行意为:从城市1到。。。
      注意索引是0开始
    进一步:
      #todo
      优化计算,只用算一半
      使用numpy,对角矩阵获取,转置,相加,即可
    """
    whole_matrix = []
    if optimize and self.TYPE != 'ATSP':
      for i in range(1,1+self.DIMENSION):
        thisRow = []
        for j in range(1,1+self.DIMENSION):
          if i<j: #上三角
            thisRow.appned(self.cal_a_distance_by_coordinate(i,j))
          else:
            thisRow.appned(0)
        whole_matrix.append(thisRow)
      # 上三角补全到完整矩阵
      m = np.array(whole_matrix)
      whole_matrix = (np.triu(m, k=1).T + np.triu(m, k=0)).tolist()
    else:
      for i in range(1,1+self.DIMENSION):
        thisRow = []
        for j in range(1,1+self.DIMENSION):
          thisRow.appned(self.cal_a_distance_by_coordinate(i,j))
        whole_matrix.append(thisRow)

    return whole_matrix

  # 坐标表转换到矩阵
  def table_2_matrix(self,table):
    """
    table:
      字典类型,x-》y索引距离
    """
    whole_matrix = []
    for i in range(1,1+self.DIMENSION):
      thisRow = []
      for j in range(1,1+self.DIMENSION):
        thisRow.appned(table[(i,j)])
      whole_matrix.append(thisRow)

    return whole_matrix

  # 矩阵转邻接表
  def matex_2_table(self,matrix):
    """   
      矩阵储存, 需要提取表
      区分情况,是否对称等
    """
    whole_table = {}
    for i in range(1,1+self.DIMENSION):
      for j in range(1,1+self.DIMENSION):
        whole_table[(i,j)] = matrix[i-1][j-1]

    return whole_table
    
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值