F=
->input{
 
rows = input.split'
'
node = Struct.new :incoming_connection_count, :next_node
 
nodes = Hash.new{|h,k|h[k] = node.new 0}
 
rows.size.times{|y|
  x=0
  rows[y].chars{|c|
    case c
    when ?>
      from = [y,x-4]
      to = [y,x+2]
    when ?<
      from = [y,x+4]
      to = [y,x-2]     
    when ?^
      from = [y+3,x]
      to = [y-1,x]
    when ?v
      from = [y-3,x]
      to = [y+1,x]
    end
 
    if from
      nodes[from].next_node = nodes[to]
      nodes[to].incoming_connection_count += 1
    end
 
    x+=1
  }
}
 
current = nodes.values.find{|n|n.incoming_connection_count < 1}
loop_start = nodes.values.find{|n|n.incoming_connection_count > 1}
 
unless loop_start
  # no loop; adjust the total node count
  [nodes.size-1, 0]
else
  # there's a loop; measure length of the tail
  tail = 0
  (tail+=1;current = current.next_node) while current!=loop_start
  [tail, nodes.size-tail]
end
 
}
 
require 'minitest/autorun'
 
describe F do
 
  def test_simple_case
    assert_equal [2, 6], F[<<-DOC]
# --> # --> # --> #
^     ^           |
|     |           |
|     |           v
#     # <-- # <-- #
DOC
  end
 
  def test_harder_case
    assert_equal [3, 10], F[<<-DOC]
            # --> # --> #
            ^           |
            |           |
            |           v
      # --> # <-- #     # --> #
      ^           ^           |
      |           |           |
      |           |           v
# --> #           # <-- # <-- #
DOC
  end
 
  def test_no_loop
    assert_equal [6, 0], F[<<-DOC]
# --> # --> # --> #
                  |
                  |
                  v
        <-- # <-- #
DOC
  end
 
end
				CkY9Ci0+aW5wdXR7Cgpyb3dzID0gaW5wdXQuc3BsaXQnCicKbm9kZSA9IFN0cnVjdC5uZXcgOmluY29taW5nX2Nvbm5lY3Rpb25fY291bnQsIDpuZXh0X25vZGUKCm5vZGVzID0gSGFzaC5uZXd7fGgsa3xoW2tdID0gbm9kZS5uZXcgMH0KCnJvd3Muc2l6ZS50aW1lc3t8eXwKICB4PTAKICByb3dzW3ldLmNoYXJze3xjfAogICAgY2FzZSBjCiAgICB3aGVuID8+CiAgICAgIGZyb20gPSBbeSx4LTRdCiAgICAgIHRvID0gW3kseCsyXQogICAgd2hlbiA/PAogICAgICBmcm9tID0gW3kseCs0XQogICAgICB0byA9IFt5LHgtMl0gICAgIAogICAgd2hlbiA/XgogICAgICBmcm9tID0gW3krMyx4XQogICAgICB0byA9IFt5LTEseF0KICAgIHdoZW4gP3YKICAgICAgZnJvbSA9IFt5LTMseF0KICAgICAgdG8gPSBbeSsxLHhdCiAgICBlbmQKCiAgICBpZiBmcm9tCiAgICAgIG5vZGVzW2Zyb21dLm5leHRfbm9kZSA9IG5vZGVzW3RvXQogICAgICBub2Rlc1t0b10uaW5jb21pbmdfY29ubmVjdGlvbl9jb3VudCArPSAxCiAgICBlbmQKCiAgICB4Kz0xCiAgfQp9CgpjdXJyZW50ID0gbm9kZXMudmFsdWVzLmZpbmR7fG58bi5pbmNvbWluZ19jb25uZWN0aW9uX2NvdW50IDwgMX0KbG9vcF9zdGFydCA9IG5vZGVzLnZhbHVlcy5maW5ke3xufG4uaW5jb21pbmdfY29ubmVjdGlvbl9jb3VudCA+IDF9Cgp1bmxlc3MgbG9vcF9zdGFydAogICMgbm8gbG9vcDsgYWRqdXN0IHRoZSB0b3RhbCBub2RlIGNvdW50CiAgW25vZGVzLnNpemUtMSwgMF0KZWxzZQogICMgdGhlcmUncyBhIGxvb3A7IG1lYXN1cmUgbGVuZ3RoIG9mIHRoZSB0YWlsCiAgdGFpbCA9IDAKICAodGFpbCs9MTtjdXJyZW50ID0gY3VycmVudC5uZXh0X25vZGUpIHdoaWxlIGN1cnJlbnQhPWxvb3Bfc3RhcnQKICBbdGFpbCwgbm9kZXMuc2l6ZS10YWlsXQplbmQKCn0KCnJlcXVpcmUgJ21pbml0ZXN0L2F1dG9ydW4nCgpkZXNjcmliZSBGIGRvCgogIGRlZiB0ZXN0X3NpbXBsZV9jYXNlCiAgICBhc3NlcnRfZXF1YWwgWzIsIDZdLCBGWzw8LURPQ10KIyAtLT4gIyAtLT4gIyAtLT4gIwpeICAgICBeICAgICAgICAgICB8CnwgICAgIHwgICAgICAgICAgIHwKfCAgICAgfCAgICAgICAgICAgdgojICAgICAjIDwtLSAjIDwtLSAjCkRPQwogIGVuZAoKICBkZWYgdGVzdF9oYXJkZXJfY2FzZQogICAgYXNzZXJ0X2VxdWFsIFszLCAxMF0sIEZbPDwtRE9DXQogICAgICAgICAgICAjIC0tPiAjIC0tPiAjCiAgICAgICAgICAgIF4gICAgICAgICAgIHwKICAgICAgICAgICAgfCAgICAgICAgICAgfAogICAgICAgICAgICB8ICAgICAgICAgICB2CiAgICAgICMgLS0+ICMgPC0tICMgICAgICMgLS0+ICMKICAgICAgXiAgICAgICAgICAgXiAgICAgICAgICAgfAogICAgICB8ICAgICAgICAgICB8ICAgICAgICAgICB8CiAgICAgIHwgICAgICAgICAgIHwgICAgICAgICAgIHYKIyAtLT4gIyAgICAgICAgICAgIyA8LS0gIyA8LS0gIwpET0MKICBlbmQKCiAgZGVmIHRlc3Rfbm9fbG9vcAogICAgYXNzZXJ0X2VxdWFsIFs2LCAwXSwgRls8PC1ET0NdCiMgLS0+ICMgLS0+ICMgLS0+ICMKICAgICAgICAgICAgICAgICAgfAogICAgICAgICAgICAgICAgICB8CiAgICAgICAgICAgICAgICAgIHYKICAgICAgICA8LS0gIyA8LS0gIwpET0MKICBlbmQKCmVuZA==