vvm 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 255e957bd23e4b4b099fd3cfa5eb3f215eaa94b6032bfcf23ec3479e0a4866a7
4
- data.tar.gz: e4c83101f4f8daea662b07aa5c2ae7677814b6375d38824b6f8cf43fcc47271c
3
+ metadata.gz: '0742129dc37e28bf46ea49679313f24eb670cbf949f5db63c433a6dfd0848349'
4
+ data.tar.gz: 28be2e9c01eee6566c07a3f8618c06601b3534bffa097de8514da72df1b4e856
5
5
  SHA512:
6
- metadata.gz: 76f07cb86f35e4425c37ff8f92c46a9b03b3d498eda1e4407484b837479dcd240edfb37ad1835ed7aa4794d28b0278bce206844e2b7dfd9ab7d587fb74de1318
7
- data.tar.gz: 0301b3b61d82540cc78b89520c81882447edd71befa47bf3c35d53c4b46a3c7bc5b59e2ab98a2839dc73739abafc49146028740022cbcea0d80cd16c3e9a188c
6
+ metadata.gz: f341b440b96bf4669f8fb8af2c85ee696a662ba4f68fde5cee5ee3061ff912ebc405f334f2b0ec3f6b254c2ca8608a63febb5aad59ddaa2997adbd9ee9a8502c
7
+ data.tar.gz: 668038ac248fc9a3fd99085fecbe0960c97fd78a45622744291500bd49992713dddcebaa8cee308fb8cdd59b7ae0961d0cd44621a7ca3a5809b0db5fc89c59f6
@@ -9,7 +9,9 @@ module Vvm
9
9
  def call
10
10
  prompt.say("Balance: $#{machine.balance}")
11
11
 
12
- formatted_coins = machine.dispense.filter { _1.qty.positive? }.map { "#{_1.qty} x $#{_1.value}" }.join(', ')
12
+ coins = machine.dispense.filter { _1.qty.positive? }.map { "#{_1.qty} x $#{_1.value}" }
13
+
14
+ formatted_coins = coins.empty? ? '0' : coins.join(', ')
13
15
 
14
16
  env.call(:confirm_dispense, { formatted_coins: formatted_coins })
15
17
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vvm
4
+ class CLI
5
+ module Command
6
+ class Exit < Base
7
+ self.command_name = :exit
8
+
9
+ def call
10
+ exit
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -12,7 +12,8 @@ module Vvm
12
12
  choices = [
13
13
  { name: 'Insert a coin', value: :insert_coin, **maybe_out_of_stock },
14
14
  { name: 'Pick the product', value: :pick_product },
15
- { name: 'Dispense', value: :dispense, **maybe_empty_balance, **maybe_no_change }
15
+ { name: 'Dispense', value: :dispense, **maybe_empty_balance, **maybe_no_change },
16
+ { name: 'Exit', value: :exit }
16
17
  ]
17
18
 
18
19
  result = prompt.select('Main menu:', choices)
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vvm
4
+ class Dispenser
5
+ def initialize(amount, coins)
6
+ @amount = amount
7
+ @coins = coins
8
+ @min_coins = []
9
+ @min_qty = 0
10
+ @subset = coins
11
+ end
12
+
13
+ def call # rubocop:disable Metrics/AbcSize
14
+ return give_away if not_enough_change?
15
+
16
+ coins.each_with_object([]).with_index do |(_, memo), index|
17
+ self.subset = define_subset(index)
18
+
19
+ result = calculate(subset, amount)
20
+
21
+ memo << result if result.sum { _1.value * _1.qty } == amount
22
+
23
+ match_qty = calculate_min_qty(memo)
24
+
25
+ calibrate(memo, match_qty) if min_coins.empty? || match_qty < min_qty
26
+ end
27
+
28
+ min_coins
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :amount, :coins
34
+ attr_accessor :min_coins, :min_qty, :subset
35
+
36
+ def give_away
37
+ coins.map do |coin|
38
+ dup = coin.dup
39
+ coin.qty = 0
40
+ dup
41
+ end
42
+ end
43
+
44
+ def not_enough_change?
45
+ amount > coins.sum { _1.value * _1.qty }
46
+ end
47
+
48
+ def define_subset(index)
49
+ coins.dup.tap do |list|
50
+ list.delete_at(index) unless index.zero?
51
+ list.each { |coin| coin.qty = initial_coins.detect { coin.value == _1.value }.qty }
52
+ end
53
+ end
54
+
55
+ def calculate_min_qty(coins_matrix)
56
+ coins_matrix.min_by { _1.sum(&:qty) }.sum(&:qty)
57
+ end
58
+
59
+ def calibrate(coins_matrix, match_qty)
60
+ self.min_coins = coins_matrix.detect { _1.sum(&:qty) == match_qty }
61
+ self.min_qty = match_qty
62
+ end
63
+
64
+ def calculate(coins, change, index = 0) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
65
+ coins.drop(index).each_with_object([]).with_index do |(coin, memo), idx|
66
+ remainder = change % coin.value
67
+
68
+ if coin.qty.positive? && coin.value <= change && remainder < change
69
+ required_qty = [coin.qty, (change - remainder) / coin.value].min
70
+
71
+ memo << Vvm::Model::Coin.new(coin.value, required_qty)
72
+
73
+ amount = required_qty * coin.value
74
+ change_left = change - amount
75
+ coin.qty -= 1
76
+
77
+ break memo if change_left.zero?
78
+
79
+ sub_result = calculate(coins, change_left, idx + 1)
80
+
81
+ break unless sub_result
82
+
83
+ return memo + sub_result
84
+ end
85
+ end
86
+ end
87
+
88
+ def initial_coins
89
+ @initial_coins ||= coins.map(&:dup)
90
+ end
91
+ end
92
+ end
data/lib/vvm/machine.rb CHANGED
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'YAML'
4
- require 'forwardable'
5
-
6
3
  module Vvm
