Решение на Трета задача от Красимира Божанова

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

Към профила на Красимира Божанова

Резултати

  • 6 точки от тестове
  • 0 бонус точки
  • 6 точки общо
  • 65 успешни тест(а)
  • 4 неуспешни тест(а)

Код

module Graphics
class Canvas
attr_reader :width, :height
def initialize(width, height)
@width = width
@height = height
@canvas = Array.new(height) { Array.new(width) {0} }
end
def set_pixel(x, y)
@canvas[y][x] = 1
end
def pixel_at?(x, y)
not @canvas[y][x].zero?
end
def draw(figure)
figure.pixels.each { |point| set_pixel point.x, point.y }
end
def render_as(renderer)
renderer_string = renderer::RENDER_TABLE[:start]
renderer_string += @canvas.map do |line|
line.map { |symbol| renderer::RENDER_TABLE[symbol] }.join
end.join(renderer::RENDER_TABLE[:line_separator])
renderer_string += renderer::RENDER_TABLE[:end]
end
end
class GeometryFigure
def ==(other_figure)
sufficient == other_figure.sufficient
end
def eql?(other_figure)
self == other_figure
end
def hash
sufficient.hash
end
end
class Point < GeometryFigure
attr_reader :x, :y
def initialize(x, y)
@x = x
@y = y
end
def sufficient
[x, y]
end
def <=>(other_point)
return -1 if x < other_point.x or (x == other_point.x and y < other_point.y)
return 0 if self == other_point
1
end
def pixels
[self]
end
end
class Line < GeometryFigure
attr_reader :from, :to
def initialize(first_end, second_end)
@from, @to = *[first_end, second_end].sort
end
def sufficient
[from, to]
end
def difference(axis)
(@from.public_send(axis) - @to.public_send(axis)).abs
end
def delta
[difference(:x), difference(:y)].map(&:to_r).sort.reduce(&:/)
end
def point_based_on_steep(x, y)
difference(:y) > difference(:x) ? Point.new(y, x) : Point.new(x, y)
end
def bresenham_generation(from, to, error, y)
from.x.upto(to.x).each_with_object([]) do |x, gen_pixels|
gen_pixels << (point_based_on_steep(x, y))
error += delta
if error >= 0.5
y += to.y <=> from.y
error -= 1.0
end
end
end
def pixels
from, to = sufficient.map { |point| point_based_on_steep(*point.sufficient) }.sort
bresenham_generation(from, to, 0, from.y)
end
end
class Rectangle < GeometryFigure
attr_reader :top_left, :top_right, :bottom_left, :bottom_right, :left, :right
def initialize(first_point, second_point)
@left, @right = [first_point, second_point].sort
@top_left, @bottom_left, @top_right, @bottom_right = *[first_point, second_point,
Point.new(first_point.x, second_point.y),
Point.new(second_point.x, first_point.y)].sort
end
def sufficient
[top_left, bottom_left, bottom_right, top_right]
end
def pixels
(sufficient << top_left).each_cons(2).each_with_object([]) do |points, gen_pixels|
gen_pixels.concat Line.new(*points).pixels
end
end
end
module Renderers
module Ascii
RENDER_TABLE = { 0 => '-', 1 => '@', :start => "",
:end => "", :line_separator => "\n" }
end
module Html
RENDER_TABLE = { 0 => '<i></i>', 1 => '<b></b>',
:start => <<START_HTML, :end => <<END_HTML, :line_separator => "<br>" }
<!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">
START_HTML
</div>
</body>
</html>
END_HTML
end
end
end

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

