2022研究生数模竞赛B题子问题一

最低水平线搜索算法

总的说明

具体题目可以直接去官网下载,子问题一主要是一个二维装箱问题,对大的矩形长宽都有限制,求解这类问题主要思路是排样规则和排样顺序两个方面。排样规则方面有BL、BLF、最低水平线和填充算法;排样顺序主要通过启发式算法来解决,比如粒子群算法、遗传算法等。笔者这次比赛使用了最低水平线搜索算法和基于遗传的改进填充算法。
相关链接:
roadef_2018这是2018年欧洲那边的相似比赛
宽度有限但长度无限的最低水平线搜索算法笔者在这个基础上改成了多块板材并且长宽都有限制的输出

代码段

import pandas as pd
import random

# 水平线类(起始x位置,终止x位置,高度)
class OutLine:
    def __init__(self, origin, end, height):
        self.origin = origin
        self.end = end
        self.height = height

    def __str__(self):
        return "OutLine:origin:{}, end:{}, height:{}".format(self.origin, self.end, self.height)


# 矩形物品类(宽度,高度)
class Product:
    def __init__(self, w, h,num):
        self.w = w
        self.h = h
        self.num=num

    def __str__(self):
        return "product:w:{}, h:{}, num:{}".format(self.w, self.h,self.num)

    # 根据长度搜索矩形件(找出宽度小于目标长度的首个矩形件)
    @staticmethod
    def search_by_width(target_width, data):
        for index, p in enumerate(data):
            if p.w <= target_width:
                return index
        return None

    # 根据长度搜索矩形件(找出宽度或高度小于目标长度的首个矩形件)
    @staticmethod
    def search_by_size(target_width, data):
        for index, p in enumerate(data):
            if p.w <= target_width or p.h <= target_width:
                return index
        return None

    # 旋转90度并返回新对象
    def rotate_new(self):
        return Product(self.h, self.w,self.num)


