|
- #!/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
|