7
4
  class Machine
8
5
  extend Forwardable
@@ -3,6 +3,10 @@
3
3
  module Vvm
4
4
  module State
5
5
  class Base
6
+ extend Forwardable
7
+
8
+ def_delegators :machine, :coins, :balance
9
+
6
10
  def initialize(machine)
7
11
  @machine = machine
8
12
  end
@@ -15,21 +19,12 @@ module Vvm
15
19
  raise NotImplementedError
16
20
  end
17
21
 
18
- def dispense # rubocop:disable Metrics/AbcSize
19
- machine.coins.each_with_object([]) do |coin, acc|
20
- next if coin.value > machine.balance
21
-
22
- unbalansed_qty = (machine.balance / coin.value).floor
23
- qty_to_return = unbalansed_qty > coin.qty ? coin.qty : unbalansed_qty
24
-
25
- coin_to_return = coin.dup
26
- coin_to_return.qty = qty_to_return
22
+ def dispense
23
+ result = Vvm::Dispenser.new(balance, coins).call
27
24
 
28
- acc.push(coin_to_return)
29
- coin.qty -= qty_to_return
25
+ machine.balance -= result.sum { _1.qty * _1.value }
30
26
 
31
- machine.balance = (machine.balance - (coin.value * qty_to_return)).round(2)
32
- end
27
+ result
33
28
  end
34
29
 
35
30
  private
data/lib/vvm/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vvm
4
- VERSION = '0.1.1'
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/vvm.rb CHANGED
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'tty-prompt'
4
+ require 'yaml'
5
+ require 'forwardable'
4
6
 
5
7
  require_relative 'vvm/version'
6
8
  require_relative 'vvm/cli'
7
9
  require_relative 'vvm/machine'
10
+ require_relative 'vvm/dispenser'
8
11
  require_relative 'vvm/model/coin'
9
12
  require_relative 'vvm/model/product'
10
13
  require_relative 'vvm/error'
@@ -18,3 +21,4 @@ require_relative 'vvm/cli/command/pick_product'
18
21
  require_relative 'vvm/cli/command/dispense'
19
22
  require_relative 'vvm/cli/command/confirm_dispense'
20
23
  require_relative 'vvm/cli/command/confirm_product_pick'
24
+ require_relative 'vvm/cli/command/exit'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vvm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Bykov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-10-20 00:00:00.000000000 Z
11
+ date: 2021-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tty-prompt
@@ -41,10 +41,12 @@ files:
41
41
  - lib/vvm/cli/command/confirm_dispense.rb
42
42
  - lib/vvm/cli/command/confirm_product_pick.rb
43
43
  - lib/vvm/cli/command/dispense.rb
44
+ - lib/vvm/cli/command/exit.rb
44
45
  - lib/vvm/cli/command/insert_coin.rb
45
46
  - lib/vvm/cli/command/pick_product.rb
46
47
  - lib/vvm/cli/command/show_main_menu.rb
47
48
  - lib/vvm/cli/environment.rb
49
+ - lib/vvm/dispenser.rb
48
50
  - lib/vvm/error.rb
49
51
  - lib/vvm/machine.rb
50
52
  - lib/vvm/model/coin.rb