如何计算CSV文件中的“是”答案(在特定列中具有特定值的行)?

时间:2019-04-27 09:25:46

标签: ruby csv

我有一个摘要,我想为每个建议计数“是”和“否”:

https://images.homedepot-static.com/productImages/e0b36c9f-48c2-4fbd-ace8-3fa0e877aacc/svn/york-wallcoverings-wallpaper-at7053-64_1000.jpg

如何检索列的内容? url.openStream())

所以我从这样的事情开始:

import java.net.URL

4 个答案:

答案 0 :(得分:1)

这不是一个非常优雅的解决方案,但是是以“经典”(迭代编程)的方式完成的。我除去了Rails的内容,因此它是一个独立的应用程序。您必须将Rails-Stuff重新放回去。

请注意,您的CSV示例使用“ |” (管道)作为分隔符。

require 'csv'

# Start with 0 of each
counts = {cats: 0, dogs: 0, rabbits: 0}
# run over every row
CSV.foreach("c.csv", headers: true, col_sep: "|") do |row|
  # Check answers in each column and increase count if "Yes"
  if row[1].strip == 'Yes'                                         
    counts[:cats] = counts[:cats] + 1                              
  end                                                              
  if row[2].strip == 'Yes'                                         
    counts[:dogs] = counts[:dogs] + 1                              
  end                                                              
  if row[3].strip == 'Yes'                                         
    counts[:rabbits] = counts[:rabbits] + 1                        
  end                                                              
end                                                                

puts counts # Will print {:cats=>2, :dogs=>1, :rabbits=>2}

