On Fri, 10 Oct 2008 15:29:02 -0500, Matthew Moss wrote: >> Just a preview of my solution (inspired by TZFile): >> >> Mod26=ModularBase.new(26) >> Mod8=ModularBase.new(8) >> >> def test_incompat_bases >> assert_raises(ArgumentError) do >> Mod26.new(1)+Mod8.new(1) >> end >> assert_nothing_raised do >> Mod26.new(1)+Mod8.new(1).to_i >> end >> end >> >> That said, please clarify on the behavior of a couple tests: >> >> #new >> def test_add_reverse >> a = Mod26.new(15) >> assert_equal(???, 15 + a) >> end >> >> #new >> def test_sub_reverse >> a = Mod26.new(15) >> assert_equal(???, 1-a) >> end >> >> #already defined >> def test_mul_int_reverse >> a = Mod8.new(15) >> assert_equal(77, 11 * a) >> end >> >> Clearly a Modulo +/*/- an Integer should give a Modulo Should the >> reverse relation promote the Modulo to an Integer, or promote >> the Integer to a Modulo (of the same base)? > > > This issue requires some sort of design decision, which I intentionally > left out of the requirements, so you may do what you like in this > respect, and perhaps explain why you made a particular choice. > > When I started experimenting with my own solution, my design choice was > that the result type of any operation was the same as the first operand. > I did this primarily for simplicity, since I didn't need to raise errors > for incompatible bases, and most operations became trivial. Of course, > this choice is not without problem, since some operations that might > normally be communitive (if compatible modulo was enforced) are no > longer communitive. > > In context with your questions above and my particular design choice: > > 1. I didn't raise any errors for mismatched bases; I used the left > operand to determine the result's type. 2. As all your reverse tests > (which should be considered by all implementors) have a left Integer > argument, all the answers would be the same, and the domain would be the > full integer set. So, from top to bottom, I would replace ??? with 30 > and -14. OK. I decided against that and decided that the operations would be commutative, always promoting to modular arithmetic. Here's my solution: module ModularBase def self.new base c=Class.new do include ModularBase end c.class_eval do define_method :base do base end end c end def self.define_op op class_eval <<-"end;" def #{op} rhs case rhs when self.class: self.class.new(@value #{op} rhs.instance_variable_get(:@value)) when Integer: self.class.new(@value #{op} rhs) when ModularBase: raise ArgumentError, "Error performing modular arithmetic with incompatible bases" else raise ArgumentError, "Requires an integer or compatible base" end end end; end #begin defining operations def initialize value @value = value % base end define_op :+ define_op :- define_op :* def == rhs case rhs when self.class: @value == rhs.instance_variable_get(:@value) when ModularBase: false when Integer: @value == rhs else false end end def coerce other [self.class.new(other), self] end def <=> rhs case rhs when self.class: @value <=> rhs.instance_variable_get(:@value) when Integer: @value <=> rhs else raise ArgumentError end end def to_i @value end def to_s @value.to_s end end exit if __FILE__ != $0 require 'test/unit' class TestModulo < Test::Unit::TestCase Mod26=ModularBase.new(26) Mod11=ModularBase.new(11) Mod8=ModularBase.new(8) def test_modulo_equality a = Mod26.new(23) b = Mod26.new(23) c = Mod26.new(179) assert_equal(a, b) assert_equal(a, c) end def test_add_zero a = Mod26.new(15) b = Mod26.new(0) assert_equal(a, a + b) end def test_add a = Mod26.new(15) b = Mod26.new(19) c = Mod26.new(8) assert_equal(c, a + b) end def test_add_reverse a = Mod26.new(15) assert_equal(4, 15 + a) end def test_add_int a = Mod26.new(15) c = Mod26.new(10) assert_equal(c, a + 21) end def test_sub a = Mod26.new(15) b = Mod26.new(19) c = Mod26.new(22) assert_equal(c, a - b) end def test_sub_reverse a = Mod26.new(15) assert_equal(12, 1-a) end def test_sub_int a = Mod26.new(15) c = Mod26.new(1) assert_equal(c, a - 66) end def test_mul a = Mod26.new(15) b = Mod26.new(7) c = Mod26.new(1) assert_equal(c, a * b) end def test_mul_int a = Mod26.new(15) c = Mod26.new(9) assert_equal(c, a * 11) end def test_mul_int_reverse a = Mod8.new(15) assert_equal(5, 11 * a) end def test_non_default_modulo a = Mod11.new(15) b = Mod11.new(9) assert_equal(2, a + b) assert_equal(6, a - b) assert_equal(3, a * b) end def test_compare assert_equal(1, Mod26.new(15) <=> Mod26.new(30)) end def test_compare_int assert_equal(-1, Mod26.new(15) <=> 35) end def test_sort x = [Mod26.new(15), Mod26.new(29), Mod26.new(-6), Mod26.new(57)] y = [3, 5, 15, 20] assert_equal(y, x.sort) end def test_incompat_bases assert_raises(ArgumentError) do Mod26.new(1)+Mod8.new(1) end assert_nothing_raised do Mod26.new(1)+Mod8.new(1).to_i end end end -- Chanoch (Ken) Bloom. PhD candidate. Linguistic Cognition Laboratory. Department of Computer Science. Illinois Institute of Technology. http://www.iit.edu/~kbloom1/