#!/usr/bin/env ruby # All modules must be in the standard library. No third-party gems. require 'json' require 'date' require 'pp' def date_format(d) d.strftime("%-d %B %Y") end def divider puts '-' * 80 end def first_word(s) s.split(" ").first end def subhead(s) puts puts puts s puts '-' * s.length end # FIXME # Need to be clear what the input and output of this function should be. # We should return an array of hashes (sorted?) not an array of arrays. # This is essentially a reduce function. Do it generically? # https://riptutorial.com/ruby/example/3624/inject--reduce # https://ruby-doc.org/core-3.1.2/Enumerable.html#method-i-reduce # https://stackoverflow.com/questions/4453511/group-hashes-by-keys-and-sum-the-values # Return party names and aggregate sum of votes for each party. def votes_by_party(ary_of_candidate_hashes) #pp ary_of_candidate_hashes output = {} ary_of_candidate_hashes.each do |candidate| if output.key?(candidate['party']) output[candidate['party']] += candidate['votes'] else output[candidate['party']] = candidate['votes'] end end #pp output output end # Load JSON data unless (param = ARGV.shift) abort "Usage: #{$0} [FILE] or #{$0} -- to read from STDIN." end if param == '--' # Read JSON data from STDIN json = ARGF.read else # Load JSON from file json = File.read(param) end data = JSON.parse(json) # Bodies data['bodies'].each do |body| puts body['name'] puts "Elections: %d" % body['elections'].size # Elections sorted_elections = body['elections'].sort_by { |h| h['date'] }.reverse sorted_elections.each do |election| puts divider puts election['name'].upcase puts date_format(Date.parse(election['date'])) puts election['reason'] if election['reason'] puts "%d seats were elected in %d %s." % [ election['seats'], election['districts'], body['district_name_plural'] ] divider # Polls sorted_polls = election['polls'].sort_by { |h| h['district'] } sorted_polls.each do |poll| puts puts poll['district'].upcase puts # Candidates sorted_candidates = poll['candidates'].sort_by { |h| h['votes'] }.reverse sorted_candidates.each do |candidate| percentage = candidate['votes'].to_f / poll['votes'] * 100.0 * poll['seats'] candidate['elected'] ? elected = "elected" : "" puts "%-31s %-30s %5d %2d%% %s" % [ first_word(candidate['forenames']) + ' ' + candidate['surname'], candidate['party'], candidate['votes'], percentage, elected ] end # Total votes in this poll if poll['votes'] puts "%68s" % [ "=" * poll['votes'].to_s.size ] puts "%68d" % poll['votes'] end # Rejected ballots in this poll if poll['rejected'] puts puts "Rejected ballots: %d" % poll['rejected'] end # Votes by party # Omit for single-seat polls as meaningless there. unless poll["seats"] == 1 subhead("Votes by party") vbp = votes_by_party(poll['candidates']) # Sort array of arrays by the value in element [1], descending sorted_vbp = vbp.sort { |a,b| b[1] <=> a[1] } #pp sorted_vbp sorted_vbp.each do |party| puts "%-30s %5d %2d%%" % [ party[0], party[1], party[1].to_f / poll["votes"] * 100.0 ] end end # Sources if poll["sources"] subhead("Sources") poll["sources"].each { |source| puts source } end puts divider end divider end end