list循环_Power Query — 嵌套循环

107806eedfe56097fee62167c5aec3e9.png

题记:

线纵横交错,网也;网者,点嵌套循环也。


循环嵌套:

~~~~~~~~~~

案例1:输出九九乘法表。

ff7f784795055f29145450417c0f5ed5.png

~~~~~~~~~~

这道题的行和列单独拿出来做都很简单,但是合成一张表循环就不能单单依靠前面一节讲的循环了。如题记讲到的,“网者,点嵌套循环也”,点循环构造成线,线相互交织构造成网,我们需要将循环进行一次嵌套,才能够完成“表”的编织:

= Table.FromColumns(

List.Transform({1..9},(x)=>

List.Transform({1..9},(y)=>

if x<=y

then Text.Format("#{0}×#{1}=#{2}",{x,y,x*y})

else null)))

83f4c2492178ce2d9f9713e727118f6d.png

为了阅读方便,我有意将语句一行一行地写出来,可以看到这里面用到的函数其实都是大家很熟悉的函数,最大的特点在于第二个遍历循环嵌套在第一个遍历循环的函数表达式内,构成了嵌套,这样得到的值属于{lists}型,需要使用Table.FromColumns进行展开。


综合应用:

~~~~~~~~~~

案例2:输出2~100内的全部质数。

~~~~~~~~~~

前面讲循环时用遍历循环证明了19是质数,本题我们考虑换一种循环来实现质数的判定,然后遍历百以内的全部自然数逐个判断,这也是两个循环相嵌套的问题:

首先考虑定义一个函数专门用来判断质数,根据定义,我们采用取余法来判断是否整除,

prime=(x,y)=>if x=y then 1 else if Number.Mod(x,y)=0 then 0 else @prime(x,y+1)

这个自定义函数允许我们自定义seed值y,然后会对y~x内的全部自然数做逐个取余验证,为了使if语句写起来更加简洁,我们不妨将seed值设置为2(因为1没必要验证),那么接下来直接使用遍历循环并调用自定义函数做质数判断即可:

List.Transform({2..100},(x)=>if prime(x,2)=1 then x else 0)

显然,质数就是遍历结果筛除0后剩下的数:

let

prime=(x,y)=>if x=y then 1 else if Number.Mod(x,y)=0 then 0 else @prime(x,y+1)

in

List.Select(List.Transform({2..100},(x)=>if prime(x,2)=1 then x else 0),each _>0)

46bf1c9e52962e5784a48c989c11b2f7.png

~~~~~~~~~~

案例3:使用自定义函数将(17012345678)10转化成八进制的数。

~~~~~~~~~~

要解决这个问题首先要知道进制在数学上是怎么回事:

详细科普我就不赘述了,从表现形式上,需要知道对于一个X进制而言,下面的数都是用0~(X-1)(十进制下)的数表示的,如果十进制的数不够用,例如十六进制下的10~15怎么表示的问题,会考虑引进字母符号来表示,例如十六进制的2018就表示为:7E2=7×16^2+E×16^1+2×16^0=1792+14×16+2=2018

d90ab915b3c1fd1fa7eb157c90bfa2c9.png

如果读者还依稀记得一点中学学的多项式运算的知识,不难理解各个数位上的数字可以通过求余的方法快速获得,这个方法也就是我们后来听到的“短除法”。

951a5bfe340a7dbe652119f113198162.png

从下往上依次是176600745516,即(17012345678)10=(176600745516)8;

从上图可以看到“短除法”最小循环单元在做一件什么事情呢?

每一步都是用上一步结果去除以进制后舍弃法取整,最后将每一步的余数往前连接起来,因此不难想到我只需要使用递归把最小循环步骤实现,就完成了“短除法”下的进制转换。

let

Convert=(x,y)=>if Number.RoundDown(x/y)=0 then Text.From(Number.Mod(x,y)) else @Convert(Number.RoundDown(x/y),y)&Text.From(Number.Mod(x,y))

