【利用GPT学习编程】二进制数加法的几种实现

前言

  • 有了newbing和chatgpt后,学习变得更加简单了起来。我们可以提供gpt一份代码,并询问其代码是否出错、是否可以优化、还没有其他类似算法等。本文将介绍几种实现二进制加法的算法,对它们执行时间作比较,并生成图片,以便分析几种算法的优劣,同时,将结果生成一张图表以便更加具体的分析。由于作者水平不够,可能实际结果并不专业,本次测试仅为提高自己的python编写能力。

1.VsCode插件与快捷键

1.1 多行编辑

  1. Alt+鼠标左击:多列选择在这里插入图片描述
  2. Alt+shfit+鼠标左键:拖动在这里插入图片描述
  3. 滚轮选定在这里插入图片描述
  4. shift+alt+i:为选中的多行代码末尾插入光标在这里插入图片描述

1.2 书签插件

  • 搜索插件Bookmarks,对于某几行需要反复修改测试时,可以添加书签,使用ctrl+alt+L或ctrl+alt+J进行书签跳转

1.3 对齐

  • 搜索插件Better Align,ctrl+A进行对齐.Ctrl+Shift+P 搜索Align可设置对齐方式在这里插入图片描述

1.4 变量命名助手

  • 驼峰翻译助手、change-case、CodeLf都是可以尝试使用的插件,具体效果可以在扩展商店搜索,查看演示。同时,个人平时在编写代码时注意规范,应该也不会有这方面太多的需求。

1.5 代码补全

  • 听说最好用的是Copilot,不过要钱,我找到了CodeGeex,效果挺好的。安装好以后,鼠标右键可以看到CodeGeex Tool。

1.6 Jupyter

  • 不需要创建python文件,只要把需要测试的代码放到Jupyter中就可以直接运行在这里插入图片描述
  • 如果觉得还是麻烦的话,可以直接用python自带的idle。不过,如果从网上复制的代码是没法直接在idle中运行的,还是使用Jupyter比较好一点,平常没有太大的工程量的话,用idle一行一行输入就行了。在这里插入图片描述

1.7 代码提示

  • Python的话,一般Kite就够了,Vscode好像也自带代码补全的功能,如果觉得不够,可以自己去网上搜搜其他的代码补全插件

1.8 其他

  • 主题方面,有很多主题,可以多来几个,看久了就换一个,一切看个人使用习惯。我一般喜欢深色的,推荐深色高对比度和Cobalt2 Theme。快捷键Ctr+K Ctrl+T切换主题。在这里插入图片描述
  • 其他需求:安装vscode的时候会自带一些功能,它也会推荐你安装一些插件,根据需求选择自己想安装的插件即可。比如函数跳转、更改所有匹配性之类的

2.前期准备

2.1 要点说明

  1. 编写几种实现二进制加法的算法
  2. 编写计算运行时间的函数,对比使用几种算法的耗时
  3. 格式化输出结果(图表)
  4. 尽可能地模块化、简洁、高效。当有需要添加、删除、修改的操作时,修改参数即可

2.2 设置输出图表的字体

  • 网上有教程,这里直接贴出代码
import seaborn as sns
'''设置中文字体'''
sns.set_style("darkgrid", {"font.sans-serif": ['KaiTi', 'Arial']})
mpl.rcParams['font.sans-serif'] = ['KaiTi']
mpl.rcParams['font.serif'] = ['KaiTi']
''''''

2.3 编写类

  • 编写一个类,要用的函数全部放到类中即可
class D2b:
    def __init__(self, a, b):
        self.a = a
        self.b = b

2.4 在类中创建函数

def func1(self,a,b):
	#Code

2.5 需要导入的模块

from texttable import Texttable
import time
import cProfile
import tracemalloc
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
import seaborn as sns

3.Python实现二进制数加法的几种方法

