免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 5892 | 回复: 14
打印 上一主题 下一主题

跟大家说说一个把我坑了整两天的bug,关于map的写法 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2015-04-22 18:46 |只看该作者 |倒序浏览
这几天被一个诡异的bug坑得死去活来,刚刚终于找出问题所在,让我对于map的写法有了更清楚的认识。
其实本不是什么高深的知识,我也不是什么perl高手。只是跟大家分享一下心得。

问题起源于一个乘法始终得不到正确结果:

  1. use List::Util qw(product);

  2. my @a = ("A","B","C");
  3. my %h = ("A"=>0.1, "B"=>0.2, "C"=>0.3);

  4. my $p = product(
  5.        map{$h{$_}}(@a),
  6.        10
  7. );
复制代码
我的本意应该是0.1*0.2*0.3*10 = 0.06
可结果总为0
由于原本整个程序比较长,算法有些复杂,那个哈希的键其实是个相当复杂的表达式,我花了很多时间来确认由数组@a输出的list内不含有0
然后我终于基本确定由@a输出的list里肯定不含有0,我说这可奇了怪了!但当我尝试把map那句后面加上:

  1. product(
  2.       map{$h{$_}}(@a) || 1,
  3.       10
  4. );
复制代码
输出神奇的不为0了。
我勒个去,这tm什么鬼???!!!

不过当我我验算了这输出的数据后,发现输出不是预期值。。。。。。艹艹艹
然后我开始疯狂的怀疑这个版本的list::util模块的product函数有bug,换了个perl版本,换了个机器验证。结果依然如故。。。

最后没办法了,我开始不断地向map中代入不同的数值内容以观察其输出:

  1. product(
  2.       map{$_+1}(1..4),
  3.       0.9
  4. );
复制代码
发现这个输出的是228,于是乎突然间意识到到底是发生了什么!
狗x的!map把小括号外面逗号后面的0.9也代了进去!!!!!
说好的小括号限定函数操作内容呢???!!!

然后我马上就回忆起了以前在文档里看过的map另一种表达方式 map EXPR,LIST:
@chars = map(chr, @numbers);
不过文档里和书里写map BLOCK LIST 这种表达方式的时候都是这么写:
my @squares = map { $_ * $_ } @numbers;
所以我一直以为在这种写法中,要加括号的话就给后面的list加就行了,没想到中招了。
在map BLOCK LIST这种写法中要加括号限定map操作内容的话应该这么加:
my @squares = map({ $_ * $_ } @numbers);

  1. product(
  2.       map({$_+1}(1..4)),
  3.       0.9
  4. );
复制代码
如此,得到了预期结果108.

改好了程序之后回头想一想,其实还有几个不明确的地方。
一是map的大括号BLOCK部分到底是算个什么成分?本质上它也就是map的第一个参数吗?
二是我在中间改的

  1. product(
  2.       map{$h{$_}}(@a) || 1,
  3.       10
  4. );
复制代码
这个代码得到的输出到底是怎么算的呢?好像是||操作符把@a转换成了scalar,然后不是0的话,就scalar@a和10两个标量元素的列表被带入map的BLOCK中进行输出。
不知道我的理解对不对?

求职 : 软件工程师
论坛徽章:
3
程序设计版块每日发帖之星
日期:2015-10-07 06:20:00程序设计版块每日发帖之星
日期:2015-12-13 06:20:00程序设计版块每日发帖之星
日期:2016-05-05 06:20:00
2 [报告]
发表于 2015-04-22 19:58 |只看该作者
本帖最后由 104359176 于 2015-04-22 20:15 编辑

你需要的是对一个列表的累乘,这都不是 map 或 grep 的工作范畴。

map 像一个流水线上的工序,会对列表中的每一个元素进行处理,改变其值,甚至,1个变成两个。所以经过 map 处理过的列表,元素的数量不会减少。

grep 像一个流水线上的质检员,对于符合条件的,才挑出去放在一起。所以经过 grep 过的列表,元素数量不会增加。

对一个列表中所有的元素一起计算,例如 sum, max, sort 就是这种函数,List::Util 就提供了 sum 累加这个函数,但没有什么模块提供了累乘的接口。其实这是一个很常用的运算,在 Lisp 语言中,内置了这种操作:
  1. (apply add list)
复制代码
如果用 Perl 来实现,用递归会比较简洁一些,当然用循环更好。

循环算法:
  1. sub mul_all {
  2.     my @list = @_;
  3.     my $mul_all = 1;
  4.     for my $num (@list) {
  5.         $mul_all = $mul_all * $num;
  6.     }
  7.     return $mul_all;
  8. }
