2019软工实践_作业4_1(结对编程实现博客)

1、链接

队友链接(zxr)
出牌算法

2、分工

UI:zxr
出牌算法:zxr+zsy 当然是被队友带着养老了

3、PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 15 30
Estimate 估计这个任务需要多少时间 855 980
Developm 开发 240 360
Analysis 需求分析(包括学习新技术) 20 20
Design Spec 生成设计文档 60 60
Design Review 设计复审 30 30
Coding Standard 代码规范(为目前的开发制定合适的规范) 10 10
Design 具体设计 240 360
Coding 具体编码 240 360
Code Review 代码复审 60 60
Test 测试(自我测试,修改代码,提交修改) 420 420
Reporting 报告 0 0
Test Report 测试报告 0 0
Size Measurement 计算工作量 0 0
Postmortem & Process Improvement Plan 事后总结,并提出过程改进计划 30 30
合计 855 980

4、解题思路描述与设计实现说明

思路描述
首先,数据规模为$n=13$很容易想到枚举算法(大概是$O(13^{5} * 8^{5}$)?貌似也没有限时),只要暴力枚举前中墩的情况即可,因为前中墩枚举结束后,剩余牌自动归为前墩。
其次,对于枚举结束之后的三墩,先进行合法性检测,即三墩需满足:前墩$\lt$中墩$\lt$后墩等号取不到
再者,对于合法的一组手牌,我们算出这组牌赢的概率的估计值。对,是概率的估计值,那么如何计算?因为我们偷到了一个权重数组,同时,我们维护一个大小为25的小根堆队友说25吉利,显然最后堆底的牌组就是我们考虑的最优组合。
最后,发到服务器,等待出分。
设计实现说明
1、爆搜或者10重for的嵌套。简单粗暴,除了久一点,没什么问题。
2、爆搜或者5重for,先选出后墩,再选出中墩。即后中墩是分开选择的。显然可以出解,而且比上一种方法快很多。但是,会出现倒水的情况,即无法保证三墩的大小关系。
3、先搜模式,匹配原始牌组,最后出牌。
4、AI算法,笔者没有头绪。
最后选择了方法3因为一开始写的是方法2,发现倒水了,就被队友说服去写方法3了,而至于为什么不写方法1,因为方法1时间复杂度太高了$O(!)$,关于方法3的实现,请看官移步开头链接查看。(文件名为:_AutoRecommend)
类图
2019软工实践_作业4_1(结对编程实现博客)_第1张图片
2019软工实践_作业4_1(结对编程实现博客)_第2张图片
2019软工实践_作业4_1(结对编程实现博客)_第3张图片
2019软工实践_作业4_1(结对编程实现博客)_第4张图片

5、关键代码解释

先上流程图
2019软工实践_作业4_1(结对编程实现博客)_第5张图片

没有python-style的python大概就是我这样的

def dfs(card_list, nw, state):
    if ( nw == s ):
        ct = 0
        for i in range(len(state)):
            if ( state[i] == 0 ):
                state[i] = 1
                hands.append(card_list[i])
                state[i] = 0
                ct += 1
        print(hands)
        header = hands[:f]
        middle = hands[f:s]
        tailer = hands[s:t]
        #print(hands)
        #print(header); print(middle); print(tailer)
        ret = chk(header, middle, tailer)
        if ( ret[0] == 1 ):
            if ( len(q) >= hyper_n ):
                if ( q[0].weight < ret[1] ):
                    heapq.heappushpop(q, HandCard(hands, ret[1]))
            else:
                heapq.heappush(q, HandCard(hands, ret[1]))
        while (ct>0):
            hands.pop()
            ct -= 1
        return
    for i in range(len(state)):
        if ( state[i] == 0 ):
            state[i] = 1
            hands.append(card_list[i])
            dfs(card_list, nw+1, state)
            hands.pop()
            state[i] = 0

hyper_n = 10
f = 3
s = 8
t = 13
q = []
hands = []
heapq.heapify(q)
dfs(cards, 0, np.zeros(len(cards)))

上述是一个简(T)单(到)粗(无)暴(边)的爆搜,直接否掉,这就不能算是个解决方案,实在是太缓慢了。而接下来是一个暴力+贪心的版本。

