From d028e95d24213520f2a751bd18d2b6079dfedfe9 Mon Sep 17 00:00:00 2001 From: Bhanu Pratap Date: Tue, 31 Aug 2021 13:17:44 +0530 Subject: [PATCH 1/4] Add program to find most expensive click --- ruby/programs/click_handler/click_handler.rb | 95 ++++++++++++++++++++ ruby/programs/click_handler/clicks.json | 34 +++++++ ruby/programs/click_handler/result.json | 10 +++ 3 files changed, 139 insertions(+) create mode 100644 ruby/programs/click_handler/click_handler.rb create mode 100644 ruby/programs/click_handler/clicks.json create mode 100644 ruby/programs/click_handler/result.json diff --git a/ruby/programs/click_handler/click_handler.rb b/ruby/programs/click_handler/click_handler.rb new file mode 100644 index 0000000..6ac95c3 --- /dev/null +++ b/ruby/programs/click_handler/click_handler.rb @@ -0,0 +1,95 @@ +require 'json' +require 'date' + +class JsonFilehandler + + def self.read_file(path) + File.open(path).read + end + + def self.write_file(path, res) + + File.open(path, "w+") do |f| + f.write("[\n") + res.each do |click| + result = '{' + click.map { |k, v| "#{k.to_json}:#{v.to_json}" }.join(', ') + "},\n" + f.write(result) + end + f.write("]") + end + end + +end + +class Parser + + def self.to_json(file_data) + JSON.parse(file_data) + end + + def self.collect_valid_clicks(click_data) + click_freq = Hash.new(0) + click_data.each { |ele| click_freq[ele['ip']] += 1 } + # Remove all IPs with clicks greater than 10 + click_freq.each { |k, v| if v >= 10 ; click_freq.delete(k) end} + click_ips = click_freq.keys + click_data.select { |ele| click_ips.include?(ele['ip'])} + end + + def self.find_period(timestamp) + DateTime.parse(timestamp).hour + end + + def self.clicks_per_period(valid_clicks) + ip_amt_per_period = {} + ip_timestamp_per_period = {} + valid_clicks.each do |click| + period = DateTime.parse(click["timestamp"]).hour + if ip_amt_per_period.key?(click["ip"]) + if ip_amt_per_period[click["ip"]].key?(period) && ip_amt_per_period[click["ip"]][period] < click["amount"] + ip_amt_per_period[click["ip"]][period] = click["amount"] + ip_timestamp_per_period[click["ip"]].merge!({period => click["timestamp"]}) + elsif ip_amt_per_period[click["ip"]].key?(period) && ip_amt_per_period[click["ip"]][period] > click["amount"] + next + elsif ip_amt_per_period[click["ip"]].key?(period) && ip_amt_per_period[click["ip"]][period] == click["amount"] + ip_timestamp_per_period[click["ip"]][period] = DateTime.parse(click["timestamp"]) < DateTime.parse(ip_timestamp_per_period[click["ip"]][period]) ? + click["timestamp"] : ip_timestamp_per_period[click["ip"]] + else + ip_amt_per_period[click["ip"]].merge!({period => click["amount"]}) + ip_timestamp_per_period[click["ip"]].merge!({period => click["timestamp"]}) + end + + else + ip_amt_per_period[click["ip"]] = {} + ip_timestamp_per_period[click["ip"]] = {} + ip_amt_per_period[click["ip"]][period] = click["amount"] + ip_timestamp_per_period[click["ip"]][period] = click["timestamp"] + end + end + return ip_amt_per_period, ip_timestamp_per_period + end + + def self.form_output_array(ip_amt_per_period, ip_timestamp_per_period) + res = [] + ip_amt_per_period.each do |k,v| + v.each {|period, value| res.push({"ip" => k, "timestamp" => ip_timestamp_per_period[k][period] , "amount" => value})} + end + res + end +end + + +class ClickHandler + + def self.main + all_clicks = [] + file_data = JsonFilehandler.read_file("clicks.json") + click_data = Parser.to_json(file_data) + valid_clicks = Parser.collect_valid_clicks(click_data) + ip_amt_per_period, ip_timestamp_per_period = Parser.clicks_per_period(valid_clicks) + res = Parser.form_output_array(ip_amt_per_period, ip_timestamp_per_period) + JsonFilehandler.write_file("result.json", res) + end +end + +ClickHandler.main \ No newline at end of file diff --git a/ruby/programs/click_handler/clicks.json b/ruby/programs/click_handler/clicks.json new file mode 100644 index 0000000..f1ec2f1 --- /dev/null +++ b/ruby/programs/click_handler/clicks.json @@ -0,0 +1,34 @@ +[ + { "ip":"22.22.22.22", "timestamp":"3/11/2020 02:02:58", "amount": 7.00 }, + { "ip":"11.11.11.11", "timestamp":"3/11/2020 02:12:32", "amount": 6.50 }, + { "ip":"11.11.11.11", "timestamp":"3/11/2020 02:13:11", "amount": 7.25 }, + { "ip":"44.44.44.44", "timestamp":"3/11/2020 02:13:54", "amount": 8.75 }, + { "ip":"22.22.22.22", "timestamp":"3/11/2020 05:02:45", "amount": 11.00 }, + { "ip":"44.44.44.44", "timestamp":"3/11/2020 06:32:42", "amount": 5.00 }, + { "ip":"22.22.22.22", "timestamp":"3/11/2020 06:35:12", "amount": 2.00 }, + { "ip":"11.11.11.11", "timestamp":"3/11/2020 06:45:01", "amount": 12.00 }, + { "ip":"11.11.11.11", "timestamp":"3/11/2020 06:59:59", "amount": 11.75 }, + { "ip":"22.22.22.22", "timestamp":"3/11/2020 07:01:53", "amount": 1.00 }, + { "ip":"11.11.11.11", "timestamp":"3/11/2020 07:02:54", "amount": 4.50 }, + { "ip":"33.33.33.33", "timestamp":"3/11/2020 07:02:54", "amount": 15.75 }, + { "ip":"66.66.66.66", "timestamp":"3/11/2020 07:02:54", "amount": 14.25 }, + { "ip":"22.22.22.22", "timestamp":"3/11/2020 07:03:15", "amount": 12.00 }, + { "ip":"22.22.22.22", "timestamp":"3/11/2020 08:02:22", "amount": 3.00 }, + { "ip":"22.22.22.22", "timestamp":"3/11/2020 09:41:50", "amount": 4.00 }, + { "ip":"22.22.22.22", "timestamp":"3/11/2020 10:02:54", "amount": 5.00 }, + { "ip":"22.22.22.22", "timestamp":"3/11/2020 11:05:35", "amount": 10.00 }, + { "ip":"22.22.22.22", "timestamp":"3/11/2020 13:02:21", "amount": 6.00 }, + { "ip":"55.55.55.55", "timestamp":"3/11/2020 13:02:40", "amount": 8.00 }, + { "ip":"44.44.44.44", "timestamp":"3/11/2020 13:02:55", "amount": 8.00 }, + { "ip":"55.55.55.55", "timestamp":"3/11/2020 13:33:34", "amount": 8.00 }, + { "ip":"55.55.55.55", "timestamp":"3/11/2020 13:42:24", "amount": 8.00 }, + { "ip":"55.55.55.55", "timestamp":"3/11/2020 13:47:44", "amount": 6.25 }, + { "ip":"55.55.55.55", "timestamp":"3/11/2020 14:02:54", "amount": 4.25 }, + { "ip":"55.55.55.55", "timestamp":"3/11/2020 14:03:04", "amount": 5.25 }, + { "ip":"55.55.55.55", "timestamp":"3/11/2020 15:12:55", "amount": 6.25 }, + { "ip":"22.22.22.22", "timestamp":"3/11/2020 16:02:36", "amount": 8.00 }, + { "ip":"55.55.55.55", "timestamp":"3/11/2020 16:22:11", "amount": 8.50 }, + { "ip":"55.55.55.55", "timestamp":"3/11/2020 17:18:19", "amount": 11.25 }, + { "ip":"55.55.55.55", "timestamp":"3/11/2020 18:19:20", "amount": 9.00 }, + { "ip":"22.22.22.22", "timestamp":"3/11/2020 23:59:59", "amount": 9.00 } +] diff --git a/ruby/programs/click_handler/result.json b/ruby/programs/click_handler/result.json new file mode 100644 index 0000000..be8588d --- /dev/null +++ b/ruby/programs/click_handler/result.json @@ -0,0 +1,10 @@ +[ +{"ip":"11.11.11.11", "timestamp":"3/11/2020 02:13:11", "amount":7.25}, +{"ip":"11.11.11.11", "timestamp":"3/11/2020 06:45:01", "amount":12.0}, +{"ip":"11.11.11.11", "timestamp":"3/11/2020 07:02:54", "amount":4.5}, +{"ip":"44.44.44.44", "timestamp":"3/11/2020 02:13:54", "amount":8.75}, +{"ip":"44.44.44.44", "timestamp":"3/11/2020 06:32:42", "amount":5.0}, +{"ip":"44.44.44.44", "timestamp":"3/11/2020 13:02:55", "amount":8.0}, +{"ip":"33.33.33.33", "timestamp":"3/11/2020 07:02:54", "amount":15.75}, +{"ip":"66.66.66.66", "timestamp":"3/11/2020 07:02:54", "amount":14.25}, +] \ No newline at end of file From 22abb317166399555d2ad5d3a53468df5b0a2d16 Mon Sep 17 00:00:00 2001 From: Bhanu Pratap Date: Tue, 31 Aug 2021 13:30:59 +0530 Subject: [PATCH 2/4] Remove last comma from json --- ruby/programs/click_handler/click_handler.rb | 8 ++++++-- ruby/programs/click_handler/result.json | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ruby/programs/click_handler/click_handler.rb b/ruby/programs/click_handler/click_handler.rb index 6ac95c3..a87673f 100644 --- a/ruby/programs/click_handler/click_handler.rb +++ b/ruby/programs/click_handler/click_handler.rb @@ -11,8 +11,12 @@ def self.write_file(path, res) File.open(path, "w+") do |f| f.write("[\n") - res.each do |click| - result = '{' + click.map { |k, v| "#{k.to_json}:#{v.to_json}" }.join(', ') + "},\n" + res.each_with_index do |click, idx| + if idx != res.size-1 + result = '{' + click.map { |k, v| "#{k.to_json}:#{v.to_json}" }.join(', ') + "},\n" + else + result = '{' + click.map { |k, v| "#{k.to_json}:#{v.to_json}" }.join(', ') + "}\n" + end f.write(result) end f.write("]") diff --git a/ruby/programs/click_handler/result.json b/ruby/programs/click_handler/result.json index be8588d..12797cd 100644 --- a/ruby/programs/click_handler/result.json +++ b/ruby/programs/click_handler/result.json @@ -6,5 +6,5 @@ {"ip":"44.44.44.44", "timestamp":"3/11/2020 06:32:42", "amount":5.0}, {"ip":"44.44.44.44", "timestamp":"3/11/2020 13:02:55", "amount":8.0}, {"ip":"33.33.33.33", "timestamp":"3/11/2020 07:02:54", "amount":15.75}, -{"ip":"66.66.66.66", "timestamp":"3/11/2020 07:02:54", "amount":14.25}, +{"ip":"66.66.66.66", "timestamp":"3/11/2020 07:02:54", "amount":14.25} ] \ No newline at end of file From 4668cd0e48c054751f4e89bf4e0faf6f7aaf8026 Mon Sep 17 00:00:00 2001 From: Bhanu Pratap Date: Tue, 31 Aug 2021 13:38:08 +0530 Subject: [PATCH 3/4] Adding period value to timestamp in case of equal amount --- ruby/programs/click_handler/click_handler.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/programs/click_handler/click_handler.rb b/ruby/programs/click_handler/click_handler.rb index a87673f..f788d89 100644 --- a/ruby/programs/click_handler/click_handler.rb +++ b/ruby/programs/click_handler/click_handler.rb @@ -57,7 +57,7 @@ def self.clicks_per_period(valid_clicks) next elsif ip_amt_per_period[click["ip"]].key?(period) && ip_amt_per_period[click["ip"]][period] == click["amount"] ip_timestamp_per_period[click["ip"]][period] = DateTime.parse(click["timestamp"]) < DateTime.parse(ip_timestamp_per_period[click["ip"]][period]) ? - click["timestamp"] : ip_timestamp_per_period[click["ip"]] + click["timestamp"] : ip_timestamp_per_period[click["ip"]][period] else ip_amt_per_period[click["ip"]].merge!({period => click["amount"]}) ip_timestamp_per_period[click["ip"]].merge!({period => click["timestamp"]}) From 7bd1e9687ebf245325ebe7195d4a6915f7134165 Mon Sep 17 00:00:00 2001 From: Bhanu Pratap Date: Tue, 31 Aug 2021 17:04:35 +0530 Subject: [PATCH 4/4] Partial refactor as per rubocop --- ruby/programs/click_handler/click_handler.rb | 81 +++++++++----------- ruby/programs/click_handler/result.json | 16 ++-- 2 files changed, 46 insertions(+), 51 deletions(-) diff --git a/ruby/programs/click_handler/click_handler.rb b/ruby/programs/click_handler/click_handler.rb index f788d89..2132ff5 100644 --- a/ruby/programs/click_handler/click_handler.rb +++ b/ruby/programs/click_handler/click_handler.rb @@ -1,32 +1,30 @@ +# frozen_string_literal: true + require 'json' require 'date' +# Class having json file operations class JsonFilehandler - def self.read_file(path) File.open(path).read end def self.write_file(path, res) - - File.open(path, "w+") do |f| + File.open(path, 'w+') do |f| f.write("[\n") res.each_with_index do |click, idx| - if idx != res.size-1 - result = '{' + click.map { |k, v| "#{k.to_json}:#{v.to_json}" }.join(', ') + "},\n" - else - result = '{' + click.map { |k, v| "#{k.to_json}:#{v.to_json}" }.join(', ') + "}\n" - end + key_val = click.map { |k, v| "#{k.to_json}:#{v.to_json}" }.join(', ') + result = "{ #{key_val} }\n" + result += ',' if idx != res.size - 1 f.write(result) end - f.write("]") + f.write(']') end end - end +# Class having all data parsing operations class Parser - def self.to_json(file_data) JSON.parse(file_data) end @@ -35,65 +33,62 @@ def self.collect_valid_clicks(click_data) click_freq = Hash.new(0) click_data.each { |ele| click_freq[ele['ip']] += 1 } # Remove all IPs with clicks greater than 10 - click_freq.each { |k, v| if v >= 10 ; click_freq.delete(k) end} + click_freq.each { |k, v| click_freq.delete(k) if v >= 10 } click_ips = click_freq.keys - click_data.select { |ele| click_ips.include?(ele['ip'])} - end - - def self.find_period(timestamp) - DateTime.parse(timestamp).hour + click_data.select { |ele| click_ips.include?(ele['ip']) } end def self.clicks_per_period(valid_clicks) ip_amt_per_period = {} ip_timestamp_per_period = {} valid_clicks.each do |click| - period = DateTime.parse(click["timestamp"]).hour - if ip_amt_per_period.key?(click["ip"]) - if ip_amt_per_period[click["ip"]].key?(period) && ip_amt_per_period[click["ip"]][period] < click["amount"] - ip_amt_per_period[click["ip"]][period] = click["amount"] - ip_timestamp_per_period[click["ip"]].merge!({period => click["timestamp"]}) - elsif ip_amt_per_period[click["ip"]].key?(period) && ip_amt_per_period[click["ip"]][period] > click["amount"] + period = DateTime.parse(click['timestamp']).hour + if ip_amt_per_period.key?(click['ip']) + if ip_amt_per_period[click['ip']].key?(period) && ip_amt_per_period[click['ip']][period] < click['amount'] + ip_amt_per_period[click['ip']][period] = click['amount'] + ip_timestamp_per_period[click['ip']].merge!({ period => click['timestamp'] }) + elsif ip_amt_per_period[click['ip']].key?(period) && ip_amt_per_period[click['ip']][period] > click['amount'] next - elsif ip_amt_per_period[click["ip"]].key?(period) && ip_amt_per_period[click["ip"]][period] == click["amount"] - ip_timestamp_per_period[click["ip"]][period] = DateTime.parse(click["timestamp"]) < DateTime.parse(ip_timestamp_per_period[click["ip"]][period]) ? - click["timestamp"] : ip_timestamp_per_period[click["ip"]][period] + elsif ip_amt_per_period[click['ip']].key?(period) && ip_amt_per_period[click['ip']][period] == click['amount'] + if DateTime.parse(click['timestamp']) < DateTime.parse(ip_time) + ip_timestamp_per_period[click['ip']][period] = click['timestamp'] + end else - ip_amt_per_period[click["ip"]].merge!({period => click["amount"]}) - ip_timestamp_per_period[click["ip"]].merge!({period => click["timestamp"]}) + ip_amt_per_period[click['ip']].merge!({ period => click['amount'] }) + ip_timestamp_per_period[click['ip']].merge!({ period => click['timestamp'] }) end - - else - ip_amt_per_period[click["ip"]] = {} - ip_timestamp_per_period[click["ip"]] = {} - ip_amt_per_period[click["ip"]][period] = click["amount"] - ip_timestamp_per_period[click["ip"]][period] = click["timestamp"] + + else + ip_amt_per_period[click['ip']] = {} + ip_timestamp_per_period[click['ip']] = {} + ip_amt_per_period[click['ip']][period] = click['amount'] + ip_timestamp_per_period[click['ip']][period] = click['timestamp'] end end - return ip_amt_per_period, ip_timestamp_per_period + [ip_amt_per_period, ip_timestamp_per_period] end def self.form_output_array(ip_amt_per_period, ip_timestamp_per_period) res = [] - ip_amt_per_period.each do |k,v| - v.each {|period, value| res.push({"ip" => k, "timestamp" => ip_timestamp_per_period[k][period] , "amount" => value})} + ip_amt_per_period.each do |k, v| + v.each do |period, value| + res.push({ 'ip' => k, 'timestamp' => ip_timestamp_per_period[k][period], 'amount' => value }) + end end res end end - +# Class having main handler for finding most expensive click class ClickHandler - def self.main - all_clicks = [] - file_data = JsonFilehandler.read_file("clicks.json") + file_data = JsonFilehandler.read_file('clicks.json') click_data = Parser.to_json(file_data) valid_clicks = Parser.collect_valid_clicks(click_data) ip_amt_per_period, ip_timestamp_per_period = Parser.clicks_per_period(valid_clicks) res = Parser.form_output_array(ip_amt_per_period, ip_timestamp_per_period) - JsonFilehandler.write_file("result.json", res) + JsonFilehandler.write_file('result.json', res) end end -ClickHandler.main \ No newline at end of file +ClickHandler.main diff --git a/ruby/programs/click_handler/result.json b/ruby/programs/click_handler/result.json index 12797cd..d90d1ed 100644 --- a/ruby/programs/click_handler/result.json +++ b/ruby/programs/click_handler/result.json @@ -1,10 +1,10 @@ [ -{"ip":"11.11.11.11", "timestamp":"3/11/2020 02:13:11", "amount":7.25}, -{"ip":"11.11.11.11", "timestamp":"3/11/2020 06:45:01", "amount":12.0}, -{"ip":"11.11.11.11", "timestamp":"3/11/2020 07:02:54", "amount":4.5}, -{"ip":"44.44.44.44", "timestamp":"3/11/2020 02:13:54", "amount":8.75}, -{"ip":"44.44.44.44", "timestamp":"3/11/2020 06:32:42", "amount":5.0}, -{"ip":"44.44.44.44", "timestamp":"3/11/2020 13:02:55", "amount":8.0}, -{"ip":"33.33.33.33", "timestamp":"3/11/2020 07:02:54", "amount":15.75}, -{"ip":"66.66.66.66", "timestamp":"3/11/2020 07:02:54", "amount":14.25} +{ "ip":"11.11.11.11", "timestamp":"3/11/2020 02:13:11", "amount":7.25 } +,{ "ip":"11.11.11.11", "timestamp":"3/11/2020 06:45:01", "amount":12.0 } +,{ "ip":"11.11.11.11", "timestamp":"3/11/2020 07:02:54", "amount":4.5 } +,{ "ip":"44.44.44.44", "timestamp":"3/11/2020 02:13:54", "amount":8.75 } +,{ "ip":"44.44.44.44", "timestamp":"3/11/2020 06:32:42", "amount":5.0 } +,{ "ip":"44.44.44.44", "timestamp":"3/11/2020 13:02:55", "amount":8.0 } +,{ "ip":"33.33.33.33", "timestamp":"3/11/2020 07:02:54", "amount":15.75 } +,{ "ip":"66.66.66.66", "timestamp":"3/11/2020 07:02:54", "amount":14.25 } ] \ No newline at end of file