..........F.....F..FF................................................

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-l4qp9i/solution.rb:12:in `set_pixel'
     # /tmp/d20131223-4637-l4qp9i/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 Canvas drawing of shapes and rasterization of lines draws lines with two equal ends as points
     Failure/Error: canvas.draw make_line(make_point(1, 1), make_point(1, 1))
     ZeroDivisionError:
       divided by 0
     # /tmp/d20131223-4637-l4qp9i/solution.rb:85:in `/'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:85:in `each'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:85:in `reduce'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:85:in `delta'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:95:in `block in bresenham_generation'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:93:in `upto'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:93:in `each_with_object'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:93:in `bresenham_generation'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:105:in `pixels'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:20:in `draw'
     # /tmp/d20131223-4637-l4qp9i/spec.rb:143: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)>'

  3) Graphics Canvas drawing of shapes and rasterization of rectangles works with rects with a zero height as a line
     Failure/Error: canvas.draw make_rectangle(make_point(1, 1), make_point(8, 1))
     ZeroDivisionError:
       divided by 0
     # /tmp/d20131223-4637-l4qp9i/solution.rb:85:in `/'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:85:in `each'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:85:in `reduce'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:85:in `delta'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:95:in `block in bresenham_generation'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:93:in `upto'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:93:in `each_with_object'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:93:in `bresenham_generation'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:105:in `pixels'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:125:in `block in pixels'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:124:in `each'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:124:in `each_cons'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:124:in `each_with_object'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:124:in `pixels'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:20:in `draw'
     # /tmp/d20131223-4637-l4qp9i/spec.rb:182: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)>'

  4) Graphics Canvas drawing of shapes and rasterization of rectangles works with rects with a zero width and height as a single point
     Failure/Error: canvas.draw make_rectangle(make_point(1, 1), make_point(1, 1))
     ZeroDivisionError:
       divided by 0
     # /tmp/d20131223-4637-l4qp9i/solution.rb:85:in `/'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:85:in `each'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:85:in `reduce'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:85:in `delta'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:95:in `block in bresenham_generation'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:93:in `upto'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:93:in `each_with_object'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:93:in `bresenham_generation'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:105:in `pixels'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:125:in `block in pixels'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:124:in `each'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:124:in `each_cons'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:124:in `each_with_object'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:124:in `pixels'
     # /tmp/d20131223-4637-l4qp9i/solution.rb:20:in `draw'
     # /tmp/d20131223-4637-l4qp9i/spec.rb:193: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.08891 seconds
69 examples, 4 failures

Failed examples:

rspec /tmp/d20131223-4637-l4qp9i/spec.rb:51 # Graphics Canvas drawing of shapes and rasterization of points works for multiple ones
rspec /tmp/d20131223-4637-l4qp9i/spec.rb:141 # Graphics Canvas drawing of shapes and rasterization of lines draws lines with two equal ends as points
rspec /tmp/d20131223-4637-l4qp9i/spec.rb:180 # Graphics Canvas drawing of shapes and rasterization of rectangles works with rects with a zero height as a line
rspec /tmp/d20131223-4637-l4qp9i/spec.rb:191 # Graphics Canvas drawing of shapes and rasterization of rectangles works with rects with a zero width and height as a single point

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

Красимира обнови решението на 22.12.2013 14:27 (преди почти 11 години)

+module Graphics
+ class Canvas
+ attr_reader :width, :height
+
+ def initialize(width, height)
+ @width = width
+ @height = height
+ @canvas = Array.new(height) { Array.new(width) {0} }
+ end
+
+ def set_pixel(x, y)
+ @canvas[y][x] = 1
+ end
+
+ def pixel_at?(x, y)
+ not @canvas[y][x].zero?
+ end
+
+ def draw(figure)
+ figure.pixels.each { |point| set_pixel point.x, point.y }
+ end
+
+ def render_as(renderer)
+ renderer_string = renderer::RENDER_HASH[:start]
+ renderer_string += @canvas.map do |line|
+ line.map { |symbol| renderer::RENDER_HASH[symbol] }.join
+ end.join(renderer::RENDER_HASH[:line_separator])
+ renderer_string += renderer::RENDER_HASH[:end]
+ end
+ end
+
+ class GeometryFigure
+ def ==(other_figure)
+ sufficient == other_figure.sufficient
+ end
+
+ def eql?(other_figure)
+ self == other_figure
+ end
+
+ def hash
+ sufficient.hash
+ end
+ end
+
+ class Point < GeometryFigure
+ attr_reader :x, :y
+
+ def initialize(x, y)
+ @x = x
+ @y = y
+ end
+
+ def sufficient
+ [x, y]
+ end
+
+ def <=>(other_point)
+ return -1 if x < other_point.x or (x == other_point.x and y < other_point.y)
+ return 0 if self == other_point
+ 1
+ end
+
+ def pixels
+ [self]
+ end
+ end
+
+ class Line < GeometryFigure
+ attr_reader :from, :to
+
+ def initialize(first_end, second_end)
+ @from, @to = *[first_end, second_end].sort
+ end
+
+ def sufficient
+ [from, to]
+ end
+
+ def difference(axis)
+ (@from.public_send(axis) - @to.public_send(axis)).abs
+ end
+
+ def delta
+ [difference(:x), difference(:y)].map(&:to_r).sort.reduce(&:/)
+ end
+
+ def point_based_on_steep(x, y)
+ difference(:y) > difference(:x) ? Point.new(y, x) : Point.new(x, y)
+ end
+
+ def bresenham_generation(from, to, error, y)
+ from.x.upto(to.x).each_with_object([]) do |x, gen_pixels|
+ gen_pixels << (point_based_on_steep(x, y))
+ error += delta
+ if error >= 0.5
+ y += to.y <=> from.y
+ error -= 1.0
+ end
+ end
+ end
+
+ def pixels
+ from, to = sufficient.map { |point| point_based_on_steep(*point.sufficient) }.sort
+ bresenham_generation(from, to, 0, from.y)
+ end
+ end
+
+ class Rectangle < GeometryFigure
+ attr_reader :top_left, :top_right, :bottom_left, :bottom_right, :left, :right
+
+ def initialize(first_point, second_point)
+ @left, @right = [first_point, second_point].sort
+ @top_left, @bottom_left, @top_right, @bottom_right = *[first_point, second_point,
+ Point.new(first_point.x, second_point.y),
+ Point.new(second_point.x, first_point.y)].sort
+ end
+
+ def sufficient
+ [top_left, bottom_left, bottom_right, top_right]
+ end
+
+ def pixels
+ (sufficient << top_left).each_cons(2).each_with_object([]) do |points, gen_pixels|
+ gen_pixels.concat Line.new(*points).pixels
+ end
+ end
+ end
+
+ module Renderers
+ module Ascii
+ RENDER_HASH = { 0 => '-', 1 => '@', :start => "",
+ :end => "", :line_separator => "\n" }
+ end
+
+ module Html
+ RENDER_HASH = { 0 => '<i></i>', 1 => '<b></b>',
+ :start => <<START_HTML, :end => <<END_HTML, :line_separator => "<br>" }
+<!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">
+START_HTML
+ </div>
+</body>
+</html>
+END_HTML
+ end
+ end
+end

