From 32c1f7a3230cc3f663e840ecfc702c5e34b325c7 Mon Sep 17 00:00:00 2001 From: Alesia Yakutsionak Date: Mon, 10 Dec 2018 17:32:44 +0300 Subject: [PATCH 01/13] Task 0: initial commit --- Alesia Yakutsionak/0/Gemfile | 4 + Alesia Yakutsionak/0/Gemfile.lock | 27 ++++++ Alesia Yakutsionak/0/README.md | 12 +++ Alesia Yakutsionak/0/run.rb | 133 ++++++++++++++++++++++++++++++ 4 files changed, 176 insertions(+) create mode 100644 Alesia Yakutsionak/0/Gemfile create mode 100644 Alesia Yakutsionak/0/Gemfile.lock create mode 100644 Alesia Yakutsionak/0/README.md create mode 100644 Alesia Yakutsionak/0/run.rb diff --git a/Alesia Yakutsionak/0/Gemfile b/Alesia Yakutsionak/0/Gemfile new file mode 100644 index 0000000..d0aaf12 --- /dev/null +++ b/Alesia Yakutsionak/0/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +gem 'roo', '~> 2.7.0' +gem 'roo-xls' diff --git a/Alesia Yakutsionak/0/Gemfile.lock b/Alesia Yakutsionak/0/Gemfile.lock new file mode 100644 index 0000000..1c77c14 --- /dev/null +++ b/Alesia Yakutsionak/0/Gemfile.lock @@ -0,0 +1,27 @@ +GEM + remote: https://rubygems.org/ + specs: + mini_portile2 (2.3.0) + nokogiri (1.8.5) + mini_portile2 (~> 2.3.0) + roo (2.7.1) + nokogiri (~> 1) + rubyzip (~> 1.1, < 2.0.0) + roo-xls (1.2.0) + nokogiri + roo (>= 2.0.0, < 3) + spreadsheet (> 0.9.0) + ruby-ole (1.2.12.1) + rubyzip (1.2.2) + spreadsheet (1.1.8) + ruby-ole (>= 1.0) + +PLATFORMS + ruby + +DEPENDENCIES + roo (~> 2.7.0) + roo-xls + +BUNDLED WITH + 1.17.1 diff --git a/Alesia Yakutsionak/0/README.md b/Alesia Yakutsionak/0/README.md new file mode 100644 index 0000000..66abe2c --- /dev/null +++ b/Alesia Yakutsionak/0/README.md @@ -0,0 +1,12 @@ +## Homework 0 + +Made by Alesia Yakutsionak + +## How to use: + +Put all Excel files with data to 'data' folder. + +``` +bundle +ruby run.rb +``` \ No newline at end of file diff --git a/Alesia Yakutsionak/0/run.rb b/Alesia Yakutsionak/0/run.rb new file mode 100644 index 0000000..afbbf93 --- /dev/null +++ b/Alesia Yakutsionak/0/run.rb @@ -0,0 +1,133 @@ +require 'rubygems' +require 'bundler/setup' + +Bundler.require(:default) + +class ProductPrice + attr_accessor :min_price, :max_price, :last_price, :min_price_date, :max_price_date + + def initialize(price, date) + @min_price = price + @min_price_date = date + @max_price = price + @max_price_date = date + @last_price = price if date == '2018/10' + end + + def update(price, date) + if min_price > price + @min_price = price + @min_price_date = date + end + if max_price < price + @max_price = price + @max_price_date = date + end + end +end + +class PriceCollector + MONTHS = %w(январь февраль март апрель май июнь июль август сентябрь октябрь ноябрь декабрь) + attr_accessor :products + + def initialize + @products = {} + end + + def start + collect_data + found = search_product(ask_product) + show_found_products(found) + # puts "Кол-во продуктов: #{products.size}" + end + + private + + def collect_data + Dir['data/*'].each do |f| + if f.end_with?('.xls') + collect_from_xls(f) + elsif f.end_with?('.xlsx') + collect_from_xlsx(f) + end + end + end + + def collect_from_xls(f) + xls = Roo::Excel.new(f) + sheet = xls.worksheets.first + date = parse_date(sheet.rows[2].compact.first) + puts "Can't parse date in #{f}" unless date + + sheet.rows.each_with_index do |row,i| + next if i < 8 || row[0].nil? + name = clean_name(row[0]) + price = row[6].to_f.round(2) + add_product(name, price, date) + end + end + + def collect_from_xlsx(f) + xlsx = Roo::Excelx.new(f) + date = parse_date(xlsx.sheet(0).row(3).compact.first) + puts "Can't parse date in #{f}" unless date + + xlsx.sheet(0).each_row_streaming(offset: 7) do |row| + name = clean_name(row[0].cell_value) + next if name.nil? + price = row[6].cell_value.to_f.round(2) + add_product(name, price, date) + end + end + + def add_product(name, price, date) + existing_product = products[name] + if existing_product + existing_product.update(price, date) + else + products[name] = ProductPrice.new(price, date) + end + end + + def parse_date(date_str) + month = MONTHS.find{|m| date_str.include?(m)} + return unless month + month_number = MONTHS.index(month) + 1 + year = $1 if date_str.match(/(\d{4})/) + return unless year + "#{year}/#{month_number.to_s.rjust(2, '0')}" + end + + def clean_name(name) + name.to_s.gsub(/[a-z<\/>]/, '').squeeze(' ') + end + + def ask_product + puts 'What price are you looking for?' + print '> ' + gets.chomp.downcase + end + + def search_product(user_product) + products.keys.select do |name| + name.downcase.include?(user_product) && products[name].last_price + end + end + + def show_found_products(found) + found.each do |p| + product_price = products[p] + puts "#{p} is #{product_price.last_price}BYN in Minsk these days." + puts "Lowest was on #{product_price.min_price_date} at price #{product_price.min_price}BYN" + puts "Maximum was on #{product_price.max_price_date} at price #{product_price.max_price}BYN" + end + end +end + + +PriceCollector.new.start + + + + + From e3efd24f91e56c9cf66d5a09bb494a802108907d Mon Sep 17 00:00:00 2001 From: Alesia Yakutsionak Date: Tue, 11 Dec 2018 00:07:48 +0300 Subject: [PATCH 02/13] Task 0: bugfixes, added level 3 --- Alesia Yakutsionak/0/run.rb | 42 ++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/Alesia Yakutsionak/0/run.rb b/Alesia Yakutsionak/0/run.rb index afbbf93..c02bedd 100644 --- a/Alesia Yakutsionak/0/run.rb +++ b/Alesia Yakutsionak/0/run.rb @@ -15,6 +15,7 @@ def initialize(price, date) end def update(price, date) + @last_price = price if date == '2018/10' if min_price > price @min_price = price @min_price_date = date @@ -38,19 +39,21 @@ def start collect_data found = search_product(ask_product) show_found_products(found) - # puts "Кол-во продуктов: #{products.size}" end private def collect_data + print 'Please wait' Dir['data/*'].each do |f| if f.end_with?('.xls') collect_from_xls(f) elsif f.end_with?('.xlsx') collect_from_xlsx(f) end + print '.' end + puts end def collect_from_xls(f) @@ -62,7 +65,7 @@ def collect_from_xls(f) sheet.rows.each_with_index do |row,i| next if i < 8 || row[0].nil? name = clean_name(row[0]) - price = row[6].to_f.round(2) + price = row[6] add_product(name, price, date) end end @@ -75,12 +78,15 @@ def collect_from_xlsx(f) xlsx.sheet(0).each_row_streaming(offset: 7) do |row| name = clean_name(row[0].cell_value) next if name.nil? - price = row[6].cell_value.to_f.round(2) + price = row[6].cell_value add_product(name, price, date) end end def add_product(name, price, date) + price = price.to_f + price /= 10_000 if date < '2017/01' + price = price.round(2) existing_product = products[name] if existing_product existing_product.update(price, date) @@ -110,24 +116,36 @@ def ask_product def search_product(user_product) products.keys.select do |name| - name.downcase.include?(user_product) && products[name].last_price + name.downcase.gsub(/[^(а-я)]/, ' ').split(/\s+/).include?(user_product) && products[name].last_price end + end def show_found_products(found) found.each do |p| product_price = products[p] - puts "#{p} is #{product_price.last_price}BYN in Minsk these days." - puts "Lowest was on #{product_price.min_price_date} at price #{product_price.min_price}BYN" - puts "Maximum was on #{product_price.max_price_date} at price #{product_price.max_price}BYN" + puts "#{p} is #{product_price.last_price} BYN in Minsk these days." + puts "Lowest was on #{product_price.min_price_date} at price #{product_price.min_price} BYN" + puts "Maximum was on #{product_price.max_price_date} at price #{product_price.max_price} BYN" + same_price_products = find_same_price(found, product_price.last_price) + unless same_price_products.empty? + puts "For similar price you also can afford #{same_price_products.join(' and ')}." + end + puts + end + end + + def find_same_price(found, price) + same = [] + products.each do |name, product_price| + if product_price.last_price && (product_price.last_price - price).abs < 0.03 + same << name unless found.include?(name) + break if same.size == 2 + end end + same end end PriceCollector.new.start - - - - - From be21f91c293e601b3c9b85df621ecd18fe16e2de Mon Sep 17 00:00:00 2001 From: Alesia Yakutsionak Date: Tue, 11 Dec 2018 00:44:28 +0300 Subject: [PATCH 03/13] Task 0: style fixes --- Alesia Yakutsionak/0/run.rb | 50 ++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/Alesia Yakutsionak/0/run.rb b/Alesia Yakutsionak/0/run.rb index c02bedd..6bfd231 100644 --- a/Alesia Yakutsionak/0/run.rb +++ b/Alesia Yakutsionak/0/run.rb @@ -28,7 +28,7 @@ def update(price, date) end class PriceCollector - MONTHS = %w(январь февраль март апрель май июнь июль август сентябрь октябрь ноябрь декабрь) + MONTHS = %w(январь февраль март апрель май июнь июль август сентябрь октябрь ноябрь декабрь).freeze attr_accessor :products def initialize @@ -38,7 +38,11 @@ def initialize def start collect_data found = search_product(ask_product) - show_found_products(found) + found.each do |product| + show_found_product(product) + show_same_price_products(found, product) + puts + end end private @@ -56,13 +60,13 @@ def collect_data puts end - def collect_from_xls(f) - xls = Roo::Excel.new(f) + def collect_from_xls(file) + xls = Roo::Excel.new(file) sheet = xls.worksheets.first date = parse_date(sheet.rows[2].compact.first) - puts "Can't parse date in #{f}" unless date + puts "Can't parse date in #{file}" unless date - sheet.rows.each_with_index do |row,i| + sheet.rows.each_with_index do |row, i| next if i < 8 || row[0].nil? name = clean_name(row[0]) price = row[6] @@ -70,10 +74,10 @@ def collect_from_xls(f) end end - def collect_from_xlsx(f) - xlsx = Roo::Excelx.new(f) + def collect_from_xlsx(file) + xlsx = Roo::Excelx.new(file) date = parse_date(xlsx.sheet(0).row(3).compact.first) - puts "Can't parse date in #{f}" unless date + puts "Can't parse date in #{file}" unless date xlsx.sheet(0).each_row_streaming(offset: 7) do |row| name = clean_name(row[0].cell_value) @@ -99,7 +103,8 @@ def parse_date(date_str) month = MONTHS.find{|m| date_str.include?(m)} return unless month month_number = MONTHS.index(month) + 1 - year = $1 if date_str.match(/(\d{4})/) + match_data = date_str.match(/(\d{4})/) + year = match_data[1] if match_data return unless year "#{year}/#{month_number.to_s.rjust(2, '0')}" end @@ -116,22 +121,22 @@ def ask_product def search_product(user_product) products.keys.select do |name| - name.downcase.gsub(/[^(а-я)]/, ' ').split(/\s+/).include?(user_product) && products[name].last_price + name.downcase.gsub(%r/[^(а-я)]/, ' ').split(/\s+/).include?(user_product) && products[name].last_price end + end + def show_found_product(product_name) + product_price = products[product_name] + puts "#{product_name} is #{product_price.last_price} BYN in Minsk these days." + puts "Lowest was on #{product_price.min_price_date} at price #{product_price.min_price} BYN" + puts "Maximum was on #{product_price.max_price_date} at price #{product_price.max_price} BYN" end - def show_found_products(found) - found.each do |p| - product_price = products[p] - puts "#{p} is #{product_price.last_price} BYN in Minsk these days." - puts "Lowest was on #{product_price.min_price_date} at price #{product_price.min_price} BYN" - puts "Maximum was on #{product_price.max_price_date} at price #{product_price.max_price} BYN" - same_price_products = find_same_price(found, product_price.last_price) - unless same_price_products.empty? - puts "For similar price you also can afford #{same_price_products.join(' and ')}." - end - puts + def show_same_price_products(found, product_name) + product_price = products[product_name] + same_price_products = find_same_price(found, product_price.last_price) + unless same_price_products.empty? + puts "For similar price you also can afford #{same_price_products.join(' and ')}." end end @@ -147,5 +152,4 @@ def find_same_price(found, price) end end - PriceCollector.new.start From 54f1f0dd6d01bb05b32ad4d91d5ac63cc3dcbcb9 Mon Sep 17 00:00:00 2001 From: Alesia Yakutsionak Date: Wed, 12 Dec 2018 23:27:42 +0300 Subject: [PATCH 04/13] Task 0: style fixes (2) --- Alesia Yakutsionak/0/run.rb | 109 ++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 47 deletions(-) diff --git a/Alesia Yakutsionak/0/run.rb b/Alesia Yakutsionak/0/run.rb index 6bfd231..73b4ff1 100644 --- a/Alesia Yakutsionak/0/run.rb +++ b/Alesia Yakutsionak/0/run.rb @@ -4,6 +4,8 @@ Bundler.require(:default) class ProductPrice + LAST_PRICE_DATE = '2018/10'.freeze + attr_accessor :min_price, :max_price, :last_price, :min_price_date, :max_price_date def initialize(price, date) @@ -11,11 +13,11 @@ def initialize(price, date) @min_price_date = date @max_price = price @max_price_date = date - @last_price = price if date == '2018/10' + @last_price = price if date == LAST_PRICE_DATE end def update(price, date) - @last_price = price if date == '2018/10' + @last_price = price if date == LAST_PRICE_DATE if min_price > price @min_price = price @min_price_date = date @@ -29,64 +31,91 @@ def update(price, date) class PriceCollector MONTHS = %w(январь февраль март апрель май июнь июль август сентябрь октябрь ноябрь декабрь).freeze - attr_accessor :products - - def initialize - @products = {} - end - - def start - collect_data - found = search_product(ask_product) - found.each do |product| - show_found_product(product) - show_same_price_products(found, product) - puts - end - end + SHEET_NUM = 0 + DATE_ROW = 3 + FIRST_DATA_ROW = 8 + PRODUCT_NAME_COL = 0 + MINSK_PRICE_COL = 6 - private - def collect_data + def self.collect_data(&block) print 'Please wait' Dir['data/*'].each do |f| if f.end_with?('.xls') - collect_from_xls(f) + collect_from_xls(f, block) elsif f.end_with?('.xlsx') - collect_from_xlsx(f) + collect_from_xlsx(f, block) end print '.' end puts end - def collect_from_xls(file) + private + + def self.collect_from_xls(file, block) xls = Roo::Excel.new(file) sheet = xls.worksheets.first - date = parse_date(sheet.rows[2].compact.first) + date = parse_date(sheet.rows[DATE_ROW - 1].compact.first) puts "Can't parse date in #{file}" unless date sheet.rows.each_with_index do |row, i| - next if i < 8 || row[0].nil? - name = clean_name(row[0]) - price = row[6] - add_product(name, price, date) + next if i < FIRST_DATA_ROW || row[PRODUCT_NAME_COL].nil? + name = clean_name(row[PRODUCT_NAME_COL]) + price = row[MINSK_PRICE_COL] + block.call(name, price, date) end end - def collect_from_xlsx(file) + def self.collect_from_xlsx(file, block) xlsx = Roo::Excelx.new(file) - date = parse_date(xlsx.sheet(0).row(3).compact.first) + date = parse_date(xlsx.sheet(SHEET_NUM).row(DATE_ROW).compact.first) puts "Can't parse date in #{file}" unless date - xlsx.sheet(0).each_row_streaming(offset: 7) do |row| - name = clean_name(row[0].cell_value) + xlsx.sheet(SHEET_NUM).each_row_streaming(offset: FIRST_DATA_ROW - 1) do |row| + name = clean_name(row[PRODUCT_NAME_COL].cell_value) next if name.nil? - price = row[6].cell_value + price = row[MINSK_PRICE_COL].cell_value + block.call(name, price, date) + end + end + + def self.parse_date(date_str) + month_index = MONTHS.index {|m| date_str.include?(m) } + return unless month_index + month_number = month_index + 1 + match_data = date_str.match(/(\d{4})/) + year = match_data[1] if match_data + return unless year + "#{year}/#{month_number.to_s.rjust(2, '0')}" + end + + def self.clean_name(name) + name.to_s.gsub(/[a-z<\/>]/, '').squeeze(' ') + end +end + +class Products + attr_accessor :products + + def initialize + @products = {} + end + + def start + PriceCollector.collect_data do |name, price, date| add_product(name, price, date) end + found = search_product(ask_product) + found.each do |product| + show_found_product(product) + show_same_price_products(found, product) + puts + end end + private + def add_product(name, price, date) price = price.to_f price /= 10_000 if date < '2017/01' @@ -99,20 +128,6 @@ def add_product(name, price, date) end end - def parse_date(date_str) - month = MONTHS.find{|m| date_str.include?(m)} - return unless month - month_number = MONTHS.index(month) + 1 - match_data = date_str.match(/(\d{4})/) - year = match_data[1] if match_data - return unless year - "#{year}/#{month_number.to_s.rjust(2, '0')}" - end - - def clean_name(name) - name.to_s.gsub(/[a-z<\/>]/, '').squeeze(' ') - end - def ask_product puts 'What price are you looking for?' print '> ' @@ -152,4 +167,4 @@ def find_same_price(found, price) end end -PriceCollector.new.start +Products.new.start From 2b9274668d452bfa753c9bdfaa24726e11594278 Mon Sep 17 00:00:00 2001 From: Alesia Yakutsionak Date: Wed, 12 Dec 2018 23:57:30 +0300 Subject: [PATCH 05/13] Task 0: fix line endings, style fixes (3) --- Alesia Yakutsionak/0/run.rb | 343 ++++++++++++++++++------------------ 1 file changed, 173 insertions(+), 170 deletions(-) diff --git a/Alesia Yakutsionak/0/run.rb b/Alesia Yakutsionak/0/run.rb index 73b4ff1..3e81b45 100644 --- a/Alesia Yakutsionak/0/run.rb +++ b/Alesia Yakutsionak/0/run.rb @@ -1,170 +1,173 @@ -require 'rubygems' -require 'bundler/setup' - -Bundler.require(:default) - -class ProductPrice - LAST_PRICE_DATE = '2018/10'.freeze - - attr_accessor :min_price, :max_price, :last_price, :min_price_date, :max_price_date - - def initialize(price, date) - @min_price = price - @min_price_date = date - @max_price = price - @max_price_date = date - @last_price = price if date == LAST_PRICE_DATE - end - - def update(price, date) - @last_price = price if date == LAST_PRICE_DATE - if min_price > price - @min_price = price - @min_price_date = date - end - if max_price < price - @max_price = price - @max_price_date = date - end - end -end - -class PriceCollector - MONTHS = %w(январь февраль март апрель май июнь июль август сентябрь октябрь ноябрь декабрь).freeze - SHEET_NUM = 0 - DATE_ROW = 3 - FIRST_DATA_ROW = 8 - PRODUCT_NAME_COL = 0 - MINSK_PRICE_COL = 6 - - - def self.collect_data(&block) - print 'Please wait' - Dir['data/*'].each do |f| - if f.end_with?('.xls') - collect_from_xls(f, block) - elsif f.end_with?('.xlsx') - collect_from_xlsx(f, block) - end - print '.' - end - puts - end - - private - - def self.collect_from_xls(file, block) - xls = Roo::Excel.new(file) - sheet = xls.worksheets.first - date = parse_date(sheet.rows[DATE_ROW - 1].compact.first) - puts "Can't parse date in #{file}" unless date - - sheet.rows.each_with_index do |row, i| - next if i < FIRST_DATA_ROW || row[PRODUCT_NAME_COL].nil? - name = clean_name(row[PRODUCT_NAME_COL]) - price = row[MINSK_PRICE_COL] - block.call(name, price, date) - end - end - - def self.collect_from_xlsx(file, block) - xlsx = Roo::Excelx.new(file) - date = parse_date(xlsx.sheet(SHEET_NUM).row(DATE_ROW).compact.first) - puts "Can't parse date in #{file}" unless date - - xlsx.sheet(SHEET_NUM).each_row_streaming(offset: FIRST_DATA_ROW - 1) do |row| - name = clean_name(row[PRODUCT_NAME_COL].cell_value) - next if name.nil? - price = row[MINSK_PRICE_COL].cell_value - block.call(name, price, date) - end - end - - def self.parse_date(date_str) - month_index = MONTHS.index {|m| date_str.include?(m) } - return unless month_index - month_number = month_index + 1 - match_data = date_str.match(/(\d{4})/) - year = match_data[1] if match_data - return unless year - "#{year}/#{month_number.to_s.rjust(2, '0')}" - end - - def self.clean_name(name) - name.to_s.gsub(/[a-z<\/>]/, '').squeeze(' ') - end -end - -class Products - attr_accessor :products - - def initialize - @products = {} - end - - def start - PriceCollector.collect_data do |name, price, date| - add_product(name, price, date) - end - found = search_product(ask_product) - found.each do |product| - show_found_product(product) - show_same_price_products(found, product) - puts - end - end - - private - - def add_product(name, price, date) - price = price.to_f - price /= 10_000 if date < '2017/01' - price = price.round(2) - existing_product = products[name] - if existing_product - existing_product.update(price, date) - else - products[name] = ProductPrice.new(price, date) - end - end - - def ask_product - puts 'What price are you looking for?' - print '> ' - gets.chomp.downcase - end - - def search_product(user_product) - products.keys.select do |name| - name.downcase.gsub(%r/[^(а-я)]/, ' ').split(/\s+/).include?(user_product) && products[name].last_price - end - end - - def show_found_product(product_name) - product_price = products[product_name] - puts "#{product_name} is #{product_price.last_price} BYN in Minsk these days." - puts "Lowest was on #{product_price.min_price_date} at price #{product_price.min_price} BYN" - puts "Maximum was on #{product_price.max_price_date} at price #{product_price.max_price} BYN" - end - - def show_same_price_products(found, product_name) - product_price = products[product_name] - same_price_products = find_same_price(found, product_price.last_price) - unless same_price_products.empty? - puts "For similar price you also can afford #{same_price_products.join(' and ')}." - end - end - - def find_same_price(found, price) - same = [] - products.each do |name, product_price| - if product_price.last_price && (product_price.last_price - price).abs < 0.03 - same << name unless found.include?(name) - break if same.size == 2 - end - end - same - end -end - -Products.new.start +require 'rubygems' +require 'bundler/setup' + +Bundler.require(:default) + +class ProductPrice + LAST_PRICE_DATE = '2018/10'.freeze + + attr_accessor :min_price, :max_price, :last_price, :min_price_date, :max_price_date + + def initialize(price, date) + @min_price = price + @min_price_date = date + @max_price = price + @max_price_date = date + @last_price = price if date == LAST_PRICE_DATE + end + + def update(price, date) + @last_price = price if date == LAST_PRICE_DATE + if min_price > price + @min_price = price + @min_price_date = date + end + if max_price < price + @max_price = price + @max_price_date = date + end + end +end + +class PriceCollector + MONTHS = %w(январь февраль март апрель май июнь июль август сентябрь октябрь ноябрь декабрь).freeze + SHEET_NUM = 0 + DATE_ROW = 3 + FIRST_DATA_ROW = 8 + PRODUCT_NAME_COL = 0 + MINSK_PRICE_COL = 6 + + def self.collect_data(&block) + print 'Please wait' + Dir['data/*'].each do |f| + if f.end_with?('.xls') + collect_from_xls(f, block) + elsif f.end_with?('.xlsx') + collect_from_xlsx(f, block) + end + print '.' + end + puts + end + + def self.collect_from_xls(file, block) + xls = Roo::Excel.new(file) + sheet = xls.worksheets.first + date = parse_date(sheet.rows[DATE_ROW - 1].compact.first) + puts "Can't parse date in #{file}" unless date + + sheet.rows.each_with_index do |row, i| + next if i < FIRST_DATA_ROW || row[PRODUCT_NAME_COL].nil? + name = clean_name(row[PRODUCT_NAME_COL]) + price = row[MINSK_PRICE_COL] + block.call(name, price, date) + end + end + + def self.collect_from_xlsx(file, block) + xlsx = Roo::Excelx.new(file) + date = parse_date(xlsx.sheet(SHEET_NUM).row(DATE_ROW).compact.first) + puts "Can't parse date in #{file}" unless date + + xlsx.sheet(SHEET_NUM).each_row_streaming(offset: FIRST_DATA_ROW - 1) do |row| + name = clean_name(row[PRODUCT_NAME_COL].cell_value) + next if name.nil? + price = row[MINSK_PRICE_COL].cell_value + block.call(name, price, date) + end + end + + def self.parse_date(date_str) + month_index = MONTHS.index { |m| date_str.include?(m) } + return unless month_index + month_number = month_index + 1 + match_data = date_str.match(/(\d{4})/) + year = match_data[1] if match_data + return unless year + "#{year}/#{month_number.to_s.rjust(2, '0')}" + end + + def self.clean_name(name) + name.to_s.gsub(%r/[a-z<\/>]/, '').squeeze(' ') + end +end + +class Products + DENOMINATION_DATE = '2017/01' + DENOMINATION_VALUE = 10_000 + + attr_accessor :products + + def initialize + @products = {} + end + + def start + PriceCollector.collect_data do |name, price, date| + add_product(name, price, date) + end + found = search_product(ask_product) + found.each do |product| + show_found_product(product) + show_same_price_products(found, product) + puts + end + end + + private + + def add_product(name, price, date) + price = convert_price(price, date) + existing_product = products[name] + if existing_product + existing_product.update(price, date) + else + products[name] = ProductPrice.new(price, date) + end + end + + def convert_price(price, date) + price = price.to_f + price /= DENOMINATION_VALUE if date < DENOMINATION_DATE + price.round(2) + end + + def ask_product + puts 'What price are you looking for?' + print '> ' + gets.chomp.downcase + end + + def search_product(user_product) + products.keys.select do |name| + name.downcase.gsub(%r{[^(а-я)]}, ' ').split(%r{\s+}).include?(user_product) && products[name].last_price + end + end + + def show_found_product(product_name) + product_price = products[product_name] + puts "#{product_name} is #{product_price.last_price} BYN in Minsk these days." + puts "Lowest was on #{product_price.min_price_date} at price #{product_price.min_price} BYN" + puts "Maximum was on #{product_price.max_price_date} at price #{product_price.max_price} BYN" + end + + def show_same_price_products(found, product_name) + product_price = products[product_name] + same_price_products = find_same_price(found, product_price.last_price) + return if same_price_products.empty? + puts "For similar price you also can afford #{same_price_products.join(' and ')}." + end + + def find_same_price(found, price) + same = [] + products.each do |name, product_price| + if product_price.last_price && (product_price.last_price - price).abs < 0.03 + same << name unless found.include?(name) + break if same.size == 2 + end + end + same + end +end + +Products.new.start From abcd1d2e7a175fc1f1c453415e30b792071774a7 Mon Sep 17 00:00:00 2001 From: Alesia Yakutsionak Date: Thu, 13 Dec 2018 00:25:46 +0300 Subject: [PATCH 06/13] Task 0: style fixes (4) --- Alesia Yakutsionak/0/run.rb | 41 +++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/Alesia Yakutsionak/0/run.rb b/Alesia Yakutsionak/0/run.rb index 3e81b45..a05c022 100644 --- a/Alesia Yakutsionak/0/run.rb +++ b/Alesia Yakutsionak/0/run.rb @@ -22,10 +22,9 @@ def update(price, date) @min_price = price @min_price_date = date end - if max_price < price - @max_price = price - @max_price_date = date - end + return unless max_price < price + @max_price = price + @max_price_date = date end end @@ -51,8 +50,7 @@ def self.collect_data(&block) end def self.collect_from_xls(file, block) - xls = Roo::Excel.new(file) - sheet = xls.worksheets.first + sheet = Roo::Excel.new(file).worksheets.first date = parse_date(sheet.rows[DATE_ROW - 1].compact.first) puts "Can't parse date in #{file}" unless date @@ -65,11 +63,13 @@ def self.collect_from_xls(file, block) end def self.collect_from_xlsx(file, block) - xlsx = Roo::Excelx.new(file) - date = parse_date(xlsx.sheet(SHEET_NUM).row(DATE_ROW).compact.first) - puts "Can't parse date in #{file}" unless date + sheet = Roo::Excelx.new(file).sheet(SHEET_NUM) + date = parse_date(sheet.row(DATE_ROW).compact.first) + unless date + puts "Can't parse date in #{file}" + end - xlsx.sheet(SHEET_NUM).each_row_streaming(offset: FIRST_DATA_ROW - 1) do |row| + sheet.each_row_streaming(offset: FIRST_DATA_ROW - 1) do |row| name = clean_name(row[PRODUCT_NAME_COL].cell_value) next if name.nil? price = row[MINSK_PRICE_COL].cell_value @@ -93,7 +93,7 @@ def self.clean_name(name) end class Products - DENOMINATION_DATE = '2017/01' + DENOMINATION_DATE = '2017/01'.freeze DENOMINATION_VALUE = 10_000 attr_accessor :products @@ -106,11 +106,16 @@ def start PriceCollector.collect_data do |name, price, date| add_product(name, price, date) end - found = search_product(ask_product) - found.each do |product| - show_found_product(product) - show_same_price_products(found, product) - puts + user_product = ask_product + found = search_product(user_product) + if found.empty? + puts "'#{user_product}' can not be found in database." + else + found.each do |product| + show_found_product(product) + show_same_price_products(found, product) + puts + end end end @@ -135,12 +140,12 @@ def convert_price(price, date) def ask_product puts 'What price are you looking for?' print '> ' - gets.chomp.downcase + gets.chomp end def search_product(user_product) products.keys.select do |name| - name.downcase.gsub(%r{[^(а-я)]}, ' ').split(%r{\s+}).include?(user_product) && products[name].last_price + name.downcase.gsub(/[^(а-я)]/, ' ').split(/\s+/).include?(user_product.downcase) && products[name].last_price end end From afef0e4b1f096be00abf940ba5ee8c8236c1bad1 Mon Sep 17 00:00:00 2001 From: Alesia Yakutsionak Date: Thu, 13 Dec 2018 00:38:19 +0300 Subject: [PATCH 07/13] Task 0: style fixes (5) --- Alesia Yakutsionak/0/run.rb | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Alesia Yakutsionak/0/run.rb b/Alesia Yakutsionak/0/run.rb index a05c022..3fd6a74 100644 --- a/Alesia Yakutsionak/0/run.rb +++ b/Alesia Yakutsionak/0/run.rb @@ -57,23 +57,19 @@ def self.collect_from_xls(file, block) sheet.rows.each_with_index do |row, i| next if i < FIRST_DATA_ROW || row[PRODUCT_NAME_COL].nil? name = clean_name(row[PRODUCT_NAME_COL]) - price = row[MINSK_PRICE_COL] - block.call(name, price, date) + block.call(name, row[MINSK_PRICE_COL], date) end end def self.collect_from_xlsx(file, block) sheet = Roo::Excelx.new(file).sheet(SHEET_NUM) date = parse_date(sheet.row(DATE_ROW).compact.first) - unless date - puts "Can't parse date in #{file}" - end + puts "Can't parse date in #{file}" unless date sheet.each_row_streaming(offset: FIRST_DATA_ROW - 1) do |row| name = clean_name(row[PRODUCT_NAME_COL].cell_value) next if name.nil? - price = row[MINSK_PRICE_COL].cell_value - block.call(name, price, date) + block.call(name, row[MINSK_PRICE_COL].cell_value, date) end end @@ -81,8 +77,7 @@ def self.parse_date(date_str) month_index = MONTHS.index { |m| date_str.include?(m) } return unless month_index month_number = month_index + 1 - match_data = date_str.match(/(\d{4})/) - year = match_data[1] if match_data + year = date_str[/\d{4}/] return unless year "#{year}/#{month_number.to_s.rjust(2, '0')}" end From 6931a83624e8b665d269109fd5ee9d03bce26d3b Mon Sep 17 00:00:00 2001 From: Alesia Yakutsionak Date: Thu, 13 Dec 2018 00:48:46 +0300 Subject: [PATCH 08/13] Task 0: style fixes (6) --- Alesia Yakutsionak/0/run.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Alesia Yakutsionak/0/run.rb b/Alesia Yakutsionak/0/run.rb index 3fd6a74..f2bdcc1 100644 --- a/Alesia Yakutsionak/0/run.rb +++ b/Alesia Yakutsionak/0/run.rb @@ -57,7 +57,8 @@ def self.collect_from_xls(file, block) sheet.rows.each_with_index do |row, i| next if i < FIRST_DATA_ROW || row[PRODUCT_NAME_COL].nil? name = clean_name(row[PRODUCT_NAME_COL]) - block.call(name, row[MINSK_PRICE_COL], date) + price = row[MINSK_PRICE_COL] + block.call(name, price, date) end end @@ -69,7 +70,8 @@ def self.collect_from_xlsx(file, block) sheet.each_row_streaming(offset: FIRST_DATA_ROW - 1) do |row| name = clean_name(row[PRODUCT_NAME_COL].cell_value) next if name.nil? - block.call(name, row[MINSK_PRICE_COL].cell_value, date) + price = row[MINSK_PRICE_COL].cell_value + block.call(name, price, date) end end From 925b76a863e08b9fcd1075761ce123ebb244fb8a Mon Sep 17 00:00:00 2001 From: Alesia Yakutsionak Date: Fri, 8 Feb 2019 19:19:18 +0300 Subject: [PATCH 09/13] Task 1: Telegram Bot --- Alesia Yakutsionak/1/Gemfile | 5 +++ Alesia Yakutsionak/1/README.md | 8 +++++ Alesia Yakutsionak/1/bot.rb | 40 +++++++++++++++++++++++ Alesia Yakutsionak/1/pun_parser.rb | 52 ++++++++++++++++++++++++++++++ Alesia Yakutsionak/1/run_bot.rb | 3 ++ Alesia Yakutsionak/1/run_parser.rb | 3 ++ 6 files changed, 111 insertions(+) create mode 100755 Alesia Yakutsionak/1/Gemfile create mode 100755 Alesia Yakutsionak/1/README.md create mode 100644 Alesia Yakutsionak/1/bot.rb create mode 100644 Alesia Yakutsionak/1/pun_parser.rb create mode 100644 Alesia Yakutsionak/1/run_bot.rb create mode 100644 Alesia Yakutsionak/1/run_parser.rb diff --git a/Alesia Yakutsionak/1/Gemfile b/Alesia Yakutsionak/1/Gemfile new file mode 100755 index 0000000..252e28e --- /dev/null +++ b/Alesia Yakutsionak/1/Gemfile @@ -0,0 +1,5 @@ +source 'https://rubygems.org' + +gem 'nokogiri' +gem 'redis' +gem 'telegram-bot-ruby' diff --git a/Alesia Yakutsionak/1/README.md b/Alesia Yakutsionak/1/README.md new file mode 100755 index 0000000..289993f --- /dev/null +++ b/Alesia Yakutsionak/1/README.md @@ -0,0 +1,8 @@ +To test the program: + +-run "bundle install" +-run "redis-server" to start redis +-run "ruby run_parser.rb" +-put Your_Telegram_Bot_Token in BOT_TOKEN +-run "run_bot.rb" +-write command (/wordplay) to bot in Telegram diff --git a/Alesia Yakutsionak/1/bot.rb b/Alesia Yakutsionak/1/bot.rb new file mode 100644 index 0000000..61cafb3 --- /dev/null +++ b/Alesia Yakutsionak/1/bot.rb @@ -0,0 +1,40 @@ +require 'telegram/bot' +require 'redis' + +class Bot + INVALID_COMMAND_TEXT = "I didn't understand you.".freeze + BOT_TOKEN = 'Your_Telegram_Bot_Token'.freeze + + def initialize + @redis = Redis.new + @puns_count = @redis.keys('pun_*').count + end + + def run + Telegram::Bot::Client.run(BOT_TOKEN) do |bot| + bot.listen do |message| + reply(bot, message) + end + end + end + + private + + def reply(bot, message) + case message.text + when /^hey|hi|hello$/i + bot.api.sendMessage(chat_id: message.chat.id, text: "Hey, #{message.from.first_name}") + when '/wordplay' then + random_pun = select_random_pun + wordplay_text = "Random wordplay [#{random_pun[:number]}]:\n#{random_pun[:text]}" + bot.api.sendMessage(chat_id: message.chat.id, text: wordplay_text) + else + bot.api.sendMessage(chat_id: message.chat.id, text: INVALID_COMMAND_TEXT) + end + end + + def select_random_pun + i = rand(0..@puns_count-1) + { number: i, text: @redis.get("pun_#{i}") } + end +end diff --git a/Alesia Yakutsionak/1/pun_parser.rb b/Alesia Yakutsionak/1/pun_parser.rb new file mode 100644 index 0000000..5114faa --- /dev/null +++ b/Alesia Yakutsionak/1/pun_parser.rb @@ -0,0 +1,52 @@ +require 'rubygems' +require 'nokogiri' +require 'open-uri' +require 'redis' + +class PunParser + BASE_URL = 'https://onelinefun.com'.freeze + + def run + while true do + puts "Parsing #{@link}" + page = parse_page + @link = find_next_page_link(page) + break unless @link + end + save_puns_to_redis + end + + def initialize + @link = '/puns/' + @redis = Redis.new + @puns = [] + end + + private + + def parse_page + url = BASE_URL + @link + page = Nokogiri::HTML(open(url)) + page.css('article div p').each do |p| + pun = p.text + @puns << pun + end + page + end + + def find_next_page_link(page) + a_next = page.css('article div.p > a') + @link = if a_next && a_next.text =~ /^next/i + a_next.attr('href').value + else + nil + end + @link + end + + def save_puns_to_redis + @puns.each_with_index do |pun, i| + @redis.set("pun_#{i}", pun) + end + end +end diff --git a/Alesia Yakutsionak/1/run_bot.rb b/Alesia Yakutsionak/1/run_bot.rb new file mode 100644 index 0000000..6a93afd --- /dev/null +++ b/Alesia Yakutsionak/1/run_bot.rb @@ -0,0 +1,3 @@ +require_relative 'bot.rb' + +Bot.new.run diff --git a/Alesia Yakutsionak/1/run_parser.rb b/Alesia Yakutsionak/1/run_parser.rb new file mode 100644 index 0000000..5c131ed --- /dev/null +++ b/Alesia Yakutsionak/1/run_parser.rb @@ -0,0 +1,3 @@ +require_relative 'pun_parser.rb' + +PunParser.new.run From 2deecbe694c08c31dbeb4e961df969bd08a5e929 Mon Sep 17 00:00:00 2001 From: Alesia Yakutsionak Date: Sun, 10 Feb 2019 17:52:43 +0300 Subject: [PATCH 10/13] Task 1: fixing styles --- Alesia Yakutsionak/1/bot.rb | 2 +- Alesia Yakutsionak/1/pun_parser.rb | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Alesia Yakutsionak/1/bot.rb b/Alesia Yakutsionak/1/bot.rb index 61cafb3..4bfd5b8 100644 --- a/Alesia Yakutsionak/1/bot.rb +++ b/Alesia Yakutsionak/1/bot.rb @@ -34,7 +34,7 @@ def reply(bot, message) end def select_random_pun - i = rand(0..@puns_count-1) + i = rand(0..@puns_count - 1) { number: i, text: @redis.get("pun_#{i}") } end end diff --git a/Alesia Yakutsionak/1/pun_parser.rb b/Alesia Yakutsionak/1/pun_parser.rb index 5114faa..54beb2e 100644 --- a/Alesia Yakutsionak/1/pun_parser.rb +++ b/Alesia Yakutsionak/1/pun_parser.rb @@ -7,7 +7,7 @@ class PunParser BASE_URL = 'https://onelinefun.com'.freeze def run - while true do + loop do puts "Parsing #{@link}" page = parse_page @link = find_next_page_link(page) @@ -26,21 +26,21 @@ def initialize def parse_page url = BASE_URL + @link - page = Nokogiri::HTML(open(url)) + page = Nokogiri::HTML(URI.open(url)) page.css('article div p').each do |p| pun = p.text @puns << pun - end + end page end def find_next_page_link(page) a_next = page.css('article div.p > a') @link = if a_next && a_next.text =~ /^next/i - a_next.attr('href').value - else - nil - end + a_next.attr('href').value + else + nil + end @link end From 3ce8739aa05f784d318f1b382c64e8deef189b1d Mon Sep 17 00:00:00 2001 From: Alesia Yakutsionak Date: Sun, 10 Feb 2019 18:52:10 +0300 Subject: [PATCH 11/13] Task 1: read token from file --- Alesia Yakutsionak/1/README.md | 12 ++++++------ Alesia Yakutsionak/1/bot.rb | 9 +++++++-- Alesia Yakutsionak/1/secrets.yml.sample | 1 + 3 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 Alesia Yakutsionak/1/secrets.yml.sample diff --git a/Alesia Yakutsionak/1/README.md b/Alesia Yakutsionak/1/README.md index 289993f..b06262e 100755 --- a/Alesia Yakutsionak/1/README.md +++ b/Alesia Yakutsionak/1/README.md @@ -1,8 +1,8 @@ To test the program: --run "bundle install" --run "redis-server" to start redis --run "ruby run_parser.rb" --put Your_Telegram_Bot_Token in BOT_TOKEN --run "run_bot.rb" --write command (/wordplay) to bot in Telegram +- run `bundle install` +- run `redis-server` to start redis +- run `ruby run_parser.rb` +- create `secrets.yml` file with your Telegram Bot token (see `secrets.yml.samle`) +- run `run_bot.rb` +- write command (/wordplay) to bot in Telegram diff --git a/Alesia Yakutsionak/1/bot.rb b/Alesia Yakutsionak/1/bot.rb index 4bfd5b8..49fd59b 100644 --- a/Alesia Yakutsionak/1/bot.rb +++ b/Alesia Yakutsionak/1/bot.rb @@ -1,9 +1,10 @@ require 'telegram/bot' require 'redis' +require 'yaml' class Bot INVALID_COMMAND_TEXT = "I didn't understand you.".freeze - BOT_TOKEN = 'Your_Telegram_Bot_Token'.freeze + SECRETS_PATH = 'secrets.yml'.freeze def initialize @redis = Redis.new @@ -11,7 +12,7 @@ def initialize end def run - Telegram::Bot::Client.run(BOT_TOKEN) do |bot| + Telegram::Bot::Client.run(load_token) do |bot| bot.listen do |message| reply(bot, message) end @@ -37,4 +38,8 @@ def select_random_pun i = rand(0..@puns_count - 1) { number: i, text: @redis.get("pun_#{i}") } end + + def load_token + YAML.load_file(SECRETS_PATH)["TELEGRAM_TOKEN"] + end end diff --git a/Alesia Yakutsionak/1/secrets.yml.sample b/Alesia Yakutsionak/1/secrets.yml.sample new file mode 100644 index 0000000..35e1719 --- /dev/null +++ b/Alesia Yakutsionak/1/secrets.yml.sample @@ -0,0 +1 @@ +TELEGRAM_TOKEN: "your telegram token" From c3dd3cf45c830e19a2f9efcb71e99657ab6fd052 Mon Sep 17 00:00:00 2001 From: Alesia Yakutsionak Date: Sun, 10 Feb 2019 19:08:32 +0300 Subject: [PATCH 12/13] Task 1: Fixing rubocop issues --- Alesia Yakutsionak/1/bot.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Alesia Yakutsionak/1/bot.rb b/Alesia Yakutsionak/1/bot.rb index 49fd59b..c7ecbaf 100644 --- a/Alesia Yakutsionak/1/bot.rb +++ b/Alesia Yakutsionak/1/bot.rb @@ -26,20 +26,23 @@ def reply(bot, message) when /^hey|hi|hello$/i bot.api.sendMessage(chat_id: message.chat.id, text: "Hey, #{message.from.first_name}") when '/wordplay' then - random_pun = select_random_pun - wordplay_text = "Random wordplay [#{random_pun[:number]}]:\n#{random_pun[:text]}" bot.api.sendMessage(chat_id: message.chat.id, text: wordplay_text) else bot.api.sendMessage(chat_id: message.chat.id, text: INVALID_COMMAND_TEXT) end end + def wordplay_text + random_pun = select_random_pun + "Random wordplay [#{random_pun[:number]}]:\n#{random_pun[:text]}" + end + def select_random_pun i = rand(0..@puns_count - 1) { number: i, text: @redis.get("pun_#{i}") } end def load_token - YAML.load_file(SECRETS_PATH)["TELEGRAM_TOKEN"] + YAML.load_file(SECRETS_PATH)['TELEGRAM_TOKEN'] end end From 5b135bfeab2aa83b4f758541966cfdd7ea59784c Mon Sep 17 00:00:00 2001 From: Alesia Yakutsionak Date: Sun, 17 Feb 2019 21:27:59 +0300 Subject: [PATCH 13/13] Task 1: Gemfile.lock --- Alesia Yakutsionak/1/Gemfile.lock | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Alesia Yakutsionak/1/Gemfile.lock diff --git a/Alesia Yakutsionak/1/Gemfile.lock b/Alesia Yakutsionak/1/Gemfile.lock new file mode 100644 index 0000000..4874a30 --- /dev/null +++ b/Alesia Yakutsionak/1/Gemfile.lock @@ -0,0 +1,42 @@ +GEM + remote: https://rubygems.org/ + specs: + axiom-types (0.1.1) + descendants_tracker (~> 0.0.4) + ice_nine (~> 0.11.0) + thread_safe (~> 0.3, >= 0.3.1) + coercible (1.0.0) + descendants_tracker (~> 0.0.1) + descendants_tracker (0.0.4) + thread_safe (~> 0.3, >= 0.3.1) + equalizer (0.0.11) + faraday (0.15.4) + multipart-post (>= 1.2, < 3) + ice_nine (0.11.2) + inflecto (0.0.2) + mini_portile2 (2.4.0) + multipart-post (2.0.0) + nokogiri (1.10.1) + mini_portile2 (~> 2.4.0) + redis (4.1.0) + telegram-bot-ruby (0.8.6.1) + faraday + inflecto + virtus + thread_safe (0.3.6) + virtus (1.0.5) + axiom-types (~> 0.1) + coercible (~> 1.0) + descendants_tracker (~> 0.0, >= 0.0.3) + equalizer (~> 0.0, >= 0.0.9) + +PLATFORMS + ruby + +DEPENDENCIES + nokogiri + redis + telegram-bot-ruby + +BUNDLED WITH + 2.0.1