干这个ruby代码最好的办法是什么?
What is the best way to dry out this ruby code?
我想干掉这段代码。最好的解决方案是像 rails 中那样使用 before_action 方法吗?
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"
您可以:
freeze
避免修改的常量。
- 如果常量未在
Direction
class 之外使用,请更改常量的可见性。
- 如果您仅在 class 中使用它,请更改
dir
的可见性。
- 创建一个
OPERATIONS
散列,它定义方向和它将用于 return 下一个方向的操作。
- 遍历
OPERATIONS
键以动态定义方法 turn_left
和 turn_right
.
- 定义一个
direction_index
方法,return使用dir
在DIRECTIONS
中索引。
- 定义一个turn方法,接收一个
operation
参数:
- 使用
operation
你可以从 OPERATIONS
中获取操作,它告诉你如何旋转(正向或负向)。
- 将方法
-
或 +
应用于 direction_index
的结果,您将获得要旋转的参数。
- 之后您在
DIRECTIONS
上调用 rotate
并获取第一个元素。
您始终可以实现独立于状态的方向图:
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
您现在可以在何处导航任意罗盘映射:
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'
所以你也可以%w[ N NE E SE S SW W NW ]
。
我建议使用散列,主要是为了提高可读性。
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"
注:
NEXT_RIGHT
#=> {"W"=>"N", "S"=>"W", "E"=>"S", "N"=>"E"}
很多好的答案,但一个简单的直接解决方案是将两种方法之间共有的部分分解为一个 turn 方法,然后传入 1
或 -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
我认为你可以避免每次打开时都创建新数组的所有工作(通过调用 rotate
)。只需将您当前的方向存储为其字母在数组中的索引即可。车削只是索引上的模运算(注意在 Ruby -1 % 4 == 3
中)。当你想要方向的字母时,只需使用索引从数组中获取它。
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"
我想干掉这段代码。最好的解决方案是像 rails 中那样使用 before_action 方法吗?
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"
您可以:
freeze
避免修改的常量。- 如果常量未在
Direction
class 之外使用,请更改常量的可见性。 - 如果您仅在 class 中使用它,请更改
dir
的可见性。 - 创建一个
OPERATIONS
散列,它定义方向和它将用于 return 下一个方向的操作。 - 遍历
OPERATIONS
键以动态定义方法turn_left
和turn_right
. - 定义一个
direction_index
方法,return使用dir
在DIRECTIONS
中索引。 - 定义一个turn方法,接收一个
operation
参数:- 使用
operation
你可以从OPERATIONS
中获取操作,它告诉你如何旋转(正向或负向)。 - 将方法
-
或+
应用于direction_index
的结果,您将获得要旋转的参数。 - 之后您在
DIRECTIONS
上调用rotate
并获取第一个元素。
- 使用
您始终可以实现独立于状态的方向图:
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
您现在可以在何处导航任意罗盘映射:
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'
所以你也可以%w[ N NE E SE S SW W NW ]
。
我建议使用散列,主要是为了提高可读性。
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"
注:
NEXT_RIGHT
#=> {"W"=>"N", "S"=>"W", "E"=>"S", "N"=>"E"}
很多好的答案,但一个简单的直接解决方案是将两种方法之间共有的部分分解为一个 turn 方法,然后传入 1
或 -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
我认为你可以避免每次打开时都创建新数组的所有工作(通过调用 rotate
)。只需将您当前的方向存储为其字母在数组中的索引即可。车削只是索引上的模运算(注意在 Ruby -1 % 4 == 3
中)。当你想要方向的字母时,只需使用索引从数组中获取它。
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"