动态循环即不定层数循环的两种Python3 实现(递归、纯循环)

'''
动态循环即不定层数循环的两种Python3 实现(递归、纯循环)

# 注意:Python 默认只有list 类型,把它相当于C 的数组来理解即可。
# 要从下面3 个数组分别取3 个元素组成一个,并把可能的组合打印出来
data = [
    [1, 2],
    [3, 4, 5],
    [6, 7, 8, 9]
]
# 意味着会有2*3*4=24 种可能即所有list 的迪卡乘积,解决的办法可以用三重循环实现例如:
for i in data[0]:
    for j in data[1]:
        for k in data[2]:
            print([i, j, k])

但是如果data 里的数组个数不确定的话如果实现动态的不定层循环呢?
我将data 看成一个二维数组:
第一维是data 包含数组的个数即Y 轴,由0 开始自上而下逐行递增;
第二维是每个数组包含的元素个数即X 轴,由0 开始从左到右逐列递增;
然后分别用递归和纯循环的方式处理X、Y 这两个轴。
'''

'''
递归法
从Y 轴第一层即index=0 开始从上往下递归,到了最底层就用一个循环每次将最底层的一个元素加入到要返回的list 中。
注意:Python 对递归层数是有限制的,而且不同操作系统的限制数不一样,在100 层以上就要考虑换个算法了。
'''
# data 数据源, cur_y_idx 当前Y 轴值, lst_rst 返回结果list, lst_tmp 用于临时拼装lst_rst 的元素
def dynloop_rcsn(data, cur_y_idx = 0, lst_rst = [], lst_tmp = []):
    max_y_idx = len(data) - 1  # 获取Y 轴最大索引值
    for x_idx in range(len(data[cur_y_idx])):  # 遍历当前层的X 轴
        lst_tmp.append(data[cur_y_idx][x_idx])  # 将当前层X 轴的元素追加到lst_tmp 中
        if cur_y_idx == max_y_idx:  # 如果当前层是最底层则将lst_tmp 作为元素追加到lst_rst 中
            lst_rst.append([*lst_tmp])
        else:  # 如果当前还不是最底层则Y 轴+1 继续往下递归,所以递归最大层数就是Y 轴的最大值
               # lst_rst 和lst_tmp 的地址也传到下次递归中,这样不论在哪一层中修改的都是同一个list 对象
            dynloop_rcsn(data, cur_y_idx+1, lst_rst, lst_tmp)
        lst_tmp.pop()  # 在本次循环最后,不管是递归回来的,还是最底层循环的,都要将lst_tmp 最后一个元素移除

    return lst_rst


'''
循环法
将多层循环'拍平'成一层循环,即Y 轴只有0,只遍历X 轴,2 维数组变1 维数组。
难点在于每次循环都要计算每个被提取的元素的索引,以提取原2 维数组中的元素。
'''
def dynloop_loop(data):
    # 变量初始化
    max_y_idx = len(data)  # 获取原2 维数组Y 轴最大值
    row_max_idx = 1  # 记录X 轴的最大值,初始为1,下面会计算
    arr_len, lst_row, lst_rst = [], [], []
    arr_idx = [0] * max_y_idx  # 保存每次提取max_y_idx 个元素的索引值的集合,初始值为[0, 0, 0, 0]

    # 将2 维数组data 转换成1 维数组lst_row
    for item in data:
        _n = len(item)  # 求原2 维数组中每一层的长度
        arr_len.append(_n)  # 保存原2 维数组中每一层的长度的集合
        lst_row += item  # 将原2 维数组的每个元素累加到1 维数组lst_row 中
        row_max_idx *= _n  # 记录1 维数组需要循环的总数

    # 遍历1 维数组
    for row_idx in range(row_max_idx):
        # 求每个被提取的元素的索引值
        for y_idx in range(max_y_idx):
            # 遍历原2 维数组各层长度的'切片'集合,例如:lst = [1, 2, 3, 4]
            # 则lst[2:] 为[3, 4] 即从下标2 开始后面全要;lst[:2] 为[1, 2] 即到下标2 之前都要
            # _pdt 是product 乘积的缩写,记录原2 维数组当前层之下所有层长度的乘积
            _pdt = 1
            for n in arr_len[y_idx+1:]:
                _pdt *= n
            # _offset 是偏移量,记录原2 维数组当前层之上所有层长度的和
            _offset = 0
            for n in arr_len[:y_idx]:
                _offset += n
            # 计算元素提取索引:当前X 轴的值除以_pdt,再与原2 维数组当前层长度取余,最后加上偏移量
            arr_idx[y_idx] = (row_idx // _pdt) % arr_len[y_idx] + _offset

        # 遍历索引集合,从1 维数组中提选元素放入_lst_tmp 中
        _lst_tmp = []
        for idx in arr_idx:
            _lst_tmp.append(lst_row[idx])
        # 最后将_lst_tmp 作为元素追加到lst_rst 中
        lst_rst.append(_lst_tmp)

    return lst_rst



'''
相对来说,递归法更便于代码阅读,也比较符合思维的直觉;循环法就比较绕但是相对没有递归层数的限制。
以下是两种方法对于同一份数据的测试,可以看到返回的两个list 是拥有相同元素的。
'''
if __name__ == "__main__":

    data = [
        [1, 2],
        [3, 4, 5],
        [6, 7, 8, 9]
    ]

    print('----------------')
    lst1 = dynloop_loop(data)
    print(len(lst1))
    print(lst1)

    print('----------------')
    lst2 = dynloop_rcsn(data)
    print(len(lst2))
    print(lst2)

    print('----------------')
    # 如果两个list 拥有相同的元素则返回True
    print(lst1 == lst2)

 

import itertools
from dynloop_loop_rcsn import dynloop_loop, dynloop_rcsn


if __name__ == "__main__":

    data = [
        [1, 2],
        [3, 4, 5],
        [6, 7, 8, 9],
        [11, 12],
        [13, 14, 15],
        [16, 17, 18, 19],
        [21, 22],
        [23, 24, 25],
        [26, 27, 28, 29]
    ]

    print('----------------')
    lst1 = dynloop_loop(data)
    print(len(lst1))
    #print(lst1)

    print('----------------')
    lst2 = dynloop_rcsn(data)
    print(len(lst2))
    #print(lst2)

    print('----------------')
    lst3 = list(map(list, (itertools.product(*data))))
    print(len(lst3))
    #print(lst3)

    print('----------------')
    print(lst1 == lst2, lst2 == lst3)

 

/Users/abc/PycharmProjects/testpy/venv/bin/python /Users/abc/PycharmProjects/testpy/test.py

----------------

13824

----------------

13824

----------------

13824

----------------

True True

 

Process finished with exit code 0

  • 12
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值