3.1 方法1 addBinary

    # 方法1:a和b都是二进制,直接实现二进制加法,并返回二进制的结果
        # 步骤1:a和b的长度对齐,例如a='1010',b='01'.则对齐后a='1010',b='0001'
        # 步骤2:根据二进制转十进制的原理进行二进制加法
        # 步骤3:将二进制的结果转换(可选)
    def alignLength(self, a, b):#补齐长度
        a = a.zfill(len(b)) if len(a) < len(b) else a
        b = b.zfill(len(a)) if len(b) < len(a) else b
        return a, b, len(a)
    def addBinary(self):
        a, b, l = self.alignLength(self.a, self.b)
        sum = 0
        for i in range(l):
            sum = sum + int(a[i])*2**(l-1-i) + int(b[i])*2**(l-1-i)
        return bin(sum)[2:]
  • 原理很简单,就是根据二进制转十进制的原理,对每一位乘以2^n

3.2 方法2 addDecimal

    def addDecimal(self):    # 方法2:先将a和b用int函数转换为十进制,进行加法运算,再将结果转换为二进制
        decimal_num1 = int(self.a, 2)
        decimal_num2 = int(self.b, 2)
        return bin(decimal_num1 + decimal_num2)[2:]
  • 这种方法比较方便,也便于理解,耗时也比较短

3.3 方法3 iterationAddBinary

    def iterationAddBinary(self):    # 方法3:按位异或
        x, y = int(self.a, 2), int(self.b, 2)
        while y:
            answer = x ^ y     # 异或相当于不产生进位的加法     
            carry = (x & y) << 1   # 与运算相当于对是否产生进位进行判断
            x, y = answer, carry		#更新x和y的值
            # 例如,x='1001',y='0101',首先对x和y进行"加法",得到answer=x^y='1100',根据逢二进一的规则,效果等同于(x的第四位+y的第四位+进位)%2,所以answer的第四位为'0'
            # 然后对是否进位产生进行判断,x&y='0001',第四位的计算结果为'1',代表那一位产生了进位,我们将carry=x&y左移一位,得到'0010',更新x和y
            #第二次计算,x='1100',y='0010',再次进行"加法",x^y='1110',在第三位,两个数相加为1,效果等同于(x的第三位+y的第三位+进位,也就是0+0+1)%2
            # 继续对是否进位进行判断,x&y='0000',结果在第三位并没有产生进位。继续左移,更新x和y
            #我们发现,由于y=(x&y)<<1无论如何左移都都为'0000',就相当x+'0000',无论如何都不会再产生任何进位,这也符合实际,那么x^y的效果等同于x+y,所以本次计算就是最终结果。这里得到的x是十进制数,所以需要用bin函数进行处理,由于得到的结果是'0b1110',需要返回bin(x)[2:]
        return bin(x)[2:]
  • 这是LeetCode上关于这个算法的官方解法,网上也可以搜索对这段代码的详细解释,耗时较短

3.4 方法4 recursionAddBinary

    def recursionAddBinary(self, a, b):  # 方法4:递归实现二进制数加法
        if not a:
            return b
        if not b:
            return a  # 最终结果为r(a,'')或r('',b)时,分别返回a,b
        if a[-1] == '1' and b[-1] == '1':  # 产生进位时,原位置留一个0,获得进位1,继续处理剩下的二进制数与进位1
            return self.recursionAddBinary(self.recursionAddBinary(a[:-1], b[:-1]), '1')+'0'
        else:  # 未产生进位时,原位置留a[-1]+b[-1],继续处理剩下的二进制数
            return self.recursionAddBinary(a[:-1], b[:-1])+str(int(a[-1])+int(b[-1]))

    # 赋值a,b为self.a,self.b,执行函数recursionAddBinary
    def recursionBinaryAddition(self):
        a, b = self.a, self.b
        return self.recursionAddBinary(a, b)
  • 询问newbing如何通过递归实现二进制数加法,在它给出的答案的基础上我做了一些修改,实现了该算法。代码比较抽象,但是原理就是两个数的最后一位相加,如果产生进位就添加一个’0’,然后继续处理剩下的二进制数与进位1;如果没产生进位的话,就在末尾添加一个str(int(a[-1])+int(b[-1]))

3.5 方法5 binaryAddr

    def binaryAddr(self):    # 方法5:二进制加法器
        # 假设a='1001',b='0011',从最低位开始相加,进位则将carry传至下一次加法过程中
        a, b, l = self.alignLength(self.a, self.b)
        #num_list = list(zip([a[i] for i in range(l)],[b[i] for i in range(l)]))
        carry = 0
        sum_ = ''
        for i in range(l, 0, -1):
            situ = int(a[i-1]) ^ int(b[i-1]) ^ carry
            carry = (int(a[i-1]) & int(b[i-1])) | (carry & (int(a[i-1]) ^ int(b[i-1])))
            sum_ = str(situ) + sum_
        if carry:
            sum_ = sum_ + str(carry)
        return sum_
  • 模拟加法器实现的加法运算,应该比较容易理解。几种算法比下来,貌似它的耗时最多了

4.生成图表

4.1 测试耗时

def testBinaryTime(func):#这里将函数作为参数传入进来,便于比较
    start_time     = time.perf_counter()
    func_name      = func.__name__
    sum_binary     = func()
    end_time       = time.perf_counter()
    time_consuming = end_time-start_time   #使用time.perf_count()更加精确
    sum_decimal    = int(sum_binary, 2)
    time_consuming = "{:.4f}".format(time_consuming*1000)
    result_list    = [func_name, sum_binary, sum_decimal, time_consuming]
    return result_list		#以数组的形式返回结果

4.2 生成结果

def getTable(a, b, list_name, func_list, func_explain):
    result = [[] for _ in range(len(func_list)+1)]
    for n in range(len(list_name)):
        result[0].append(list_name[n])
    for row in range(len(func_list)):
        for col in range(len(list_name)-1):
            result[row+1].append(testBinaryTime(func_list[row])[col])
        result[row+1].append(func_explain[row])
    del list_name, func_list, func_explain
    return result   #对数组进行处理,返回一个二维数组,上面记录了所有算法的耗时

4.3 生成图表

4.3.1 Texttable

def drawTable(a, b, list_name, func_list, func_explain):
    table = Texttable()
    
    table.set_cols_align(['l' for _ in range(len(list_name))])
    table.set_cols_valign(['b' for _ in range(len(list_name))])
    width = [20 for _ in range(len(list_name))]
    width[-1] = 45
    table.set_cols_width(width)
    del width
    table.set_precision(4)
    result = getTable(a, b, list_name, func_list,func_explain)     # 使用texttable库画表
    table.add_rows(result)
    print(table.draw())
    del table

4.3.2 pandas

def drawWithPd(a, b, list_name, func_list, func_explain): # 使用pandas画图
    result       = getTable(a, b, list_name, func_list, func_explain)
    df           = pd.DataFrame(result[1:], columns=result[0])
    df['耗时(ms)'] = df['耗时(ms)'].astype(float)
    ax           = df.plot(x='函数名', y='耗时(ms)', kind='bar', figsize=(10, 5))
    ax.set_xticklabels(df['函数名'], rotation=0)
    plt.show()

4.4 主体函数

if __name__ == '__main__':
    a, b = '10011', '111'
    list_name = ['函数名', '二进制结果', '十进制结果', '耗时(ms)', '说明']
    func_list = [D2b(a, b).addBinary, D2b(a, b).addDecimal, D2b(
        a, b).iterationAddBinary, D2b(a, b).recursionBinaryAddition, D2b(a, b).binaryAddr]
    func_explain = [
        '根据二进制转十进制的原理,对每一位乘以2^n',
        '先转换为十进制对两个二进制数进行加法运算',
        '利用异或运算实现二进制数相加',
        '递归实现二进制数加法',
        '二进制加法器'
    ]
    drawTable(a, b, list_name, func_list, func_explain)
    drawWithPd(a, b, list_name, func_list, func_explain)

4.5 其他

  • 性能分析、内存消耗等
  • 代码分析:是否可以进一步优化运行速度,是否能够提高代码的简洁性,如何使其模块化,更加便于修改(例如装饰器)等。
  • 遇到不会的、看不懂的、报错的,直接拿去问chatgpt或newbing
  • 如果发现同一种算法可以采用更加高效的办法,请告诉我

5 源代码

