Ruby CSV 文件如何做一个类似合并单元格的操作

mfb777 · September 12, 2018 · Last by swordray replied at September 14, 2018 · 8459 hits

比如有这样的一个 CSV 文件:

有什么比较优雅的方法可以变成这样:

{
"A": ["123", "1223", "12343", "122XX33"],
"B": ["678","612378", "67XX8"],
"C": ["100", "1000"]
}

CSV 应该有个 header 的,可以利用inject,像这样:

data = [
  {h1: 'A', h2: '123'},
  {h1: 'A', h2: '1223'},
  {h1: 'A', h2: '12343'},
  {h1: 'A', h2: '123XX33'},
  {h1: 'B', h2: '678'},
  {h1: 'B', h2: '612378'},
  {h1: 'B', h2: '67XX8'},
  {h1: 'C', h2: '100'},
  {h1: 'C', h2: '1000'}
]
data.inject({}) { |r, e| r[e[:h1]] = Array(r[e[:h1]]) << e[:h2]; r }
#=>  {"A"=>["123", "1223", "12343", "123XX33"], "B"=>["678", "612378", "67XX8"], "C"=>["100", "1000"]}

# or

data.map{|x| Hash[*x.values]}.inject(&lambda{|x,y| x.merge(y){ |_, o, n| [o, n].flatten }})
#=> {"A"=>["123", "1223", "12343", "123XX33"], "B"=>["678", "612378", "67XX8"], "C"=>["100", "1000"]}

感谢,这段代码很烧脑。inject 的方法大概看懂了。

第二个方法大概看了下好像更恐怖的样子。

可以先用数组的前一个元素做 group_by,然后用 map! 方法将后一个元素做 replace:

data = [['A', '123'], ['A', '1223'], ['A', '12343'], ['A', '122XX33'], ['B', '678'], ['B', '612378'], ['B', '67XX8'], ['C', '100'], ['C', '1000']]

data.group_by(&:first).each{|_, v| v.map!(&:last)}

# => {"A"=>["123", "1223", "12343", "122XX33"], "B"=>["678", "612378", "67XX8"], "C"=>["100", "1000"]} 
h = {}
data.each {|a, b| (h[a] ||= []) << b }

如果想要奇技淫巧,可以用 Sqlite 的 CSV mode ...

textql -header -sql "select name, group_concat(value) from csv group by name" csv.txt

Reply to hooopo

TextQL 确实很好用,省了很多事。

Reply to quakewang

其实我就是想要这个函数 group_by,在 Array 类里找了半天无果。😂

data.group_by(&:first).transform_values { |value| value.pluck(1) }
10 Floor has deleted
You need to Sign in before reply, if you don't have an account, please Sign up first.