Ruby 求一种转换数组的简洁、高端、大气、上档次的方法

neverlandxy_naix · 2013年05月29日 · 最后由 ChanceDoor 回复于 2013年06月03日 · 3704 次阅读
["0.0", "0.1", "0.2", "1.2", "1.3", "2.2", "2.3", "3.2", "4.0", "4.2"]
# 需要转换为
[["0.0", "0.1", "0.2"], [ "1.2", "1.3"], ["2.2", "2.3"], ["3.2"], ["4.0", "4.2"]]

就是根据数组中的元素,把它 split(".") 成为一个数组之后,如果数组的第一项相同,就把这个元素放到这样一个数组里面 ["0.0", "0.1", "0.2"] 比如说 "0.0", "0.1", "0.2" 分割后第一项都相同,就把它们放在一起

求一种简洁、高端、大气、上档次的转换方法~

能描述一下需求么?只看这个输入输出的话,我觉得可以这样

a = ["0.0", "0.1", "0.2", "1.2", "1.3", "2.2", "2.3", "3.2", "4.0", "4.2"]
output = [
  a[0..2],
  a[3..4],
  a[5..6],
  a[7..7],
  a[8..9],
]

假如需求是"按照每个元素的整数位分组", 那么可以这样

a = ["0.0", "0.1", "0.2", "1.2", "1.3", "2.2", "2.3", "3.2", "4.0", "4.2"]

grouped = a.group_by {|n| n.to_i.round}
output = grouped.values_at(*grouped.keys.sort) # => 应该就是你需要的.

不过话说回来,直接拿 grouped 这个 hash 去用可能更方便?而且,假如说输入的数据中没有 1.x 的数,那么这个方法的输出可能会有问题。总之还是希望 LZ 完整严密地描述一下需求。否则我觉得还是前一个办法更好

@5long 谢谢你的回答 其实就是根据数组中的元素,把它split(".")成为一个数组之后,如果数组的第一项相同,就把这个元素放到这样一个数组里面["0.0", "0.1", "0.2"] 比如说"0.0", "0.1", "0.2"分割后第一项都相同,就把它们放在一起

@5long 第二种方法很好使啊,谢谢了,我研究一下

["0.0", "0.1", "0.2", "1.2", "1.3", "2.2", "2.3", "3.2", "4.0", "4.2"].group_by{|s| s[0]}.values

data = ["0.0", "0.1", "0.2", "1.2", "1.3", "2.2", "2.3", "3.2", "4.0", "4.2"]
data.group_by(&:to_i).values
["0.0", "0.1", "0.2", "1.2", "1.3", "2.2", "2.3", "3.2", "4.0", "4.2", "11.1", "11.2"].group_by{|s| s.to_i}.values

如果需要保序,就用 chunk

例如

["0.0", "0.1", "1.2", "1.3", "0.2"].chunk(&:to_i).map &:last
#=> [["0.0", "0.1"], ["1.2", "1.3"], ["0.2"]] 最后的 0.2 不合并到第一项中去
["11.0", "0.1", "1.2", "1.3", "0.2"]
.group_by{|s|  s.match(/\d+\./)[0]  }

#1 楼 @5long #4 楼 @jjym #6 楼 @zgm #7 楼 @quakewang #8 楼 @wuwx #10 楼 @luikore #11 楼 @sevk

忍不住 at 了楼上所有人的原因是,你们真的觉得在这个问题里面用上了 :to_i 这个方法是合适的吗??

看到楼上的朋友用 :to_i,让我想起了大一上 C 语言课的时候,老师要我们计算一个数字有几个尾随的 0,比如 300 有两个,10000 有四个。由于 C 语言里面的字符串老师不打算让我们接触,于是就让我们通过求余的思维来做这一题,而且它也没提到 其实这题就逻辑上来说,是不应该用求余来做的

楼上的各位你们想想,楼主的需求要的是一种 模式 上的匹配!而你们却通过 :to_i 的方式来找出这种模式上的匹配,我觉得是不可取的一种思维。更正确的方式应该是通过字符串的思维来对待楼主的需求,即,通过正则来完成。

借用一下 #11 楼 的代码,我给出的答案是:

["11.0", "0.1", "1.2", "1.3", "0.2", "-11.5"].group_by{|s| s.match(/(.+?)./)[1] }

输出:

{"11"=>["11.0"], "0"=>["0.1", "0.2"], "1"=>["1.2", "1.3"], "-11"=>["-11.5"]}

果然自己的代码写得有点冗余~ .to_i 本身就已经转换成整数了,再 .round 没有意义。只调用 .to_i 的话就可以像 #7 楼 那样利用 &to_i 的语法了。

其实看了 #2 楼 的描述,我依然不能确定 lz 的原始需求是什么。如果能确定输入数据肯定都是干净的浮点数(只是暂时存成字符串),那么按 .to_i 的结果分组应该肯定是够用的。以及,如果数据是稀疏的,比如缺少 1.x 的输入,那么在输出的数组里原本属于 1.x 的位置上应该是 nil, 还是空数组,都只能看具体需求。

#12 楼 @alsotang 既然你用了正则,那么我也引用一下自己学习正则的知识。这本书教会我的一个重要理念是:先熟悉你手头的数据,正则写得足够好即可,十分严谨的、面面俱到的 parser 用一个巨大的正则来做非常的痛苦。像 LZ 这个问题,如果已知数据都是存成了字符串的浮点数,确定有“浮点数”这个语义在的话,用 .to_i 我觉得非常合适。但如果真如 #2 楼 所言,每个元素只是“中间恰好有一个 . 的字符串”,那么用正则确实更合适。在背景信息完整之前,我觉得没法断言哪个方案更正确。我们写代码都是想解决问题,而不是为了让自己的答案反映出自己学会了课本上的知识点。

依然,如果原始需求不明,数据来源不确定,output = [ a[0..2], a[3..4], ... ] 这个答案我觉得也未必不可。但是在社区里扔下这么一个答案就跑给人太 trolling 的感觉,所以我没敢这么做。

#14 楼 @5long 嗯 如果数据没有小数点 正则表达式就不能反映人类的理解了 还是 to_i 更图灵

#12 楼 @alsotang 写得越多,bug 就越多 ;(

["2\n1.1", "1.1", "2.2"].group_by{|s|  s.match(/(.+?)\./)[1]  }
["1", "2"].group_by{|s|  s.match(/(.+?)\./)[1]  }

TDD: 最简单的满足测试的实现就是最好的

#14 楼 @5long #16 楼 @luikore 嗯,看了 @luikore 的例子,我承认我的想法是有所偏差的,:to_i 是个很好的选择。

咋没人想着 split 呢?效率?

[.....].group_by { |n| n.split('.')[0] }

反正是方便,避免 match 不匹配@neverlandxy_naix @luikore

#15 楼 @ChanceDoor

/(.+?)\./)[1] 

这个没有小数点

#18 楼 @dddd1919 \. 不是小数点嘛

需要 登录 后方可回复, 如果你还没有账号请 注册新账号