def RecommendHands( card_list ):
    sz = len(card_list)
    q = []
    heapq.heapify(q)
    nw_hands = []
    #last O(n^5) using the brute force to enumerate the combination of the last hands
    for i in range(0,sz,1):
        nw_hands.append(card_list[i])
        for j in range(i+1,sz,1):
            nw_hands.append(card_list[j])
            for k in range(j+1,sz,1):
                nw_hands.append(card_list[k])
                for g in range(k+1,sz,1):
                    nw_hands.append(card_list[g])
                    for t in range(g+1,sz,1):
                        nw_hands.append(card_list[t])
                        _c = nw_hands; _w = get_weight(_c, 2)
                        heapq.heappush(q, Hands(_c, -_w))
                        if len(q) > hyper_n:
                            _ = heapq.heappop(q)
                        nw_hands.pop()
                    nw_hands.pop()
                nw_hands.pop()
            nw_hands.pop()
        nw_hands.pop()
    
    last_hands = []; middle_hands = []; header_hands = []
    while len(q)>0:
        last_hands.append(heapq.heappop(q))

    #last_hands = [ ([(1,1),(1,2),(1,3),(1,4),(1,5)],weight), (), (), ... () ].dtype = Hands([(),()],w)
    for _ in last_hands:
        #fir every last_hands choose the middle_hands and header_hands
        tp_card_list = card_list.copy()
        for i in _.list:
            for j in range(len(tp_card_list)):
                if tp_card_list[j] == i:
                    tp_card_list.pop(j)
                    break
        
        #now tp_card_list contain only 8 cards for middle and header
        sz = len(tp_card_list)
        for i in range(0,sz,1):
            nw_hands.append(tp_card_list[i])
            for j in range(i+1,sz,1):
                nw_hands.append(tp_card_list[j])
                for k in range(j+1,sz,1):
                    nw_hands.append(tp_card_list[k])
                    for g in range(k+1,sz,1):
                        nw_hands.append(tp_card_list[g])
                        for t in range(g+1,sz,1):
                            nw_hands.append(tp_card_list[t])
                            _c = nw_hands; _w = get_weight(_c, 1)
                            heapq.heappush(q, Hands(_c, -_w))
                            if len(q) > hyper_n:
                                _ = heapq.heappop(q)
                            nw_hands.pop()
                        nw_hands.pop()
                    nw_hands.pop()
                nw_hands.pop()
            nw_hands.pop()
        
        while len(q)>0:
            X = heapq.heappop(q)
            middle_hands.append(X)
            tp2_card_list = tp_card_list.copy()
            szz = len(middle_hands)
            for i in middle_hands[szz-1].list:
                for j in range(len(tp2_card_list)):
                    if i == tp2_card_list[j]:
                        tp2_card_list.pop(j)
                        break
            _c = tp2_card_list; _w = get_weight(tp2_card_list, 0)
            header_hands.append(Hands(_c, -_w))
    
    my_hands = []
    for lst in last_hands:
        for j in range(hyper_n):
            my_hands.append(  [(header_hands[j].list, header_hands[j].weight),
                               (middle_hands[j].list, middle_hands[j].weight),
                               (lst.list, lst.weight)] ) 
    #for i in my_hands:
    #   print(i)
    
    return my_hands
    
    #middle O(n^5) using the brute force to enumrate the combination of the middle hands 
    #first the rest, no choise to choose

最后是我们真正采用的算法。暴力枚举13张牌可能组成的牌型,从单张(junks)到顺子(straights),每种牌型从大到小排序,再从中枚举组合出后中前墩的可能出牌模式,最后枚举判断是否合法即可,如若合法则加入丢入小根堆中维护,最后小根堆弹出至空的最后一组出牌即为所求。因为每部分都不短,还请看官移步文首的仓库品鉴(文件名为:_AutoRecommend.py)

6、性能分析与改进

    ncalls  tottime  percall  cumtime  percall
       64    0.001    0.000   15.890    0.248 Algorithm_fight.py:142(get_battle)
       32    0.030    0.001    8.021    0.251 Algorithm_fight.py:155(decode_data)
       32    0.001    0.000    3.058    0.096 Algorithm_fight.py:168(my_choose)
      800    0.000    0.000    0.000    0.000 Algorithm_fight.py:180()
       32    0.001    0.000    7.483    0.234 Algorithm_fight.py:204(send_2_system)
       32    0.055    0.002   26.529    0.829 Algorithm_fight.py:221(_start)
        1    0.000    0.000   26.530   26.530 Algorithm_fight.py:239(main)
      416    0.000    0.000    0.000    0.000 Algorithm_fight.py:29(chg)
        1    0.000    0.000    0.000    0.000 Algorithm_fight.py:37(Hands)
    50944    0.019    0.000    0.025    0.000 Algorithm_fight.py:38(__init__)
   292872    0.034    0.000    0.034    0.000 Algorithm_fight.py:42(__lt__)
       32    0.125    0.004    3.056    0.096 Algorithm_fight.py:49(RecommendHands)
        1    0.000    0.000   26.767   26.767 Algorithm_fight.py:7()
        1    0.000    0.000    0.000    0.000 GetWeight.py:7()
    50944    0.240    0.000    2.790    0.000 GetWeight.py:99(get_weight)

参数解释:
ncalls:表示函数调用的次数。
tottime:表示指定函数的总的运行时间,除掉函数中调用子函数的运行时间。
percall:(第一个 percall)等于 tottime/ncalls。
cumtime:表示该函数及其所有子函数的调用运行的时间,即函数开始调用到返回的时间。
percall:(第二个 percall)即函数运行一次的平均时间,等于 cumtime/ncalls。
filename:lineno(function):每个函数调用的具体信息
其实,撇开网络请求,自我感觉完成得还是不错的,当然仅指完成层面。
改进方面:虽然是偷的权重数组,但是也和队友商量了挺久,以及讨论爆搜的剪枝(chk),也实现了一些讨论的方案。

7、单元测试

