diff --git a/challengeA/okamoto/.gitignore b/challengeA/okamoto/.gitignore new file mode 100644 index 000000000..f05cf8c8d --- /dev/null +++ b/challengeA/okamoto/.gitignore @@ -0,0 +1 @@ +/spec/examples.txt diff --git a/challengeA/okamoto/.rspec b/challengeA/okamoto/.rspec new file mode 100644 index 000000000..c99d2e739 --- /dev/null +++ b/challengeA/okamoto/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/challengeA/okamoto/Gemfile b/challengeA/okamoto/Gemfile new file mode 100644 index 000000000..8e9e159ba --- /dev/null +++ b/challengeA/okamoto/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem 'rspec' diff --git a/challengeA/okamoto/Gemfile.lock b/challengeA/okamoto/Gemfile.lock new file mode 100644 index 000000000..f7f91fc03 --- /dev/null +++ b/challengeA/okamoto/Gemfile.lock @@ -0,0 +1,26 @@ +GEM + remote: https://rubygems.org/ + specs: + diff-lcs (1.4.4) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) + rspec-core (3.10.1) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-support (3.10.2) + +PLATFORMS + x64-mingw32 + +DEPENDENCIES + rspec + +BUNDLED WITH + 2.2.20 diff --git a/challengeA/okamoto/README.md b/challengeA/okamoto/README.md new file mode 100644 index 000000000..88c25156d --- /dev/null +++ b/challengeA/okamoto/README.md @@ -0,0 +1,30 @@ +# CHALLENGE A + +## 概要 + +契約アンペア(A)と月の電力使用量(kWh)の入力に応じて、各プラン名と料金のシミュレーションを行うスクリプトです。 + +## シミュレーション実行方法 + +```sh +ruby main.rb +``` + +## データの編集 + +JSON形式で`/data`のディレクトリに格納しています。プラン追加や編集の際はJSONデータを修正することで対応できるように実装しています。 + +## テスト + +```sh +bundle exec rspec +``` + +## 補足事項 + +- 合計電気料金は小数点以下切り捨て + +## 参考文献 + +- オブジェクト指向設計実践ガイド +- [使えるRSpec入門](https://qiita.com/jnchito/items/42193d066bd61c740612) diff --git a/challengeA/okamoto/data/plans.json b/challengeA/okamoto/data/plans.json new file mode 100644 index 000000000..70d7c550b --- /dev/null +++ b/challengeA/okamoto/data/plans.json @@ -0,0 +1,166 @@ +[ + { + "name": "従量電灯B", + "provider_name": "東京電力エナジーパートナー", + "basic_price": [ + { + "ampere": 10, + "price": 286.0 + }, + { + "ampere": 15, + "price": 429.0 + }, + { + "ampere": 20, + "price": 572.0 + }, + { + "ampere": 30, + "price": 858.0 + }, + { + "ampere": 40, + "price": 1144.0 + }, + { + "ampere": 50, + "price": 1430.0 + }, + { + "ampere": 60, + "price": 1716.0 + } + ], + "pay_per_use_price": [ + { + "price_per_kwh": 19.88, + "min_kwh": 0 + }, + { + "price_per_kwh": 26.48, + "min_kwh": 120 + }, + { + "price_per_kwh": 30.57, + "min_kwh": 300 + } + ] + }, + { + "name": "おうちプラン", + "provider_name": "Looopでんき", + "basic_price": [ + { + "ampere": 10, + "price": 0 + }, + { + "ampere": 15, + "price": 0 + }, + { + "ampere": 20, + "price": 0 + }, + { + "ampere": 30, + "price": 0 + }, + { + "ampere": 40, + "price": 0 + }, + { + "ampere": 50, + "price": 0 + }, + { + "ampere": 60, + "price": 0 + } + ], + "pay_per_use_price": [ + { + "price_per_kwh": 26.40, + "min_kwh": 0 + } + ] + }, + { + "name": "ずっとも電気1", + "provider_name": "東京ガス", + "basic_price": [ + { + "ampere": 30, + "price": 858.0 + }, + { + "ampere": 40, + "price": 1144.0 + }, + { + "ampere": 50, + "price": 1430.0 + }, + { + "ampere": 60, + "price": 1716.0 + } + ], + "pay_per_use_price": [ + { + "price_per_kwh": 23.67, + "min_kwh": 0 + }, + { + "price_per_kwh": 23.88, + "min_kwh": 140 + }, + { + "price_per_kwh": 26.41, + "min_kwh": 350 + } + ] + }, + { + "name": "従量電灯Bたっぷりプラン", + "provider_name": "JXTGでんき", + "basic_price": [ + { + "ampere": 30, + "price": 858.0 + }, + { + "ampere": 40, + "price": 1144.0 + }, + { + "ampere": 50, + "price": 1430.0 + }, + { + "ampere": 60, + "price": 1716.8 + } + ], + "pay_per_use_price": [ + { + "price_per_kwh": 19.88, + "min_kwh": 0 + }, + { + "price_per_kwh": 26.48, + "min_kwh": 120 + }, + { + "price_per_kwh": 25.08, + "min_kwh": 300 + }, + { + "price_per_kwh": 26.15, + "min_kwh": 600 + } + ] + } +] diff --git a/challengeA/okamoto/lib/plan.rb b/challengeA/okamoto/lib/plan.rb new file mode 100644 index 000000000..65a4f8cdc --- /dev/null +++ b/challengeA/okamoto/lib/plan.rb @@ -0,0 +1,52 @@ +class Plan + attr_reader :name, :provider_name, :basic_prices, :pay_per_use_prices + + def initialize(name:, provider_name:, basic_prices:, pay_per_use_prices:) + @name = name + @provider_name = provider_name + @basic_prices = basic_prices + @pay_per_use_prices = pay_per_use_prices + end + + def available?(contract_amp) + basic_prices.find { |price| price['ampere'] == contract_amp } + end + + def plan_with_price(contract_amp, power_usage) + { + provider_name: provider_name, + plan_name: name, + price: sum_price(contract_amp, power_usage) + } + end + + private + + def sum_price(contract_amp, power_usage) + (basic_price(contract_amp) + pay_per_use_price(power_usage)).floor + end + + def basic_price(contract_amp) + basic_prices.find { |price| price['ampere'] == contract_amp }['price'] + end + + def pay_per_use_price(power_usage) + sum = 0 + power_usage_before_stage = 0 + sorted_pay_per_use_price_lists(power_usage).each do |price_list| + power_usage_current_stage = power_usage - price_list['min_kwh'] - power_usage_before_stage + #丸め誤差が生じるため小数点第四位を四捨五入とする + sum += (price_list['price_per_kwh'] * power_usage_current_stage).round(3) + power_usage_before_stage += power_usage_current_stage + end + sum + end + + def sorted_pay_per_use_price_lists(power_usage) + pay_per_use_price_lists(power_usage).sort_by {|list| list['min_kwh']}.reverse + end + + def pay_per_use_price_lists(power_usage) + pay_per_use_prices.select { |price| price['min_kwh'] <= power_usage } + end +end diff --git a/challengeA/okamoto/lib/plans.rb b/challengeA/okamoto/lib/plans.rb new file mode 100644 index 000000000..702011a0f --- /dev/null +++ b/challengeA/okamoto/lib/plans.rb @@ -0,0 +1,33 @@ +require './lib/plan' +require 'json' +JSON_FILE_PATH = '../data/plans.json' + +class Plans + attr_reader :contract_amp, :power_usage, :plans + + def initialize(contract_amp, power_usage) + @plans = load_plans + @contract_amp = contract_amp + @power_usage = power_usage + end + + def show_plans + plans.map { |plan| plan.available?(contract_amp)? plan.plan_with_price(contract_amp, power_usage) : nil }.compact + end + + private + + def load_plans + load_json.map {|data| Plan.new( + name: data['name'], + provider_name: data['provider_name'], + basic_prices: data['basic_price'], + pay_per_use_prices: data['pay_per_use_price'] + )} + end + + def load_json + json_file_path = File.expand_path(JSON_FILE_PATH, __dir__) + JSON.load(File.open(json_file_path)) + end +end diff --git a/challengeA/okamoto/lib/simulator.rb b/challengeA/okamoto/lib/simulator.rb new file mode 100644 index 000000000..d48034eb3 --- /dev/null +++ b/challengeA/okamoto/lib/simulator.rb @@ -0,0 +1,15 @@ +require './lib/plans' + +class Simulator + attr_reader :contract_amp, :power_usage, :plans + + def initialize(contract_amp, power_usage) + @contract_amp = contract_amp + @power_usage = power_usage + @plans = Plans.new(contract_amp, power_usage) + end + + def simulate + plans.show_plans + end +end diff --git a/challengeA/okamoto/main.rb b/challengeA/okamoto/main.rb new file mode 100644 index 000000000..729a5e692 --- /dev/null +++ b/challengeA/okamoto/main.rb @@ -0,0 +1,37 @@ +require './lib/simulator' + +AVAILABLE_AMP = [10, 15, 20, 30, 40, 50, 60] + +puts '契約アンペア数を数字のみで入力してください。' +puts "#{AVAILABLE_AMP}の中から選択してください。" +contract_amp = gets.to_i + +unless AVAILABLE_AMP.include?(contract_amp) + puts '契約アンペア数の入力が不正です。' + exit +end + +puts '電力使用量(kWh)を整数のみで入力してください' +power_usage = gets.to_i + +if power_usage.negative? + puts '電力使用量(kWh)は0以上を入力してください。' + exit +end + +simulator = Simulator.new(contract_amp, power_usage) +plan_lists = simulator.simulate + +if plan_lists.empty? + puts '条件に一致するプランはありません。' +else + puts 'ご入力いただいた条件での見積もり結果は以下の通りです。' + puts "契約アンペア:#{contract_amp}(A), 電力使用量:#{power_usage}(kwh)" + puts '================================================' + plan_lists.each do |plan| + puts "プロバイダー名:#{plan[:provider_name]}" + puts "プラン名:#{plan[:plan_name]}" + puts "月額料金:#{plan[:price]}円" + puts '================================================' + end +end diff --git a/challengeA/okamoto/spec/plans_spec.rb b/challengeA/okamoto/spec/plans_spec.rb new file mode 100644 index 000000000..2c05fad09 --- /dev/null +++ b/challengeA/okamoto/spec/plans_spec.rb @@ -0,0 +1,209 @@ +require 'spec_helper' + +RSpec.describe Plans do + TEPCO = '東京電力エナジーパートナー' + TEPCO_PLAN1 = '従量電灯B' + LOOOP = 'Looopでんき' + LOOOP_PLAN1 = 'おうちプラン' + TOKYOGAS = '東京ガス' + TOKYOGAS_PLAN1 = 'ずっとも電気1' + JXTG = 'JXTGでんき' + JXTG_PLAN1 = '従量電灯Bたっぷりプラン' + + describe '#show_plans' do + let(:plans) { Plans.new(amp, power) } + let(:tepco_plan1_price) { plans.show_plans.find {|plan| plan[:provider_name] == TEPCO && plan[:plan_name] == TEPCO_PLAN1 }[:price] } + let(:looop_plan1_price) { plans.show_plans.find {|plan| plan[:provider_name] == LOOOP && plan[:plan_name] == LOOOP_PLAN1 }[:price] } + let(:tokyogas_plan1_price) { plans.show_plans.find {|plan| plan[:provider_name] == TOKYOGAS && plan[:plan_name] == TOKYOGAS_PLAN1 }[:price] } + let(:jxtg_plan1_price) { plans.show_plans.find {|plan| plan[:provider_name] == JXTG && plan[:plan_name] == JXTG_PLAN1 }[:price] } + + shared_examples 'planがヒットしないこと' do + it { expect(plans.show_plans.count).to eq 0 } + end + + shared_examples 'planが2件ヒットすること' do + it { expect(plans.show_plans.count).to eq 2 } + end + + shared_examples 'planが4件ヒットすること' do + it { expect(plans.show_plans.count).to eq 4 } + end + + context 'planヒット件数毎のテスト' do + context '141kwhの場合' do + let(:power) { 141 } + context '10Aの場合' do + let(:amp) { 10 } + + it_behaves_like 'planが2件ヒットすること' + it 'tepco_plan1_の価格が正常であること' do + expect(tepco_plan1_price).to eq (286.0 + 120 * 19.88 + (141 - 120) * 26.48).floor + end + it 'looop_plan1_の価格が正常であること' do + expect(looop_plan1_price).to eq (141 * 26.4).floor + end + end + + context '60Aの場合' do + let(:amp) { 60 } + + it_behaves_like 'planが4件ヒットすること' + it 'tepco_plan1_の価格が正常であること' do + expect(tepco_plan1_price).to eq (1716.0 + 120 * 19.88 + (141 - 120) * 26.48).floor + end + it 'looop_plan1_の価格が正常であること' do + expect(looop_plan1_price).to eq (141 * 26.4).floor + end + it 'tokyogas_plan1_の価格が正常であること' do + expect(tokyogas_plan1_price).to eq (1716.0 + 140 * 23.67 + (141 - 140) * 23.88).floor + end + it 'jxtg_plan1_の価格が正常であること' do + expect(jxtg_plan1_price).to eq (1716.8 + 120 * 19.88 + (141 - 120) * 26.48).floor + end + end + end + end + + context '従量制プランの境界値テスト' do + context '30Aのとき' do + let(:amp) { 30 } + context '0kwhのとき' do + let(:power) { 0 } + + it_behaves_like 'planが4件ヒットすること' + it 'tepco_plan1_の価格が正常であること' do + expect(tepco_plan1_price).to eq 858 + end + it 'looop_plan1_の価格が正常であること' do + expect(looop_plan1_price).to eq 0 + end + it 'tokyogas_plan1_の価格が正常であること' do + expect(tokyogas_plan1_price).to eq 858 + end + it 'jxtg_plan1_の価格が正常であること' do + expect(jxtg_plan1_price).to eq 858 + end + end + + context '120kwhのとき' do + let(:power) { 120 } + + it_behaves_like 'planが4件ヒットすること' + it 'tepco_plan1_の価格が正常であること' do + expect(tepco_plan1_price).to eq (858.0 + 120 * 19.88).floor + end + it 'looop_plan1_の価格が正常であること' do + expect(looop_plan1_price).to eq (120 * 26.4).floor + end + it 'tokyogas_plan1_の価格が正常であること' do + expect(tokyogas_plan1_price).to eq (858.0 + 120 * 23.67).floor + end + it 'jxtg_plan1_の価格が正常であること' do + expect(jxtg_plan1_price).to eq (858.0 + 120 * 19.88).floor + end + end + + context '140kwhのとき' do + let(:power) { 140 } + + it_behaves_like 'planが4件ヒットすること' + it 'tepco_plan1_の価格が正常であること' do + expect(tepco_plan1_price).to eq (858.0 + 120 * 19.88 + (140 - 120) * 26.48).floor + end + it 'looop_plan1_の価格が正常であること' do + expect(looop_plan1_price).to eq (140 * 26.4).floor + end + it 'tokyogas_plan1_の価格が正常であること' do + expect(tokyogas_plan1_price).to eq (858.0 + 140 * 23.67).floor + end + it 'jxtg_plan1_の価格が正常であること' do + expect(jxtg_plan1_price).to eq (858.0 + 120 * 19.88 + (140 - 120) * 26.48).floor + end + end + + context '300kwhのとき' do + let(:power) { 300 } + + it_behaves_like 'planが4件ヒットすること' + it 'tepco_plan1_の価格が正常であること' do + expect(tepco_plan1_price).to eq (858.0 + 120 * 19.88 + (300 - 120) * 26.48).floor + end + it 'looop_plan1_の価格が正常であること' do + expect(looop_plan1_price).to eq (300 * 26.4).floor + end + it 'tokyogas_plan1_の価格が正常であること' do + expect(tokyogas_plan1_price).to eq (858.0 + 140 * 23.67 + (300 - 140) * 23.88).floor + end + it 'jxtg_plan1_の価格が正常であること' do + expect(jxtg_plan1_price).to eq (858.0 + 120 * 19.88 + (300 - 120) * 26.48).floor + end + end + + context '350kwhのとき' do + let(:power) { 350 } + + it_behaves_like 'planが4件ヒットすること' + it 'tepco_plan1_の価格が正常であること' do + expect(tepco_plan1_price).to eq (858.0 + 120 * 19.88 + (300 - 120) * 26.48 + (350 - 300) * 30.57).floor + end + it 'looop_plan1_の価格が正常であること' do + expect(looop_plan1_price).to eq (350 * 26.4).floor + end + it 'tokyogas_plan1_の価格が正常であること' do + expect(tokyogas_plan1_price).to eq (858.0 + 140 * 23.67 + (350 - 140) * 23.88).floor + end + it 'jxtg_plan1_の価格が正常であること' do + expect(jxtg_plan1_price).to eq (858.0 + 120 * 19.88 + (300 - 120) * 26.48 + (350 - 300) * 25.08).floor + end + end + + context '600kwhのとき' do + let(:power) { 600 } + + it_behaves_like 'planが4件ヒットすること' + it 'tepco_plan1_の価格が正常であること' do + expect(tepco_plan1_price).to eq (858.0 + 120 * 19.88 + (300 - 120) * 26.48 + (600 - 300) * 30.57).floor + end + it 'looop_plan1_の価格が正常であること' do + expect(looop_plan1_price).to eq (600 * 26.4).floor + end + it 'tokyogas_plan1_の価格が正常であること' do + expect(tokyogas_plan1_price).to eq (858.0 + 140 * 23.67 + (350 - 140) * 23.88 + (600 - 350) * 26.41).floor + end + it 'jxtg_plan1_の価格が正常であること' do + expect(jxtg_plan1_price).to eq (858.0 + 120 * 19.88 + (300 - 120) * 26.48 + (600 - 300) * 25.08).floor + end + end + + context '601kwhのとき' do + let(:power) { 601 } + + it_behaves_like 'planが4件ヒットすること' + it 'tepco_plan1_の価格が正常であること' do + expect(tepco_plan1_price).to eq (858.0 + 120 * 19.88 + (300 - 120) * 26.48 + (601 - 300) * 30.57).floor + end + it 'looop_plan1_の価格が正常であること' do + expect(looop_plan1_price).to eq (601 * 26.4).floor + end + it 'tokyogas_plan1_の価格が正常であること' do + expect(tokyogas_plan1_price).to eq (858.0 + 140 * 23.67 + (350 - 140) * 23.88 + (601 - 350) * 26.41).floor + end + it 'jxtg_plan1_の価格が正常であること' do + expect(jxtg_plan1_price).to eq (858.0 + 120 * 19.88 + (300 - 120) * 26.48 + (600 - 300) * 25.08 + (601 - 600) * 26.15).floor + end + end + end + end + + context '契約アンペア対象がない場合のテスト' do + context '141kwhの場合' do + let(:power) { 141 } + context '70Aの場合' do + let(:amp) { 70 } + + it_behaves_like 'planがヒットしないこと' + end + end + end + end +end diff --git a/challengeA/okamoto/spec/spec_helper.rb b/challengeA/okamoto/spec/spec_helper.rb new file mode 100644 index 000000000..a57ece4ab --- /dev/null +++ b/challengeA/okamoto/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +end + +Dir[File.join(File.dirname(__FILE__), "../lib/**/*.rb")].each { |f| require f }