in

Convert(17012345678,8)

d2a60bfa316cb64e0ba94016d0ae3e0f.png

d1e13cf76dd6e0237c15e300ed360844.png

~~~~~~~~~~

案例4:定义一个分红包函数,输出每个人“抢”的金额(至少0.01元)。

~~~~~~~~~~

乍一看,不就是一个随机数生成的问题吗?

是的,的确就是一个生成随机数的问题,但是这个问题并不是想象的那么简单,首先我们需要考虑的是,随机数是一次生成的还是逐个生成的问题。这个问题很重要,涉及到M语句具体怎么编写。我自己做了一次尝试,两种方案都写了一遍,发现了一个有意思的现象:

如果我采用逐个生成的方法,也就是,第一个随机数是在总额范围内生成,第二个是在总额减去保底金额和已经生成的随机数后的范围内生成,以此类推……这样会非常大概率地发生随机数方差特别大的现象,什么意思呢?就是会比较容易出现类似这样的红包分布情况:(90,1,0.1,0.9,8)或者(50,10,5,35)。试想一下,如果100元丢进去,大部分人拿到几毛钱,一个人吃一大半,发的人(只有一个人发“谢谢老板”的表情包)和抢的人(大部分人没抢到啥钱)都不是特别开心,至于为什么会出现这样极度不均匀的情况,我猜想可能与缓存机制有关系,这一点在后面的Buffer篇章会讲到。

但是,办法总是比问题多!我尝试了另外一种方法,每个随机数都是在总金额范围内随机生成,最后一个随机数是总额减去前面数的值,这样也会有一个问题,就是除去最后一个值容易超包超预算,那我就给它套一个循环,最后一个符合标准就输出,不符合就一直重复生成前面的值,这样相当于每个值是同时生成的(实际不是),因此基本上随机值符合正态分布规律。但是,这个方法的效率低,因为加了一个递归循环,且人数多了容易发生计算堆栈溢出

两种算法我都分享给大家,如果没有大方差那种情况,逐个生成是一个不错的方案:

同范围随机法:

let

list=(m,p)=>List.Skip(List.Accumulate({1..p},{0},(x,y)=>

if y=p then x&{m-List.Sum(x)}

else x&{Number.Round(Number.RandomBetween(0.01,m-0.01*(p-1)),2)})),

listr=(m,p)=>

let temp=list(m,p)

in if temp{p-1}>=0.01 then temp else @listr(m,p)//套了一层循环检测机制

in

listr(200,6)

af32c37681db35df5184211dba2adb37.png

b30dd8cd665972956595981bb67b1039.png

试验了多次,结果很少有大方差的数据组生成。

aa1812dc0a21f614ef45eca21148bae7.png

当我将抢红包人数设置为20时,就出现了计算堆栈溢出的现象。

逐个生成法:

let

list=(m,p)=>List.Accumulate({1..p},{0},(x,y)=>//设置为{0}的原因是后面用到了Sum函数

if y=p then x&{m-List.Sum(x)}

else x&{Number.Round(Number.RandomBetween(0.01,m-0.01*(p-y)-List.Sum(x)),2)})

in

List.Skip(list(200,20))//使用Skip跳过设置的初始值

2e959f0e2e555dffcc0ad78f41f53494.png

20个人没有发生计算堆栈溢出,但是有14个人抢到的钱不足1元钱。

逐个生成法还可以使用递归的方式书写:

let

m=200,p=20,

list=(x,y)=>if x=p then y&{m-List.Sum(y)}

else @list(x+1,y&{Number.Round(

Number.RandomBetween(0.01,m-0.01*(p-x)-List.Sum(y)),2)})

in

list(1,{Number.Round(Number.RandomBetween(0.01,m-0.01*(p-1)),2)})

2fe4dc3a0477709a890b01e1b6c09d89.png

结果也是大部分人分到的钱不足1元。

哈哈,这样的红包估计大家抢的都不开心~


下篇笔记:《缓存》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值