from texttable import Texttable
import time
import cProfile
import tracemalloc
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
import seaborn as sns


""" 设置中文字体 """
sns.set_style("darkgrid", {"font.sans-serif": ['KaiTi', 'Arial']})
mpl.rcParams['font.sans-serif'] = ['KaiTi']
mpl.rcParams['font.serif'] = ['KaiTi']


class D2b:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    # 方法1:a和b都是二进制,直接实现二进制加法,并返回二进制的结果
        # 步骤1:a和b的长度对齐,例如a='1010',b='01'.则对齐后a='1010',b='0001'
        # 步骤2:根据二进制转十进制的原理进行二进制加法
        # 步骤3:将二进制的结果转换(可选)

    def alignLength(self, a, b):
        a = a.zfill(len(b)) if len(a) < len(b) else a
        b = b.zfill(len(a)) if len(b) < len(a) else b
        return a, b, len(a)

    def addBinary(self):
        a, b, l = self.alignLength(self.a, self.b)
        sum = 0
        for i in range(l):
            sum = sum + int(a[i])*2**(l-1-i) + int(b[i])*2**(l-1-i)
        return bin(sum)[2:]

    def addDecimal(self):    # 方法2:先将a和b用int函数转换为十进制,进行加法运算,再将结果转换为二进制
        decimal_num1 = int(self.a, 2)
        decimal_num2 = int(self.b, 2)
        return bin(decimal_num1 + decimal_num2)[2:]

    def iterationAddBinary(self):    # 方法3:按位异或
        x, y = int(self.a, 2), int(self.b, 2)
        while y:
            answer = x ^ y     # 异或相当于不产生进位的加法     
            carry = (x & y) << 1   # 与运算相当于对是否产生进位进行判断
            x, y = answer, carry		#更新x和y的值
            # 例如,x='1001',y='0101',首先对x和y进行"加法",得到answer=x^y='1100',根据逢二进一的规则,效果等同于(x的第四位+y的第四位+进位)%2,所以answer的第四位为'0'
            # 然后对是否进位产生进行判断,x&y='0001',第四位的计算结果为'1',代表那一位产生了进位,我们将carry=x&y左移一位,得到'0010',更新x和y
            # 第二次计算,x='1100',y='0010',再次进行"加法",x^y='1110',在第三位,两个数相加为1,效果等同于(x的第三位+y的第三位+进位,也就是0+0+1)%2
            # 继续对是否进位进行判断,x&y='0000',结果在第三位并没有产生进位。继续左移,更新x和y
            # 我们发现,由于y=(x&y)<<1无论如何左移都都为'0000',就相当x+'0000',无论如何都不会再产生任何进位,这也符合实际,那么x^y的效果等同于x+y,所以本次计算就是最终结果。这里得到的x是十进制数,所以需要用bin函数进行处理,由于得到的结果是'0b1110',需要返回bin(x)[2:]
        return bin(x)[2:]

    def recursionAddBinary(self, a, b):  # 方法4:递归实现二进制数加法
        if not a:
            return b
        if not b:
            return a  # 最终结果为r(a,'')或r('',b)时,分别返回a,b
        if a[-1] == '1' and b[-1] == '1':  # 产生进位时,原位置留一个0,获得进位1,继续处理剩下的二进制数与进位1
            return self.recursionAddBinary(self.recursionAddBinary(a[:-1], b[:-1]), '1')+'0'
        else:  # 未产生进位时,原位置留a[-1]+b[-1],继续处理剩下的二进制数
            return self.recursionAddBinary(a[:-1], b[:-1])+str(int(a[-1])+int(b[-1]))

    # 赋值a,b为self.a,self.b,执行函数recursionAddBinary
    def recursionBinaryAddition(self):
        a, b = self.a, self.b
        return self.recursionAddBinary(a, b)

    def binaryAddr(self):    # 方法5:二进制加法器
        # 假设a='1001',b='0011',从最低位开始相加,进位则将carry传至下一次加法过程中
        a, b, l = self.alignLength(self.a, self.b)
        #num_list = list(zip([a[i] for i in range(l)],[b[i] for i in range(l)]))
        carry = 0
        sum_ = ''
        for i in range(l, 0, -1):
            situ = int(a[i-1]) ^ int(b[i-1]) ^ carry
            carry = (int(a[i-1]) & int(b[i-1])) | (carry & (int(a[i-1]) ^ int(b[i-1])))
            sum_ = str(situ) + sum_
        if carry:
            sum_ = sum_ + str(carry)
        return sum_


