Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions exercises/all-your-base/.meta/.version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2
110 changes: 50 additions & 60 deletions exercises/all-your-base/all_your_base_test.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
gem 'minitest', '>= 5.0.0'
require 'minitest/autorun'
Copy link
Copy Markdown
Contributor Author

@tommyschaefer tommyschaefer Dec 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Insti Just wanted to double check that it's okay to remove this based on your comment on #509? Thanks!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. remove this line.

require_relative 'all_your_base'

# Test data version: aa12f2e
class AllYourBaseTest < Minitest::Test
def test_single_bit_to_one_decimal
def test_single_bit_one_to_decimal
# skip
digits = [1]
input_base = 2
output_base = 10
Expand Down Expand Up @@ -131,7 +132,7 @@ def test_empty_list
def test_single_zero
skip
digits = [0]
input_base = 10
input_base = 10
output_base = 2
expected = [0]

Expand All @@ -142,10 +143,10 @@ def test_single_zero
"Expected #{expected} but got #{converted}."
end

def test_multiple_zeroes
def test_multiple_zeros
skip
digits = [0, 0, 0]
input_base = 10
input_base = 10
output_base = 2
expected = [0]

Expand Down Expand Up @@ -175,129 +176,118 @@ def test_negative_digit
digits = [1, -1, 1, 0, 1, 0]
input_base = 2
output_base = 10
expected = nil

converted = BaseConverter.convert(input_base, digits, output_base)

assert_equal expected, converted,
"Input base: #{input_base}, output base #{output_base}. " \
"Expected #{expected} but got #{converted}."
assert_raises ArgumentError do
BaseConverter.convert(input_base, digits, output_base)
end
end

def test_invalid_positive_digit
skip
digits = [1, 2, 1, 0, 1, 0]
input_base = 2
output_base = 10
expected = nil

converted = BaseConverter.convert(input_base, digits, output_base)

assert_equal expected, converted,
"Input base: #{input_base}, output base #{output_base}. " \
"Expected #{expected} but got #{converted}."
assert_raises ArgumentError do
BaseConverter.convert(input_base, digits, output_base)
end
end

def test_first_base_is_one
skip
digits = []
input_base = 1
output_base = 10
expected = nil

converted = BaseConverter.convert(input_base, digits, output_base)

assert_equal expected, converted,
"Input base: #{input_base}, output base #{output_base}. " \
"Expected #{expected} but got #{converted}."
assert_raises ArgumentError do
BaseConverter.convert(input_base, digits, output_base)
end
end

def test_second_base_is_one
skip
digits = [1, 0, 1, 0, 1, 0]
input_base = 2
output_base = 1
expected = nil

converted = BaseConverter.convert(input_base, digits, output_base)

assert_equal expected, converted,
"Input base: #{input_base}, output base #{output_base}. " \
"Expected #{expected} but got #{converted}."
assert_raises ArgumentError do
BaseConverter.convert(input_base, digits, output_base)
end
end

def test_first_base_is_zero
skip
digits = []
input_base = 0
output_base = 10
expected = nil

converted = BaseConverter.convert(input_base, digits, output_base)

assert_equal expected, converted,
"Input base: #{input_base}, output base #{output_base}. " \
"Expected #{expected} but got #{converted}."
assert_raises ArgumentError do
BaseConverter.convert(input_base, digits, output_base)
end
end

def test_second_base_is_zero
skip
digits = [7]
input_base = 10
output_base = 0
expected = nil

converted = BaseConverter.convert(input_base, digits, output_base)

assert_equal expected, converted,
"Input base: #{input_base}, output base #{output_base}. " \
"Expected #{expected} but got #{converted}."
assert_raises ArgumentError do
BaseConverter.convert(input_base, digits, output_base)
end
end

def test_first_base_is_negative
skip
digits = [1]
input_base = -2
output_base = 10
expected = nil

converted = BaseConverter.convert(input_base, digits, output_base)

