I would like to DRY out this code. Is the best solution to do it like in rails with before_action method?
class Direction
attr_accessor :dir
def initialize(dir)
@dir = dir
end
DIRECTIONS = %w[N E S W]
def turn_left
d = DIRECTIONS.find_index(dir)
@dir = DIRECTIONS.rotate!(d-1).first
end
def turn_right
d = DIRECTIONS.find_index(dir)
@dir = DIRECTIONS.rotate!(d+1).first
end
end
# frozen_string_literal: true
class Direction
DIRECTIONS = %w[N E S W].freeze
OPERATIONS = { left: :-, right: :+ }.freeze
private_constant :DIRECTIONS, :OPERATIONS
def initialize(dir)
@dir = dir
end
OPERATIONS.keys.each do |turn_direction| # turn_left, turn_right
define_method "turn_#{turn_direction}" do
turn(turn_direction)
end
end
private
attr_reader :dir
def direction_index
DIRECTIONS.find_index(dir)
end
def turn(operation)
DIRECTIONS.rotate(direction_index.public_send(OPERATIONS[operation], 1)).first
end
end
p Direction.new('N').turn_left # "W"
p Direction.new('E').turn_left # "N"
p Direction.new('S').turn_left # "E"
p Direction.new('W').turn_left # "S"
p Direction.new('N').turn_right # "E"
p Direction.new('E').turn_right # "S"
p Direction.new('S').turn_right # "W"
p Direction.new('W').turn_right # "N"
You can:
freeze
your constants to avoid modifications. Direction
class.dir
if you're only using it within the class itself.OPERATIONS
hash, which defines the direction and the operation it'll use to return the next direction.OPERATIONS
keys to dynamically define the methods turn_left
and turn_right
.direction_index
method, which returns the index in DIRECTIONS
by using dir
.operation
parameter:
operation
you can get the operation from OPERATIONS
which tells how to rotate (positive or negative).-
or +
to the result of direction_index
you get the arguments to rotate.rotate
on DIRECTIONS
and get the first element.I suggest using hashes, mostly for readability.
class Direction
NEXT_LEFT = { 'N'=>'W', 'W'=>'S', 'S'=>'E', 'E'=>'N' }
NEXT_RIGHT = NEXT_LEFT.invert
attr_reader :dir
def initialize(dir)
@dir = dir
end
def turn_left
turn(NEXT_LEFT)
end
def turn_right
turn(NEXT_RIGHT)
end
private
def turn(nxt)
@dir = nxt[@dir]
end
end
d = Direction.new('N')
d.dir
#=> "N"
d.turn_left
#=> "W"
d.turn_left
#=> "S"
d.turn_right
#=> "W"
Note:
NEXT_RIGHT
#=> {"W"=>"N", "S"=>"W", "E"=>"S", "N"=>"E"}
Lots of good answers, but a simple direct solution would be to just factor out the part that is common between the two methods into a turn method and pass in 1
or -1
.
class Direction
attr_accessor :dir
def initialize(dir)
@dir = dir
end
DIRECTIONS = %w[N E S W]
def turn(delta_d)
d = DIRECTIONS.find_index(dir)
@dir = DIRECTIONS.rotate!(d + delta_d).first
end
def turn_left
turn(-1)
end
def turn_right
turn(1)
end
end
You can always implement a direction map independent of the state:
class DirectionMap
def initialize(*list)
# Create a Hash mapping table with left and right directions
# pre-computed. This uses modulo to "wrap" the array around.
@directions = list.map.with_index do |dir, i|
[ dir, [ list[(i - 1) % list.length], list[(i + 1) % list.length] ] ]
end.to_h
end
# These methods use dig to avoid blowing up on an invalid direction,
# instead just returning nil for garbage input.
def left(dir)
@directions.dig(dir, 0)
end
def right(dir)
@directions.dig(dir, 1)
end
end
Where you can now navigate arbitrary compass mappings:
map = DirectionMap.new(*%w[ N E S W ])
map.left('N') # => 'W'
map.left(map.left('N')) # => 'S'
map.right('N') # => 'E'
map.right(map.left('N')) # => 'N'
So you can do %w[ N NE E SE S SW W NW ]
as well.
I think you can avoid all the work of creating new array each time you turn (by calling rotate
on it). Just store your current direction as an index of its letter in the array. Turning is just modular arithmetic on the index (note that in Ruby -1 % 4 == 3
). And when you want the letter of the direction, just get it from the array using the index.
class Direction
DIRECTIONS = %w[N E S W].freeze
def initialize(dir)
self.dir = dir
end
# dir getter
def dir
DIRECTIONS[@dir_index]
end
# dir setter
def dir=(dir)
@dir_index = DIRECTIONS.index(dir)
end
# turning logic
def turn(delta)
@dir_index = (@dir_index + delta) % DIRECTIONS.size
dir
end
def turn_left
turn(-1)
end
def turn_right
turn(1)
end
end
p Direction.new('N').turn_left #=> "W"
p Direction.new('E').turn_left #=> "N"
p Direction.new('S').turn_left #=> "E"
p Direction.new('W').turn_left #=> "S"
p Direction.new('N').turn_right #=> "E"
p Direction.new('E').turn_right #=> "S"
p Direction.new('S').turn_right #=> "W"
p Direction.new('W').turn_right #=> "N"
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.