0

There are 64 squares on a chessboard. Every square has the double that the one before. Write a program that shows: - how many grains were on each square, and - the total number of grains

My code is working for the first part, but I have problems declaring the total. Some basic class/method declaration that I am missing. Thanks for helping.

class Grains

  def square(n)
    array_of_grains = []
    (0..63).each {|x| array_of_grains << 2**x}
     n = n-1
    array_of_grains[n]
  end

  def total
     array_of_grains.each {|x| sum += x }
  end

end
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
RadW2020
  • 148
  • 2
  • 11

5 Answers5

3

There are a bunch of problems here.

First, array_of_grains is local to square. It doesn't exist outside the method. If you want it to be an instance variable on an instance of Grain, it needs to be @array_of_grains.

Second, your total method causes an error because there is no local array_of_grains variable for it to invoke each on. Even if there were, sum would then be undefined. You need to declare sum and set its initial value to 0, or simply use inject(&:+), which is amongst the oldest Ruby tricks in the book.

Third, you're re-building your entire array_of_grains array every time the method square is invoked. You can use ||= to only compute that value once.

Your methods should look something like this:

def square(n)
  @array_of_grains ||= (0..63).map {|x| 2 ** x }
  @array_of_grains[n - 1]
end

def total
  @array_of_grains.inject(&:+)
end

You can make your code almost work by the simple addition of an attr_accessor :array_of_grains above your methods, but that still wouldn't fix the problem with sum not being defined.

Community
  • 1
  • 1
user229044
  • 232,980
  • 40
  • 330
  • 338
2

This is one way to write it:

square = 63.times.with_object([1]) { |i,a| a << 2 * a.last }
  #=> [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192,
  #    16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152,
  #...
  #    2305843009213693952, 4611686018427387904, 9223372036854775808]

To determine the total as well (that I initially missed in the question):

total, square = 63.times.reduce([0,[1]]) do |(t,a),_|
  v = 2 * a.last
  [t + v, a << v]
end

total  #=> 18446744073709551614
square #=> (as above)
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • That's extremely over-wrought, and much longer than the clearer solution of `squares = (1..63).map { |i| 2 ** i }; total = squares.inject(&:+)` for no gain at all and a huge hit to readability. If I encountered this code in any codebase I worked on, I would replace it with something cleaner. – user229044 Nov 11 '14 at 01:35
  • Would you fire me as well? (I put it there for educational value.) – Cary Swoveland Nov 11 '14 at 01:40
  • 1
    No, I would coach you on why that's a bizarre and ugly solution. – user229044 Nov 11 '14 at 01:42
2

Here's a meta-answer, combining all the good ideas here (upvotes all around!) into a complete solution using your class.

class Grains

  attr_accessor :array_of_grains

  def initialize(n = 1)
    @array_of_grains = 63.times.with_object([n]) { |i,a| a << 2 * a.last }
  end

  def square(n)
    array_of_grains[n - 1]
  end

  def total
    array_of_grains.reduce(:+)
  end
end

Usage:

board = Grains.new
board.square(3) #=> 4
board.total #=> 18446744073709551615
Mark Thomas
  • 37,131
  • 11
  • 74
  • 101
1

If you want array_of_grains available as an instance variable use @array_of_grains

You can also use Ruby's accessors:

attr_accessor array_of_grains # getter and setter
attr_reader array_of_grains # getter
attr_writer array_of_grains # setter

In which case you could do:

class Grains

  attr_accessor :array_of_grains

  def set_grains(array)
    self.array_of_grains = array
  end

  def total
    return self.array_of_grains.count
  end

end
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
kreek
  • 8,774
  • 8
  • 44
  • 69
  • I think the count method is usefull only to return the sum of number of elements that we have. We need to calculate de sum of the values. – RadW2020 Nov 11 '14 at 17:52
1
@square_of_sums = (n * (n + 1) / 2)**2
@number_of_squares = n * (n + 1) * (2 * n + 1) / 6

This will still take time, but it is the math for what you are wanting to do.

vgoff
  • 10,980
  • 3
  • 38
  • 56