Красимира обнови решението на 22.12.2013 14:28 (преди почти 11 години)

module Graphics
class Canvas
attr_reader :width, :height
def initialize(width, height)
@width = width
@height = height
@canvas = Array.new(height) { Array.new(width) {0} }
end
def set_pixel(x, y)
@canvas[y][x] = 1
end
def pixel_at?(x, y)
not @canvas[y][x].zero?
end
def draw(figure)
figure.pixels.each { |point| set_pixel point.x, point.y }
end
def render_as(renderer)
- renderer_string = renderer::RENDER_HASH[:start]
+ renderer_string = renderer::RENDER_TABLE[:start]
renderer_string += @canvas.map do |line|
- line.map { |symbol| renderer::RENDER_HASH[symbol] }.join
- end.join(renderer::RENDER_HASH[:line_separator])
- renderer_string += renderer::RENDER_HASH[:end]
+ line.map { |symbol| renderer::RENDER_TABLE[symbol] }.join
+ end.join(renderer::RENDER_TABLE[:line_separator])
+ renderer_string += renderer::RENDER_TABLE[:end]
end
end
class GeometryFigure
def ==(other_figure)
sufficient == other_figure.sufficient
end
def eql?(other_figure)
self == other_figure
end
def hash
sufficient.hash
end
end
class Point < GeometryFigure
attr_reader :x, :y
def initialize(x, y)
@x = x
@y = y
end
def sufficient
[x, y]
end
def <=>(other_point)
return -1 if x < other_point.x or (x == other_point.x and y < other_point.y)
return 0 if self == other_point
1
end
def pixels
[self]
end
end
class Line < GeometryFigure
attr_reader :from, :to
def initialize(first_end, second_end)
@from, @to = *[first_end, second_end].sort
end
def sufficient
[from, to]
end
def difference(axis)
(@from.public_send(axis) - @to.public_send(axis)).abs
end
def delta
[difference(:x), difference(:y)].map(&:to_r).sort.reduce(&:/)
end
def point_based_on_steep(x, y)
difference(:y) > difference(:x) ? Point.new(y, x) : Point.new(x, y)
end
def bresenham_generation(from, to, error, y)
from.x.upto(to.x).each_with_object([]) do |x, gen_pixels|
gen_pixels << (point_based_on_steep(x, y))
error += delta
if error >= 0.5
y += to.y <=> from.y
error -= 1.0
end
end
end
def pixels
from, to = sufficient.map { |point| point_based_on_steep(*point.sufficient) }.sort
bresenham_generation(from, to, 0, from.y)
end
end
class Rectangle < GeometryFigure
attr_reader :top_left, :top_right, :bottom_left, :bottom_right, :left, :right
def initialize(first_point, second_point)
@left, @right = [first_point, second_point].sort
@top_left, @bottom_left, @top_right, @bottom_right = *[first_point, second_point,
Point.new(first_point.x, second_point.y),
Point.new(second_point.x, first_point.y)].sort
end
def sufficient
[top_left, bottom_left, bottom_right, top_right]
end
def pixels
(sufficient << top_left).each_cons(2).each_with_object([]) do |points, gen_pixels|
gen_pixels.concat Line.new(*points).pixels
end
end
end
module Renderers
module Ascii
- RENDER_HASH = { 0 => '-', 1 => '@', :start => "",
+ RENDER_TABLE = { 0 => '-', 1 => '@', :start => "",
:end => "", :line_separator => "\n" }
end
module Html
- RENDER_HASH = { 0 => '<i></i>', 1 => '<b></b>',
+ RENDER_TABLE = { 0 => '<i></i>', 1 => '<b></b>',
:start => <<START_HTML, :end => <<END_HTML, :line_separator => "<br>" }
<!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">
START_HTML
</div>
</body>
</html>
END_HTML
end
end
end

Дизайнът ти е в правилна посока. Следваш перфектно конвенциите и правилата за идентация. Ето малко бележки за неща, които би могла да подобриш:

  • Ако трябваше да запиша ред 8, бих сложил интервали около нулата, така: { 0 }. Бих подравнил и равенствата в този метод, както и на други сходни места.
  • Помисли дали няма друг по-оптимален начин в Ruby за реализация вътрешното представяне на пано, от масив от масиви. Ruby не е C и има по-удобни структури от данни, за които дори няма нужда да require-ваш нищо :)
  • Тъй като pixel_at? може да връща нещо, което се оценява като истина или лъжа и не е задължително да е true/false, помисли дали най-правилният избор за представяне на "има пиксел"/"няма пиксел" са 1 и 0.
  • На ред 20 може да се възползваш от начина, по който аргументи на блок могат да се "разпаднат", т.е. да напишеш figure.pixels.each { |x, y| set_pixel x, y }.
  • Помисли кой трбява да носи отговорността за "рендериране" на пано. Самото пано или рендерера? Помисли кой обект каква отговорност носи. Една основна идея на ОО-програмирането е да разпределиш различните отговорности между различни обекти. Стреми се всеки обект да отговаря за едно конкретно нещо и само за него. Напълно нормално е да имаш голям брой малки обекти в системата, с тясно специализирани отговорности. В момента класовете ти Html и Ascii ги ползваш просто като "кофи" за данни.
  • Ако изобщо трябва да я има променливата render_string, не е ли по-добре да се казва rendered_string?
  • GeometryFigure vs. Figure?
  • А какво трябва да означава "sufficient"? Не ми е достатъчно ясно това име.
  • Само да уточня, че условието не изисква да дефинираш метода <=>. Не го и забранява, разбира се. Аз бих го написал с if/elsif/else. Също така, когато дефинираш този метод, обикновено правиш и include Comparable, за да получиш купчина други методи. Виж документацията на Comparable за детайли.
  • На ред 73 няма нужда от звездичката.
  • point_based_on_steeppoint_based_on_steepness?
  • bresenham_generationbresenham_rasterization?
  • Бих направил горните два метода private. Както и някои други, които не би трябвало да са част от публичния интерфейс на Line.
  • Харесва ми как ти се е получила сметката за върховете на правоъгълник, благодарение на <=>. Аз бих го форматирал така:

      @top_left, @bottom_left, @top_right, @bottom_right = [
        first_point, second_point,
        Point.new(first_point.x, second_point.y),
        Point.new(second_point.x, first_point.y),
      ].sort
    
  • Методът pixels на правоъгълник ми е някак твърде сложен. Защо просто не дефинираш експлицитно четирите линии? Въпреки, че има малко дублиране на код, може би ще е далеч по-ясно какво се случва.

Благодаря много за коментара, но за съжаление нямах възможност да отделя време вчера... Иначе методът sufficient трябва да представлява необходимото и достатъчно да се идентифицира и отличи един геометричен обект (на правоъгълника е с 4те върха, а не с 2 срещуположни, за да се различава от линия при генериране на hash) :)

Аз много добре виждам какво прави този метод и каква му е логиката, въпросът е, че именуването му ми се струва неподходящо. Поне да беше нещо от рода на sufficient_identification, макар че съм сигурен, че може да се измисли и по-добро име...