请注意,可以多种方式极大地简化CSV-Access /使CSV-Access更具可读性(请参见https://ruby-doc.org/stdlib-2.0.0/libdoc/csv/rdoc/CSV.html)。通过使用Enumerable模块https://ruby-doc.org/core-2.6.2/Enumerable.html中的方法,可以大大简化和美化计数和分组,在示例代码中是通过手动遍历每一行和每一列来完成的。阅读和理解给定的链接将大大提高您的编程性能。

有趣的学习和黑客活动!

答案 1 :(得分:1)

csv =<<-END
Name | Cats| Dogs| Rabbits|
john | Yes | No  | No    |
max  | No  | No  | Yes   |
oli  | Yes | Yes | Yes   |
END

FNAME = 'temp.csv'
File.write(FNAME, csv)
  #=> 109

使用CSV方法

我们可以如下使用CSV方法。

require 'csv'

csv = CSV.read(FNAME, headers: true, col_sep: '|')
csv.headers.each_with_object({}) do |animal,h|
  unless animal.nil? || animal.strip == "Name"
    h[animal.strip.downcase] = csv[animal].count { |s| s.strip == "Yes" }
  end
end
  #=> {"cats"=>2, "dogs"=>1, "rabbits"=>2}

在预处理文件后使用CSV方法

在这里使用CSV方法有点麻烦。对于一个:

csv.headers
  #=> ["Name ", " Cats", " Dogs", " Rabbits", nil] 

存在元素nil的原因是分隔符为|,并且该字符出现在每行的末尾。第二个问题是空间的存在。例如,如果列标签" Cats""Cats",或者更好的是"cats",则会更方便。

鉴于这些复杂性,人们可能会考虑对文件进行一些简单的预处理,以使其更易于应用CSV方法。

TEMP_FNAME = 'temp1.csv'    
File.write(TEMP_FNAME, File.read(FNAME).delete(' ').downcase.gsub(/\|$/,''))
  #=> 68

让我们看看所写的内容。

puts File.read(TEMP_FNAME)
name|cats|dogs|rabbits
john|yes|no|no
max|no|no|yes
oli|yes|yes|yes

我们现在可以很容易地构造所需的哈希。

csv = CSV.read(TEMP_FNAME, headers: true, col_sep: '|')
csv.headers.each_with_object({}) do |animal,h|
  h[animal] = csv[animal].count("yes") unless animal == 'name'
end
  #=> {"cats"=>2, "dogs"=>1, "rabbits"=>2}

可以说,分两个步骤进行操作还可以简化调试和测试。

将文件视为普通文本文件

当文件的内容不允许直接使用CSV方法时,将文件视为普通文本文件可能更简单:

File.read(FNAME).downcase.split("\n").
     map { |line| line.split(/ *\| */)[1..] }.transpose.
     each_with_object({}) { |(lbl,*rest),h| h[lbl]=rest.count('yes') }
       #=> {"cats"=>2, "dogs"=>1, "rabbits"=>2}

步骤如下。

a = File.read(FNAME).downcase.split("\n")
puts a
name | cats| dogs| rabbits|
john | yes | no  | no    |
max  | no  | no  | yes   |
oli  | yes | yes | yes   |

b = a.map { |line| line.split(/ *\| */)[1..] }
  #=> [["cats", "dogs", "rabbits"],
  #    ["yes", "no", "no"],
  #    ["no", "no", "yes"],
  #    ["yes", "yes", "yes"]] 
c = b.transpose
  #=> [["cats", "yes", "no", "yes"],
  #    ["dogs", "no", "no", "yes"],
  #    ["rabbits", "no", "yes", "yes"]] 
c.each_with_object({}) { |(lbl,*rest),h| h[lbl]=rest.count('yes') }
  #=> {"cats"=>2, "dogs"=>1, "rabbits"=>2} 

答案 2 :(得分:0)

这可以做到:

CSV.open("summary.csv", col_sep: ";")
  .to_a
  .transpose[1..]
  .map { |(name, *data)| [name, data.count { |val| val == "Yes" }] }
  .to_h

其输出:

{
  "Cats" => 2,
  "Dogs" => 1,
  "Rabbits" => 2
}

分步说明:

首先,让我们阅读数据:

irb> CSV.open("x.csv", col_sep: ";").to_a
=> [["Name", "Cats", "Dogs", "Rabbits"], ["john", "Yes", "No", "No"], ["max", "No", "No", "Yes"], ["oli", "Yes", "Yes", "Yes"]]

接下来,转置以将动物和价值观放在一起:

irb> CSV.open("x.csv", col_sep: ";").to_a.transpose
=> [["Name", "john", "max", "oli"], ["Cats", "Yes", "No", "Yes"], ["Dogs", "No", "No", "Yes"], ["Rabbits", "No", "Yes", "Yes"]]

请注意第一个元素是不相关的,因此我们通过附加[1..]以获取除第一个元素以外的所有元素来忽略它。

然后,我们只需要将每个数组元素转换为动物名和"Yes"值的计数即可。对于单个元素,我们可以执行以下操作:

row = ["Cats", "Yes", "No", "Yes"]
(name, *data) = row

name将是"Cats",而data将包含其他元素,即它将是["Yes", "No", "Yes"]

现在,我们只需要计算"Yes"中的data值:

irb> [name, data.count { |val| val == "Yes" }]
=> ["Cats", 2]

使用map,我们可以对转置数据集的所有元素执行此操作。调用#to_h会将每个数组的第一个元素用作键,将第二个元素用作值。

答案 3 :(得分:0)

首先将数据结算到table中:

require 'csv'

raw_table = File.read("summary.csv")
table = CSV.parse(raw_table.gsub(' ', ''), col_sep: '|', headers: true)

这会将数据读入字符串,删除空格并使用分隔符和标头解析为CSV。

列分隔符为'|',因为最右端有一个分隔符,它会生成一个空列。然后需要将其删除,请参阅下面在此处使用的.compact[1...-1]


然后,一个选项os将表作为哈希数组处理:

h_table = table.map { |e| e.to_h.compact }
#=> [{"Name"=>"John", "Cats"=>"Yes", "Dogs"=>"No", "Rabbits"=>"No"}, {"Name"=>"Max", "Cats"=>"No", "Dogs"=>"No", "Rabbits"=>"Yes"}, {"Name"=>"Oli", "Cats"=>"Yes", "Dogs"=>"Yes", "Rabbits"=>"Yes"}]

counts设置为零,然后扫描h_table

counts = h_table.first.transform_values { |v| 0 }.tap{ |h| h.delete 'Name'}
counts #=> {"Cats"=>0, "Dogs"=>0, "Rabbits"=>0}
counts.keys.each { |k| h_table.each { |h| counts[k] += 1 if h[k] == 'Yes' } }
counts #=> {"Cats"=>2, "Dogs"=>1, "Rabbits"=>2}


或(最佳选择)转换为数组并按@fphilipe所述处理已转置的数组:

table.to_a.transpose[1...-1].each_with_object({}) { |(k, *v), h| h[k] = v.count{ |e| e == 'Yes' } }
#=> {"Cats"=>2, "Dogs"=>1, "Rabbits"=>2}


但是如果原始输入是

Name;Cats;Dogs;Rabbits
John;Yes;No ;No
Max;No ;No ;Yes
Oli;Yes;Yes;Yes

只需使用table = CSV.read("summary.csv", col_sep: ';', headers: true)然后应用上述方法之一,只需不删除空列即可。