# 布局类
class RectLayout:
    def __init__(self, width, height,material,line_list=[]):
        self.width = width
        self.height = height
        # 水平线集合(起始x位置,终止x位置,高度)
        self.line_list = line_list
        # 水平线(起始x位置,终止x位置,高度)
        self.lowest_line = None
        # 水平线索引(起始x位置,终止x位置,高度)
        self.lowest_line_idx = 0
        # 最终位置结果[[板材材质,板材号,矩形件编号,左下角横坐标,左下角纵坐标,矩形件宽度,矩形件高度], ...]
        self.result_pos = []
        # 板材利用率
        self.ratio = 0.0
        # 板材的序号
        self.metal_num=0
        #原片材质
        self.m=material

    # 初始化水平线集合(起始x位置,终止x位置,高度)
    def init_line_list(self, origin, end, height):
        init_line = OutLine(origin, end, height)
        self.line_list = [init_line]
        self.lowest_line = init_line
        self.lowest_line_idx = 0

    # 提升最低水平线
    def enhance_line(self, index):
        if len(self.line_list) > 1:
            # 获取高度较低的相邻水平线索引,并更新水平线集
            neighbor_idx = 0
            if index == 0:
                neighbor_idx = 1
            elif index + 1 == len(self.line_list):
                neighbor_idx = index - 1
            else:
                # 左边相邻水平线
                left_neighbor = self.line_list[index - 1]
                # 右边相邻水平线
                right_neighbor = self.line_list[index + 1]
                # 选择高度较低的相邻水平线,左右相邻水平线高度相同时,选择左边相邻的水平线
                if left_neighbor.height < right_neighbor.height:
                    neighbor_idx = index - 1
                elif left_neighbor.height == right_neighbor.height:
                    if left_neighbor.origin < right_neighbor.origin:
                        neighbor_idx = index - 1
                    else:
                        neighbor_idx = index + 1
                else:
                    neighbor_idx = index + 1
            # 选中的高度较低的相邻水平线
            old = self.line_list[neighbor_idx]
            if old.height >=self.height:
                self.metal_num += 1
                self.init_line_list(0, container_width, 0)
                return
            # 更新相邻水平线
            if neighbor_idx < index:
                self.line_list[neighbor_idx] = OutLine(old.origin, old.end + self.line_width(index), old.height)
            else:
                self.line_list[neighbor_idx] = OutLine(old.origin - self.line_width(index), old.end, old.height)
            # 删除当前水平线
            del self.line_list[index]

    # 按位置更新水平线
    def update_line_list(self, index, new_line):
        self.line_list[index] = new_line

    # 按位置插入水平线(插在某索引位置后面)
    def insert_line_list(self, index, new_line):
        new_lists = []
        if len(self.line_list) == index + 1:
            new_lists = self.line_list + [new_line]
        else:
            new_lists = self.line_list[:index + 1] + [new_line] + self.line_list[index + 1:]
        self.line_list = new_lists

    # 计算水平线宽度
    def line_width(self, index):
        line = self.line_list[index]
        return line.end - line.origin

    # 找出最低水平线(如果最低水平线不止一条则选取最左边的那条)
    def find_lowest_line(self):
        # 最低高度
        lowest = min([_l.height for _l in self.line_list])
        # 最低高度时,最小开始横坐标
        origin = min([_l.origin for _l in self.line_list if _l.height == lowest])
        for _idx, _line in enumerate(self.line_list):
            if _line.height == lowest and _line.origin == origin:
                self.lowest_line_idx = _idx
                self.lowest_line = _line

    # 清空水平线集合
    def empty_line_list(self):
        self.line_list.clear()

    # 计算最高水平线高度,即所用板材最大高度
    def cal_high_line(self):
        max_height = max([ll.height for ll in self.line_list])

        return max_height

    # 将矩形物品排样
    def packing(self, _pro: Product):
        # 最低水平线宽度
        lowest_line_width = self.line_width(self.lowest_line_idx)
        # 更新水平线集
        new_line1 = OutLine(self.lowest_line.origin, self.lowest_line.origin + _pro.w, self.lowest_line.height + _pro.h)
        #当超过板材上限高度时,进行水平线重置并且将板材序号加1,返回失败表示当前排样未成功
        if new_line1.height>self.height:
            self.metal_num+=1
            self.init_line_list(0, container_width, 0)
            return False
        # 对矩形件排样
        self.result_pos.append([self.m,self.metal_num,_pro.num,self.lowest_line.origin, self.lowest_line.height, _pro.w, _pro.h])
        new_line2 = OutLine(self.lowest_line.origin + _pro.w, self.lowest_line.origin + lowest_line_width, self.lowest_line.height)
        self.update_line_list(self.lowest_line_idx, new_line1)
        if lowest_line_width - _pro.w > 0:
            self.insert_line_list(self.lowest_line_idx, new_line2)
        return True

    # 计算板材利用率
    def cal_used_ratio(self):
        # 计算板材利用率
        used_area = 0
        for _p in self.result_pos:
            used_area += _p[5]*_p[6]
        # # 板材使用最大高度
        # max_high = self.cal_high_line()
        # 利用率
        # if max_high > 0:
        self.ratio = round((used_area * 100) / (self.width * self.height* (self.metal_num+1)), 2)
        return used_area, self.ratio

    def reset(self):
        #针对不同的个体,我们要重置计算其利用率作为损失函数
        self.init_line_list(0, self.width, 0)
        self.result_pos = []
        self.ratio = 0.0
        self.metal_num = 0