def testBinaryTime(func):
    start_time     = time.perf_counter()
    func_name      = func.__name__
    sum_binary     = func()
    end_time       = time.perf_counter()
    time_consuming = end_time-start_time
    sum_decimal    = int(sum_binary, 2)
    time_consuming = "{:.4f}".format(time_consuming*1000)
    result_list    = [func_name, sum_binary, sum_decimal, time_consuming]
    return result_list


def getTable(a, b, list_name, func_list, func_explain):
    result = [[] for _ in range(len(func_list)+1)]
    for n in range(len(list_name)):
        result[0].append(list_name[n])
    for row in range(len(func_list)):
        for col in range(len(list_name)-1):
            result[row+1].append(testBinaryTime(func_list[row])[col])
        result[row+1].append(func_explain[row])
    del list_name, func_list, func_explain
    return result


def drawTable(a, b, list_name, func_list, func_explain):
    table = Texttable()
    
    table.set_cols_align(['l' for _ in range(len(list_name))])
    table.set_cols_valign(['b' for _ in range(len(list_name))])
    width = [20 for _ in range(len(list_name))]
    width[-1] = 45
    table.set_cols_width(width)
    del width
    table.set_precision(4)
    result = getTable(a, b, list_name, func_list,func_explain)     # 使用texttable库画表
    table.add_rows(result)
    print(table.draw())
    del table


def drawWithPd(a, b, list_name, func_list, func_explain): # 使用pandas画图
    result       = getTable(a, b, list_name, func_list, func_explain)
    df           = pd.DataFrame(result[1:], columns=result[0])
    df['耗时(ms)'] = df['耗时(ms)'].astype(float)
    ax           = df.plot(x='函数名', y='耗时(ms)', kind='bar', figsize=(10, 5))
    ax.set_xticklabels(df['函数名'], rotation=0)
    plt.show()


def analysisProperties(func, a, b, list_name, func_list, func_explain):  # 性能分析

    profiler = cProfile.Profile()
    profiler.enable()

    func(a, b, list_name, func_list, func_explain)

    profiler.disable()
    profiler.print_stats()


def mallocConsuming(func, a, b, list_name, func_list, func_explain):  # 内存消耗
    tracemalloc.start()
    func(a, b, list_name, func_list, func_explain)
    current, peak = tracemalloc.get_traced_memory()
    print("Current memory usage: {:.8f}KB; Peak: {:.8f}KB".format(
        current / 10**9, peak / 10**9))
    tracemalloc.stop()  # 停止跟踪内存


if __name__ == '__main__':
    a, b = '10011', '111'
    list_name = ['函数名', '二进制结果', '十进制结果', '耗时(ms)', '说明']
    func_list = [D2b(a, b).addBinary, D2b(a, b).addDecimal, D2b(
        a, b).iterationAddBinary, D2b(a, b).recursionBinaryAddition, D2b(a, b).binaryAddr]
    func_explain = [
        '根据二进制转十进制的原理,对每一位乘以2^n',
        '先转换为十进制对两个二进制数进行加法运算',
        '利用异或运算实现二进制数相加',
        '递归实现二进制数加法',
        '二进制加法器'
    ]
    drawTable(a, b, list_name, func_list, func_explain)
    drawWithPd(a, b, list_name, func_list, func_explain)
    # analysisProperties(drawTable,a,b,list_name,func_list,func_explain) #计算内存消耗
    # mallocConsuming(drawTable,a,b,list_name,func_list,func_explain)   #性能分析

6 截图

在这里插入图片描述通过图表发现,方法2和方法3耗时最短。但是,实际结果因算法、系统、计算机性能而异,并不能代表全部,这里仅作参考。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值