assert_equal expected, converted,
"Input base: #{input_base}, output base #{output_base}. " \
"Expected #{expected} but got #{converted}."
assert_raises ArgumentError do
BaseConverter.convert(input_base, digits, output_base)
end
end

def test_second_base_is_negative
skip
digits = [1]
input_base = 2
output_base = -7
expected = nil

converted = BaseConverter.convert(input_base, digits, output_base)

assert_equal expected, converted,
"Input base: #{input_base}, output base #{output_base}. " \
"Expected #{expected} but got #{converted}."
assert_raises ArgumentError do
BaseConverter.convert(input_base, digits, output_base)
end
end

def test_both_bases_are_negative
skip
digits = [1]
input_base = -2
output_base = -7
expected = nil

converted = BaseConverter.convert(input_base, digits, output_base)

assert_equal expected, converted,
"Input base: #{input_base}, output base #{output_base}. " \
"Expected #{expected} but got #{converted}."
assert_raises ArgumentError do
BaseConverter.convert(input_base, digits, output_base)
end
end

# Problems in exercism evolve over time, as we find better ways to ask
# questions.
# The version number refers to the version of the problem you solved,
# not your solution.
#
# Define a constant named VERSION inside of the top level BookKeeping
# module, which may be placed near the end of your file.
#
# In your file, it will look like this:
#
# module BookKeeping
# VERSION = 1 # Where the version number matches the one in the test.
# end
#
# If you are curious, read more about constants on RubyDoc:
# http://ruby-doc.org/docs/ruby-doc-bundle/UsersGuide/rg/constants.html
def test_bookkeeping
skip
assert_equal 1, BookKeeping::VERSION
assert_equal 2, BookKeeping::VERSION
end
end
21 changes: 13 additions & 8 deletions exercises/all-your-base/example.rb
Original file line number Diff line number Diff line change
@@ -1,31 +1,36 @@
module BookKeeping
VERSION = 1
VERSION = 2
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bumped test version since we now expect invalid base / input to raise an error rather than return nil

end

class BaseConverter
def self.convert(base_from, number_array, base_to)
return if number_array.any?{|number| number < 0 || number >= base_from}
return if base_from <= 1 || base_to <= 1
fail ArgumentError if invalid_inputs?(base_from, number_array, base_to)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to change as little as possible here to get tests to pass. Does this look okay?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fine.

return [] unless number_array.any?
number_in_canonical_base = convert_to_canonical_base(number_array, base_from)
convert_from_canonical_base(number_in_canonical_base, base_to)
end

private
private_class_method

def self.invalid_inputs?(base_from, number_array, base_to)
number_array.any? { |number| number < 0 || number >= base_from } ||
base_from <= 1 || base_to <= 1
end

def self.convert_to_canonical_base(number_array, base)
total = 0
number_array.reverse.each_with_index do |number, index|
total += number * base ** index
total += number * base**index
end
total
end

def self.convert_from_canonical_base(number, base_to)
result = []
current_number = number
while current_number >= base_to do
result << current_number % base_to
current_number = current_number / base_to
while current_number >= base_to
result << current_number % base_to
current_number /= base_to
end
result << current_number % base_to
result.reverse
Expand Down
17 changes: 17 additions & 0 deletions exercises/all-your-base/example.tt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require 'minitest/autorun'
require_relative 'all_your_base'

# Test data version: <%= sha1 %>
class AllYourBaseTest < Minitest::Test<% test_cases.each do |test_case| %>
def <%= test_case.test_name %>
<%= test_case.skipped %>
<%= test_case.workload %>
end
<% end %>

<%= IO.read(XRUBY_LIB + '/bookkeeping.md') %>
def test_bookkeeping
skip
assert_equal <%= version.next %>, BookKeeping::VERSION
end
end
109 changes: 109 additions & 0 deletions lib/all_your_base_cases.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
class AllYourBaseCase < OpenStruct
def test_name
'test_%s' % description.downcase.tr(' -', '_')
end