# 主方法
if __name__ == "__main__":
	#设置文件路径
    path='./data/a/data'
    seq=4
    a1 = pd.read_csv(path+'A'+str(seq)+'.csv')
    # 板材宽度和高度32.8
    container_width = 2440
    container_height=1220
    # 矩形物品数量
    item_num = len(a1)
    # 初始化矩形物品尺寸,也可以随机生成,保证width>height
    width=list(a1.iloc[:,3])
    height=list(a1.iloc[:,4])
    idx=list(a1.iloc[:,0])
    item_sizes=[list(y) for y in zip(width,height,idx)]
    # 按面积对矩形物品尺寸排序,并且保存矩形件序号
    _item_sizes = sorted(item_sizes, key=lambda x: x[0]*x[1], reverse=True)
    random.shuffle(_item_sizes)
    print(_item_sizes)
    # # 排样序号
    # ran = [i + 1 for i in range(item_num)]
    # print(ran)
    # 矩形物品列表
    products = []
    for idx in range(item_num):
        products.append(Product(_item_sizes[idx][0], _item_sizes[idx][1],_item_sizes[idx][2]))
    # 初始化布局类
    layout = RectLayout(width=container_width,height=container_height,material=a1.iloc[0,1])
    # 初始化水平线集
    layout.init_line_list(0, container_width, 0)
    while products:
        # 最低水平线及其索引
        layout.find_lowest_line()
        # 可用长度
        available_width = layout.line_width(layout.lowest_line_idx)
        # 候选物品索引
        # candidate_idx = Product.search_by_width(available_width, products)
        candidate_idx = Product.search_by_size(available_width, products)
        if candidate_idx is not None:
            # 候选物品
            pro = products[candidate_idx]
            # 宽度放不下时,对矩形件进行旋转
            if pro.w > available_width >= pro.h:
                pro = pro.rotate_new()
            # 将候选物品排样
            flag = layout.packing(pro)
            if flag == False:
                continue
            # 剔除已经排样的物品
            products.pop(candidate_idx)
        else:
            # 最低水平线宽度小于要排样矩形宽度,提升最低水平线
            layout.enhance_line(layout.lowest_line_idx)

    # 计算板材利用率
    _area, _ratio = layout.cal_used_ratio()
    print("used_area: {}".format(_area))
    print("ratio: {}%".format(_ratio))

    df=pd.DataFrame(columns=['原片材质','原片序号','产品id','产品x坐标','产品y坐标','产品x方向长度','产品y方向长度'])
    num=df.index.size
    for idx,val in enumerate(layout.result_pos):
        df.loc[num]=val
        df.index = df.index + 1
    print(df.head())
    #结果保存
    df.to_csv(path+'A'+str(seq)+'res.csv',index=None)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,以下是按照中国围棋规则添加提子的程序示例: ```python def add_stone(board, color, pos): """ 在棋盘上添加一颗棋子(包括提子) :param board: 二维列表,表示棋盘 :param color: 'b' 或 'w',表示棋子颜色 :param pos: 二元组 (x, y),表示落子位置 :return: 返回一个布尔值,表示是否落子成功 """ x, y = pos if board[x][y] != ' ': # 如果落子位置不为空,即有棋子 board[x][y] = ' ' # 先将该位置置为空 if not has_liberty(board, color, pos): # 判断该棋子是否无气 return False # 如果无气,则不能落子 board[x][y] = color # 将棋子落在该位置 remove_captured(board, color) # 处理被提走的棋子 return True def has_liberty(board, color, pos): """ 判断指定位置的棋子是否有气 :param board: 二维列表,表示棋盘 :param color: 'b' 或 'w',表示棋子颜色 :param pos: 二元组 (x, y),表示指定位置 :return: 返回一个布尔值,表示该棋子是否有气 """ x, y = pos for i, j in [(x+1, y), (x-1, y), (x, y+1), (x, y-1)]: if i < 0 or i >= len(board) or j < 0 or j >= len(board[0]): # 判断越界 continue if board[i][j] == ' ': # 如果有相邻空位置,则该棋子有气 return True if board[i][j] == color: # 如果有相邻同色棋子,则递归判断该棋子是否有气 if has_liberty(board, color, (i, j)): return True return False def remove_captured(board, color): """ 处理被提走的棋子 :param board: 二维列表,表示棋盘 :param color: 'b' 或 'w',表示棋子颜色 """ for i in range(len(board)): for j in range(len(board[0])): if board[i][j] != ' ' and board[i][j] != color: # 如果该位置有对方棋子 if not has_liberty(board, board[i][j], (i, j)): # 如果该棋子无气 board[i][j] = ' ' # 将该棋子提走 ``` 注意,要在落子之前先判断该位置是否有棋子,如果有,则先将该位置棋子提走。同时,落子后还需要判断是否有棋子被提走,如果有,则需要将这些棋子从棋盘上移除。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值