Решение на Трета задача от Калоян Калудов

Обратно към всички решения

Към профила на Калоян Калудов

Резултати

  • 6 точки от тестове
  • 1 бонус точка
  • 7 точки общо
  • 67 успешни тест(а)
  • 2 неуспешни тест(а)

Код

module Graphics
class Point
attr_reader :x, :y
def initialize(x, y)
@x, @y = x, y
end
def draw_on(canvas)
canvas.set_pixel @x, @y
end
def eql?(other)
@x == other.x and @y == other.y
end
alias_method :==, :eql?
def hash
@x.hash + @y.hash
end
end
class Line
class BresenhamAlgorithm
def initialize(from, to)
prepare_coordinates_and_detect_steepness(from.x, from.y, to.x, to.y)
prepare_deltas_and_initial_values
end
def apply_on(canvas)
@steps.each do |x|
canvas.set_pixel (@steep ? @y : x), (@steep ? x : @y)
@error -= @delta_y
if @error < 0
@y += @y_step
@error += @delta_x
end
end
end
private
def prepare_coordinates_and_detect_steepness(from_x, from_y, to_x, to_y)
@steep = (to_y - from_y).abs > (to_x - from_x).abs
if @steep
@from_x, @from_y = from_y, from_x
@to_x, @to_y = to_y, to_x
else
@from_x, @from_y = from_x, from_y
@to_x, @to_y = to_x, to_y
end
end
def prepare_deltas_and_initial_values
@delta_x = (@to_x - @from_x).abs
@delta_y = (@to_y - @from_y).abs
@error = @delta_x / 2
@y_step = @from_y < @to_y ? 1 : -1
@y = @from_y
@steps = @from_x < @to_x ? @from_x.upto(@to_x) : @from_x.downto(@to_x)
end
end
attr_reader :from, :to
def initialize(from, to)
if from.x > to.x or (from.x == to.x and from.y > to.y)
@from, @to = to, from
else
@from, @to = from, to
end
end
def draw_on(canvas)
BresenhamAlgorithm.new(@to, @from).apply_on(canvas)
end
def eql?(other)
@from == other.from and @to == other.to
end
alias_method :==, :eql?
def hash
@from.hash + @to.hash
end
end
class Rectangle
attr_reader :left, :right
attr_reader :top_left, :top_right, :bottom_left, :bottom_right
def initialize(left, right)
diagonal = Line.new left, right
@left, @right = diagonal.from, diagonal.to
@top_left = Point.new [left.x, right.x].min, [left.y, right.y].min
@top_right = Point.new [left.x, right.x].max, [left.y, right.y].min
@bottom_left = Point.new [left.x, right.x].min, [left.y, right.y].max
@bottom_right = Point.new [left.x, right.x].max, [left.y, right.y].max
end
def draw_on(canvas)
[ Line.new(@top_left, @top_right),
Line.new(@bottom_left, @bottom_right),
Line.new(@top_left, @bottom_left),
Line.new(@top_right, @bottom_right)
].each { |line| line.draw_on canvas }
end
def eql?(other)
@left == other.left and @right == other.right
end
alias_method :==, :eql?
def hash
@left.hash + @right.hash
end
end
module Renderers
module Ascii
def self.render(canvas)
output_string = ''
canvas.each_pixel { |pixel| output_string += pixel ? '@' : '-' }
output_string.scan(/.{#{canvas.width * 1}}/).join("\n")
end
end
module Html
HEADER = '<!DOCTYPE html>
<html>
<head>
<title>Rendered Canvas</title>
<style type="text/css">
.canvas {
font-size: 1px;
line-height: 1px;
}
.canvas * {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 5px;
}
.canvas i {
background-color: #eee;
}
.canvas b {
background-color: #333;
}
</style>
</head>
<body>
<div class="canvas">'
FOOTER = '</div>
</body>
</html>'
def self.render(canvas)
output_string = ''
canvas.each_pixel { |pixel| output_string += pixel ? '<b></b>' : '<i></i>' }
HEADER + output_string.scan(/.{#{canvas.width * 7}}/).join("<br>\n") + FOOTER
end
end
end
class Canvas
attr_reader :width, :height
def initialize(width, height)
@width = width
@height = height
@canvas = Array.new(height) { Array.new(width, false) }
end
def set_pixel(x, y)
@canvas[y][x] = true
end
def pixel_at?(x, y)
@canvas[y][x]
end
def each_pixel
@canvas.each { |row| row.each { |pixel| yield pixel } }
end
def draw(figure)
figure.draw_on(self)
end
def render_as(renderer)
renderer.render(self)
end
end
end

Лог от изпълнението

..........F.................................................F........

Failures:

  1) Graphics Canvas drawing of shapes and rasterization of points works for multiple ones
     Failure/Error: canvas.set_pixel 4, 4
     NoMethodError:
       undefined method `[]=' for nil:NilClass
     # /tmp/d20131223-4637-s9sunz/solution.rb:185:in `set_pixel'
     # /tmp/d20131223-4637-s9sunz/spec.rb:57:in `block (5 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  2) Graphics shapes Rectangle comparison for equality is true for rectangles defined with different diagonal corners
     Failure/Error: (a == b).should be_true
       expected: true value
            got: false
     # /tmp/d20131223-4637-s9sunz/spec.rb:540:in `block (5 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

Finished in 0.08363 seconds
69 examples, 2 failures

Failed examples:

rspec /tmp/d20131223-4637-s9sunz/spec.rb:51 # Graphics Canvas drawing of shapes and rasterization of points works for multiple ones
rspec /tmp/d20131223-4637-s9sunz/spec.rb:536 # Graphics shapes Rectangle comparison for equality is true for rectangles defined with different diagonal corners

История (2 версии и 2 коментара)

Калоян обнови решението на 21.12.2013 00:25 (преди почти 11 години)

+module Graphics
+ class Point
+ attr_reader :x, :y
+
+ def initialize(x, y)
+ @x, @y = x, y
+ end
+
+ def draw_on(canvas)
+ canvas.set_pixel @x, @y
+ end
+
+ def eql?(other)
+ @x == other.x and @y == other.y
+ end
+
+ alias_method :==, :eql?
+
+ def hash()
+ @x.hash + @y.hash
+ end
+ end
+
+ class Line
+ class BresenhamAlgorithm
+ def initialize(from, to)
+ prepare_coordinates_and_steep(from.x, from.y, to.x, to.y)
+ prepare_run_time_variables()
+ end
+
+ def apply_on(canvas)
+ @steps.each do |x|
+ canvas.set_pixel (@steep ? @y : x), (@steep ? x : @y)
+ @error -= @delta_y
+ @y += @y_step if @error < 0
+ @error += @delta_x if @error < 0
+ end
+ end
+
+ private
+ def prepare_coordinates_and_steep(from_x, from_y, to_x, to_y)
+ @steep = (to_y - from_y).abs > (to_x - from_x).abs
+ if @steep
+ @from_x, @from_y, @to_x, @to_y = from_y, from_x, to_y, to_x
+ else
+ @from_x, @from_y, @to_x, @to_y = from_x, from_y, to_x, to_y
+ end
+ end
+
+ def prepare_run_time_variables()
+ @delta_x, @delta_y = (@to_x - @from_x).abs, (@to_y - @from_y).abs
+ @error = @delta_x / 2
+ @y_step = @from_y < @to_y ? 1 : -1
+ @y = @from_y
+ @steps = @from_x < @to_x ? @from_x.upto(@to_x) : @from_x.downto(@to_x)
+ end
+ end
+
+ attr_reader :from, :to
+
+ def initialize(from, to)
+ if from.x > to.x or (from.x == to.x and from.y > to.y)
+ @from, @to = to, from
+ else
+ @from, @to = from, to
+ end
+ end
+
+ def draw_on(canvas)
+ algorithm = BresenhamAlgorithm.new @to, @from
+ algorithm.apply_on canvas
+ end
+
+ def eql?(other)
+ @from == other.from and @to == other.to
+ end
+
+ alias_method :==, :eql?
+
+ def hash()
+ @from.hash + @to.hash
+ end
+ end
+
+ class Rectangle
+ attr_reader :left, :right
+ attr_reader :top_left, :top_right, :bottom_left, :bottom_right
+
+ def initialize(left, right)
+ diagonal = Line.new left, right
+ @left, @right = diagonal.from, diagonal.to
+ @top_left = Point.new [left.x, right.x].min, [left.y, right.y].min
+ @top_right = Point.new [left.x, right.x].max, [left.y, right.y].min
+ @bottom_left = Point.new [left.x, right.x].min, [left.y, right.y].max
+ @bottom_right = Point.new [left.x, right.x].max, [left.y, right.y].max
+ end
+
+ def draw_on(canvas)
+ Line.new(@top_left, @top_right).draw_on canvas
+ Line.new(@bottom_left, @bottom_right).draw_on canvas
+ Line.new(@top_left, @bottom_left).draw_on canvas
+ Line.new(@top_right, @bottom_right).draw_on canvas
+ end
+
+ def eql?(other)
+ @left == other.left and @right == other.right
+ end
+
+ alias_method :==, :eql?
+
+ def hash()
+ @left.hash + @right.hash
+ end
+ end
+
+ module Renderers
+ module Ascii
+ def self.render(canvas)
+ canvas.canvas.map do |row|
+ row.map { |filled| fill_to_string filled }.join
+ end.join("\n")
+ end
+
+ def self.fill_to_string(filled)
+ filled ? '@' : '-'
+ end
+ end
+
+ module Html
+ def self.render(canvas)
+ wrappers = DATA.read.split('PIXEL_DATA')
+ wrappers[0] + canvas.canvas.map do |row|
+ row.map { |filled| fill_to_string filled }.join
+ end.join("<br>\n") + wrappers[1]
+ end
+
+ def self.fill_to_string(filled)
+ filled ? '<b></b>' : '<i></i>'
+ end
+ end
+ end
+
+ class Canvas
+ attr_reader :width, :height, :canvas
+
+ def initialize(width, height)
+ @width = width
+ @height = height
+ @canvas = Array.new(height) { Array.new(width, false) }
+ end
+
+ def set_pixel(x, y)
+ @canvas[y][x] = true
+ end
+
+ def pixel_at?(x, y)
+ @canvas[y][x]
+ end
+
+ def draw(figure)
+ figure.draw_on(self)
+ end
+
+ def render_as(renderer)
+ renderer.render(self)
+ end
+ end
+end
+
+__END__
+
+<!DOCTYPE html>
+<html>
+<head>
+<title>Rendered Canvas</title>
+<style type="text/css">
+ .canvas {
+ font-size: 1px;
+ line-height: 1px;
+ }
+ .canvas * {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ border-radius: 5px;
+ }
+ .canvas i {
+ background-color: #eee;
+ }
+ .canvas b {
+ background-color: #333;
+ }
+</style>
+</head>
+<body>
+<div class="canvas">
+PIXEL_DATA
+</div>
+</body>
+</html>

Дизайнът ти ми харесва доста. Поздравления!

Малко бележки:

  • Пропускай скобите, когато дефинириаш или извикваш метод без аргументи.
  • Оставяй по един празен ред след private.
  • Бих написал Line#draw_on на един ред: BresenhamAlgorithm.new(@to, @from).apply_on(canvas).
  • prepare_coordinates_and_steepprepare_coordinates_and_detect_steepness.
  • prepare_run_time_variables също може да се подобри някак. Пробвай. На първо време ми идват наум неща като prepare_deltas_and_initial_values.
  • На методите в BresenhamAlgorithm им липсва малко вертикална подредба на операциите, за да се подобри четимостта. Същото важи и за някои други сходни по визуална "тежест" методи.
  • В BresenhamAlgorithm#apply_on може да обединиш ред 35 и 36 в един if, мисля.
  • Може би някой each в Rectangle#draw_on?
  • Помисли дали няма друг по-оптимален начин в Ruby за реализация вътрешното представяне на пано от масив от масиви. Ruby не е C и има по-удобни структури от данни :)
  • Потърси по-добро име за fill_to_string.
  • canvas.canvas ми намирисва. Не стои добре.
  • Освен това, имаш малко дублираща се логика в двата рендерера, свързана с обхождането на пикселите на паното, която зависи от вътрешното представяне на пано. Ако смениш вътрешното представяне на пано, ще трябва да промениш и тези два метода, а това не е добре. Опитай да ползваш нещо, което не зависи от това вътрешно представяне. Нещо, което ползва само публичния интерфейс на пано, дефиниран в условието, например.
  • Да сложиш HTML-а в DATA-зоната е интересна идея, но ми се струва, че тук ще е по-удачно да е в константа/и в класа Html.
  • Стреми се да викаш join винаги с аргументи, не разчитай на тези по подразбиране.

Калоян обнови решението на 21.12.2013 23:58 (преди почти 11 години)

module Graphics
class Point
attr_reader :x, :y
def initialize(x, y)
@x, @y = x, y
end
def draw_on(canvas)
canvas.set_pixel @x, @y
end
def eql?(other)
@x == other.x and @y == other.y
end
alias_method :==, :eql?
- def hash()
+ def hash
@x.hash + @y.hash
end
end
class Line
class BresenhamAlgorithm
def initialize(from, to)
- prepare_coordinates_and_steep(from.x, from.y, to.x, to.y)
- prepare_run_time_variables()
+ prepare_coordinates_and_detect_steepness(from.x, from.y, to.x, to.y)
+ prepare_deltas_and_initial_values
end
def apply_on(canvas)
@steps.each do |x|
canvas.set_pixel (@steep ? @y : x), (@steep ? x : @y)
+
@error -= @delta_y
- @y += @y_step if @error < 0
- @error += @delta_x if @error < 0
+
+ if @error < 0
+ @y += @y_step
+ @error += @delta_x
+ end
end
end
private
- def prepare_coordinates_and_steep(from_x, from_y, to_x, to_y)
+
+ def prepare_coordinates_and_detect_steepness(from_x, from_y, to_x, to_y)
@steep = (to_y - from_y).abs > (to_x - from_x).abs
if @steep
- @from_x, @from_y, @to_x, @to_y = from_y, from_x, to_y, to_x
+ @from_x, @from_y = from_y, from_x
+ @to_x, @to_y = to_y, to_x
else
- @from_x, @from_y, @to_x, @to_y = from_x, from_y, to_x, to_y
+ @from_x, @from_y = from_x, from_y
+ @to_x, @to_y = to_x, to_y
end
end
- def prepare_run_time_variables()
- @delta_x, @delta_y = (@to_x - @from_x).abs, (@to_y - @from_y).abs
+ def prepare_deltas_and_initial_values
+ @delta_x = (@to_x - @from_x).abs
+ @delta_y = (@to_y - @from_y).abs
+
@error = @delta_x / 2
+
@y_step = @from_y < @to_y ? 1 : -1
+
@y = @from_y
+
@steps = @from_x < @to_x ? @from_x.upto(@to_x) : @from_x.downto(@to_x)
end
end
attr_reader :from, :to
def initialize(from, to)
if from.x > to.x or (from.x == to.x and from.y > to.y)
@from, @to = to, from
else
@from, @to = from, to
end
end
def draw_on(canvas)
- algorithm = BresenhamAlgorithm.new @to, @from
- algorithm.apply_on canvas
+ BresenhamAlgorithm.new(@to, @from).apply_on(canvas)
end
def eql?(other)
@from == other.from and @to == other.to
end
alias_method :==, :eql?
- def hash()
+ def hash
@from.hash + @to.hash
end
end
class Rectangle
attr_reader :left, :right
attr_reader :top_left, :top_right, :bottom_left, :bottom_right
def initialize(left, right)
diagonal = Line.new left, right
@left, @right = diagonal.from, diagonal.to
@top_left = Point.new [left.x, right.x].min, [left.y, right.y].min
@top_right = Point.new [left.x, right.x].max, [left.y, right.y].min
@bottom_left = Point.new [left.x, right.x].min, [left.y, right.y].max
@bottom_right = Point.new [left.x, right.x].max, [left.y, right.y].max
end
def draw_on(canvas)
- Line.new(@top_left, @top_right).draw_on canvas
- Line.new(@bottom_left, @bottom_right).draw_on canvas
- Line.new(@top_left, @bottom_left).draw_on canvas
- Line.new(@top_right, @bottom_right).draw_on canvas
+ [ Line.new(@top_left, @top_right),
+ Line.new(@bottom_left, @bottom_right),
+ Line.new(@top_left, @bottom_left),
+ Line.new(@top_right, @bottom_right)
+ ].each { |line| line.draw_on canvas }
end
def eql?(other)
@left == other.left and @right == other.right
end
alias_method :==, :eql?
- def hash()
+ def hash
@left.hash + @right.hash
end
end
module Renderers
module Ascii
def self.render(canvas)
- canvas.canvas.map do |row|
- row.map { |filled| fill_to_string filled }.join
- end.join("\n")
+ output_string = ''
+ canvas.each_pixel { |pixel| output_string += pixel ? '@' : '-' }
+ output_string.scan(/.{#{canvas.width * 1}}/).join("\n")
end
-
- def self.fill_to_string(filled)
- filled ? '@' : '-'
- end
end
module Html
+ HEADER = '<!DOCTYPE html>
+ <html>
+ <head>
+ <title>Rendered Canvas</title>
+ <style type="text/css">
+ .canvas {
+ font-size: 1px;
+ line-height: 1px;
+ }
+ .canvas * {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ border-radius: 5px;
+ }
+ .canvas i {
+ background-color: #eee;
+ }
+ .canvas b {
+ background-color: #333;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="canvas">'
+ FOOTER = '</div>
+ </body>
+ </html>'
+
def self.render(canvas)
- wrappers = DATA.read.split('PIXEL_DATA')
- wrappers[0] + canvas.canvas.map do |row|
- row.map { |filled| fill_to_string filled }.join
- end.join("<br>\n") + wrappers[1]
+ output_string = ''
+ canvas.each_pixel { |pixel| output_string += pixel ? '<b></b>' : '<i></i>' }
+ HEADER + output_string.scan(/.{#{canvas.width * 7}}/).join("<br>\n") + FOOTER
end
-
- def self.fill_to_string(filled)
- filled ? '<b></b>' : '<i></i>'
- end
end
end
class Canvas
- attr_reader :width, :height, :canvas
+ attr_reader :width, :height
def initialize(width, height)
@width = width
@height = height
@canvas = Array.new(height) { Array.new(width, false) }
end
def set_pixel(x, y)
@canvas[y][x] = true
end
def pixel_at?(x, y)
@canvas[y][x]
end
+ def each_pixel
+ @canvas.each { |row| row.each { |pixel| yield pixel } }
+ end
+
def draw(figure)
figure.draw_on(self)
end
def render_as(renderer)
renderer.render(self)
end
end
-end
-
+end
-__END__
-
-<!DOCTYPE html>
-<html>
-<head>
-<title>Rendered Canvas</title>
-<style type="text/css">
- .canvas {
- font-size: 1px;
- line-height: 1px;
- }
- .canvas * {
- display: inline-block;
- width: 10px;
- height: 10px;
- border-radius: 5px;
- }
- .canvas i {
- background-color: #eee;
- }
- .canvas b {
- background-color: #333;
- }
-</style>
-</head>
-<body>
-<div class="canvas">
-PIXEL_DATA
-</div>
-</body>
-</html>

Не ми харесва как си имплементирал render методите си. Особено не на място ми изглежда появата на регулярен израз там.

Имам и други дребни забележки и има още места, които могат да се подобрят, но като цяло решението ти е прилично, Виж моето решение и решенията на колегите с 2-3 звезди (бонус точки) за справка. На теб ти давам една бонус точка.