class UnitTest(unittest.TestCase):
    
    @classmethod
    def setUpClass(self):
        pass

    @classmethod
    def tearDownClass(self):
        pass

    def tst_jdg_pair(self):
        ct = 0
        for i in open('./UnitTest-in.txt').readlines():
            ct += 1
            nw = []; cards = i.split()
            for j in cards:
                nw.append((suit_sa[j[0]],number_sa[j[1:len(j)]]))
            #print(nw)
            print("card %d " % (ct), end="")
            print(jdg_pair(nw))

    def tst_jdg_2pairs(self):
        ct = 0
        for i in open('./UnitTest-in.txt').readlines():
            nw = []; cards = i.split()
            for j in cards:
                nw.append((suit_sa[j[0]],number_sa[j[1:len(j)]]))
            #print(nw)
            ct += 1
            print("card %d " % (ct), end="")
            print(jdg_2pairs(nw))

    def tst_jdg_triple(self):
        ct = 0
        for i in open('./UnitTest-in.txt').readlines():
            nw = []; cards = i.split()
            for j in cards:
                nw.append((suit_sa[j[0]],number_sa[j[1:len(j)]]))
            #print(nw)
            ct += 1
            print("card %d " % (ct), end="")
            print(jdg_triple(nw))

    def tst_jdg_boom(self):
        ct = 0
        for i in open('./UnitTest-in.txt').readlines():
            nw = []; cards = i.split()
            for j in cards:
                nw.append((suit_sa[j[0]],number_sa[j[1:len(j)]]))
            #print(nw)
            ct += 1
            print("card %d " % (ct), end="")
            print(jdg_boom(nw))

    def tst_jdg_straight(self):
        ct = 0
        for i in open('./UnitTest-in.txt').readlines():
            nw = []; cards = i.split()
            for j in cards:
                nw.append((suit_sa[j[0]],number_sa[j[1:len(j)]]))
            #print(nw)
            ct += 1
            print("card %d " % (ct), end="")
            print(jdg_boom(nw))

    def tst_jdg_flush(self):
        ct = 0
        for i in open('./UnitTest-in.txt').readlines():
            nw = []; cards = i.split()
            for j in cards:
                nw.append((suit_sa[j[0]],number_sa[j[1:len(j)]]))
            #print(nw)
            ct += 1
            print("card %d " % (ct), end="")
            print(jdg_flush(nw))

    def tst_jdg_fullhouse(self):
        ct = 0
        for i in open('./UnitTest-in.txt').readlines():
            nw = []; cards = i.split()
            for j in cards:
                nw.append((suit_sa[j[0]],number_sa[j[1:len(j)]]))
            #print(nw)
            ct += 1
            print("card %d " % (ct), end="")
            print(jdg_fullhouse(nw))

test_lists = ["tst_jdg_pair","tst_jdg_2pairs","tst_jdg_triple","tst_jdg_boom","tst_jdg_straight","tst_jdg_flush","tst_jdg_fullhouse"]
if __name__ == "__main__":
    for i in test_lists:
        suit = unittest.TestSuite()
        suit.addTest(UnitTest(i))
        runner = unittest.TextTestRunner()
        runner.run(suit)

2019软工实践_作业4_1(结对编程实现博客)_第6张图片

8、贴出Github的代码签入记录

2019软工实践_作业4_1(结对编程实现博客)_第7张图片

9、遇到的代码模块异常或结对困难及解决方法

问题描述

  • 结对没有尽早开始写代码,拖延得比较后面。国庆当然是快乐
  • 关于算法结果有冲突。倒水我是不想重写的,队友一开始的思路我是不解的

做过哪些尝试

  • 意识到快来不及了,马上动工233
  • 队友说得对,就重写吧。

是否解决

  • 较好地解决。显然我们完成了作业,我也解决了倒水。

有何收获

  • 队友是个好队友,我就不知道了,我都差点跟队友吵起来。还好队友的算法思路够真
  • 学了一下python自带堆heapq的使用。写模拟更强了

10、评价你的队友

值得学习的地方
儒雅随和,不紧不慢。
需要改进的地方
学前端太慢了。。在我看来,找到直接开抄就行。。

11、学习进度条

周数 新增代码(行) 累计代码(行) 本周学习消耗(小时) 累计学习消耗(小时) 重要成长
4 0 0 21 21 AxureRp的学习与制作
7 800+ 800+ 0.4 21.4 python的自带堆(heapq)调用,以及gitignore的编写

心得:

1、感谢我的队友,考虑到我的各种原因,包揽了前端,还写了特殊牌型得判断,最后放我来写我自己最熟悉的算法部分,同时队友也非常体谅我的进度。平心而论,如果这次是个人作业,我可能就完成不了了,或者说只会做出一个非常丑陋的UI界面。
2、一开始想写爆搜,怕爆栈就丢了for上去,确实除了慢点没什么问题,剪了剪枝也没有非常出乎意料的提升。暴力出奇迹,这是我写过最暴力的工程代码,但是却有着极低的编程复杂度。
3、最后被算法思路极真的队友,喊去写了我们最后使用的算法,不得不说,先找模式确实快很多。

你可能感兴趣的:(2019软工实践_作业4_1(结对编程实现博客))