def workload
indent(4, (assignments + assertion).join("\n")) + "\n"
end

def skipped
index.zero? ? '# skip' : 'skip'
end

private

def indent(size, text)
text.lines.each_with_object('') do |line, obj|
obj << (line == "\n" ? line : ' ' * size + line)
end
end

def assignments
[
"digits = #{input_digits}",
"input_base = #{input_base}",
"output_base = #{output_base}",
]
end

def assertion
return error_assertion unless expected

[
"expected = #{expected}",
"",
"converted = BaseConverter.convert(input_base, digits, output_base)",
"",
"assert_equal expected, converted,",
indent(13, error_message),
]
end

def error_assertion
[
"",
"assert_raises ArgumentError do",
" BaseConverter.convert(input_base, digits, output_base)",
"end",
]
end

def error_message
%q("Input base: #{input_base}, output base #{output_base}. " \\) \
"\n" + %q("Expected #{expected} but got #{converted}.")
end
end

class AllYourBaseCase::PreProcessor
class << self
attr_reader :row

def call(row)
@row = row

row.merge('expected' => expected_value)
end

private :row
private

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not:

    private

    attr_reader :row

Move the private things past the private declaration... Leaving call above that line.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what we were chatting about on Gitter the other day.

In ruby < 2.3.0 private reader methods made like that cause a warning. In 2.3 that warning is removed. Links to some info about this are in the last commit message.

Thanks for the feedback!

Copy link
Copy Markdown
Member

@kotp kotp Jan 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the last commit, and instead of moving the line, one more line was created.
If you move the attr_reader to below the private line, there is no warning for 2.4 (in regards to that) that I am finding.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warning was removed in ruby 2.3, so in all later versions it isn't there and you won't see it in 2.4. Since we use 2.1.2, it shows up in CI and whatnot.

I'm not sure if moving the reader under private but keeping the explicit private with method name will still make a warning. I'll give this a try when I'm home.

Sorry for the lack of code samples... aparently the iOS keyboard doesn't have a back tick key? 😞

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying commit message here so it can be read inline:

This change is to remove a warning present in ruby versions
less than 2.3.0. A thread discussing this warning can be found
at https://bugs.ruby-lang.org/issues/10967. It is removed in
ruby/ruby@32a5a09.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's what I had originally but it's what causes the warning mentioned above. Ruby versions less than 2.3.0 have a warning when a private attribute is defined unless its made private explicitly with private :method_name

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it is a warning, and not functionally wrong, I would probably leave it at that. This also means as time goes on and the version of Ruby moves forward, it will eventually take care of itself.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer that the code not emit warnings. If that means temporarily doing some sub-optimal things to keep the interpreter happy then so be it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely prefer warning free. How will we trigger when it should be "corrected" when the warning would naturally go away? Not that it is an important change...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it needs an explicit trigger. Next time that code gets edited, whoever modifies it will think it looks wrong, (unjustly) curse @tommyschaefer for being an idiot 😜 and fix it.

def expected_value
return row['expected'] if row['expected']

if invalid_input_digits? || invalid_bases?
nil
elsif row['input_digits'].empty?
[]
elsif input_of_zero?
[0]
else
handle_special_cases
end
end

def invalid_input_digits?
row['input_digits'].any? { |x| x < 0 || x >= row['input_base'] }
end

def invalid_bases?
row['input_base'] <= 1 || row['output_base'] <= 1
end

def input_of_zero?
row['input_digits'].all? { |x| x == 0 }
end

def handle_special_cases
[4,2] if row['input_digits'] == [0, 6, 0]
end
end
end

AllYourBaseCases = proc do |data|
JSON.parse(data)['cases'].map.with_index do |row, i|
AllYourBaseCase.new(
AllYourBaseCase::PreProcessor.call(row).merge(index: i),
)
end
end