复制代码
递归算法:
  1. sub mul_all {
  2.     my @list = @_;
  3.     my $first = shift @list;
  4.     if ($#list == 0) {
  5.         return $first;
  6.     elsif ($#list == 1) {
  7.         return $first * $list[0];
  8.     } else {
  9.         return $first * mul_all(@list);
  10.     }
  11. }
复制代码
如果用 Perl6 就更加简洁了:
  1. my $mul-all = reduce { $^a * $^b }, @list;
复制代码

论坛徽章:
0
3 [报告]
发表于 2015-04-22 20:02 |只看该作者
回复 2# 104359176

楼上没有看懂我的内容。
另外list::util模块从1.35版开始就已经提供了product函数来进行列表的连乘。知识需要更新啊。
   

论坛徽章:
0
4 [报告]
发表于 2015-04-22 20:04 |只看该作者
回复 2# 104359176

perl用递归或者循环实现连乘效率真的很低,推荐大家有需要的话都来使用product函数。


   

求职 : 软件工程师
论坛徽章:
3
程序设计版块每日发帖之星
日期:2015-10-07 06:20:00程序设计版块每日发帖之星
日期:2015-12-13 06:20:00程序设计版块每日发帖之星
日期:2016-05-05 06:20:00
5 [报告]
发表于 2015-04-22 20:59 |只看该作者
回复 4# kidaaaa


    你代码的问题是:10,被 map 吃掉了,没有 product 的份:
  1. #!perl

  2. use List::Util qw(product);

  3. my @a = ("A","B","C");
  4. my %h = ("A"=>0.1, "B"=>0.2, "C"=>0.3);

  5. say product((map{$h{$_}} @a), 10); # 0.06
复制代码
建议不要将表达式放在参数中,因为许多函数会把后面所有的参数都吃掉。

论坛徽章:
2
射手座
日期:2014-10-10 15:59:4715-16赛季CBA联赛之上海
日期:2016-03-03 10:27:14
6 [报告]
发表于 2015-04-22 22:25 |只看该作者
看来半天觉得不是map本身的问题,是没有理解map的用法以及product函数的原型

论坛徽章:
145
技术图书徽章
日期:2013-10-01 15:32:13戌狗
日期:2013-10-25 13:31:35金牛座
日期:2013-11-04 16:22:07子鼠
日期:2013-11-18 18:48:57白羊座
日期:2013-11-29 10:09:11狮子座
日期:2013-12-12 09:57:42白羊座
日期:2013-12-24 16:24:46辰龙
日期:2014-01-08 15:26:12技术图书徽章
日期:2014-01-17 13:24:40巳蛇
日期:2014-02-18 14:32:59未羊
日期:2014-02-20 14:12:13白羊座
日期:2014-02-26 12:06:59
7 [报告]
发表于 2015-04-22 23:29 |只看该作者
回复 1# kidaaaa

1. To enable all warnings and to see something wrong
   use warnings;


$ perl product.pl
0.06
Use of uninitialized value in subroutine entry at product.pl line 16.
0

$ cat product.pl
use strict;
use warnings;

use List::Util qw(product);

my @a = ("A","B","C");
my %h = ("A"=>0.1, "B"=>0.2, "C"=>0.3);

my $p;
$p = product(
     10,
     map{$h{$_}}(@a),
);
print "$p\n";
$p = product(
     map{$h{$_}}(@a),
     10,
);
print "$p\n";

   

论坛徽章:
0
8 [报告]
发表于 2015-04-23 03:52 |只看该作者
yinyuemi 发表于 2015-04-22 22:25
看来半天觉得不是map本身的问题,是没有理解map的用法以及product函数的原型


我的意思就是这个问题让我意识到map的写法值得注意。
map后的BLOCK和LIST两个部分如果需要小括号限定的话,两个部分需要都被包括,而不能只包括LIST部分,否则后面的LIST会吃掉所有可以吃掉的东西。
也就是说,某种意义上讲可以把BLOCK 和 LIST两个部分看作map函数的两个参数,和map EXPR,LIST 这种写法是一样的。

product函数的使用没有任何问题,我碰到的问题和它毫无关系。



论坛徽章:
0
9 [报告]
发表于 2015-04-23 03:59 |只看该作者
本帖最后由 kidaaaa 于 2015-04-23 04:03 编辑

回复 7# jason680


呃,是我帖子写的太乱不好懂吗?好像大家都没get我的point。。。。

确实,回头想想我碰到的问题如果开warnings会帮助我更快的找到问题原因所在,不过我的程序其它有些地方用的写法开了warnings会报一些很烦的东西,所以就没开。谢谢提醒。
然后你把list的两个部分换了个位置确实可以避免这个问题,不过对理解问题的本质没有什么帮助。

论坛徽章:
145
技术图书徽章
日期:2013-10-01 15:32:13戌狗
日期:2013-10-25 13:31:35金牛座
日期:2013-11-04 16:22:07子鼠
日期:2013-11-18 18:48:57白羊座
日期:2013-11-29 10:09:11狮子座
日期:2013-12-12 09:57:42白羊座
日期:2013-12-24 16:24:46辰龙
日期:2014-01-08 15:26:12技术图书徽章
日期:2014-01-17 13:24:40巳蛇
日期:2014-02-18 14:32:59未羊
日期:2014-02-20 14:12:13白羊座
日期:2014-02-26 12:06:59
10 [报告]
发表于 2015-04-23 09:21 |只看该作者
回复 9# kidaaaa

It's up to you

>> 跟大家说说一个把我坑了整两天的bug...

bug is not big deal, but 两天two days ...
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP