Solution: Farmer Crosses River Puzzle
Discover how to solve the Farmer Crosses River puzzle using Ruby recursion and data structures. Learn how to track moves, apply constraints, and recursively backtrack to find the solution while understanding how to manipulate arrays and hashes in Ruby.
We'll cover the following...
We'll cover the following...
Solution
Ruby
def is_everyone_across?@item_positions.values.all? { |x| x == :crossed }end# farmer move by himself or take another one (only one)def move(item)if @item_positions[item] == :crossed@item_positions[:farmer] = @item_positions[item] = :not_crossedelse@item_positions[:farmer] = @item_positions[item] = :crossedend@direction = @direction == :forward ? :backward : :forwardenddef undo_move(item)if @item_positions[item] == :crossed@item_positions[:farmer] = @item_positions[item] = :not_crossedelse@item_positions[:farmer] = @item_positions[item] = :crossedend@direction = @direction == :forward ? :backward : :forwardenddef is_safe?# OK if farmer is thereitems_not_crossed = @item_positions.collect{ |x, y| x if y.to_s == "not_crossed" }.compactitems_crossed = @item_positions.collect{ |x, y| x if y.to_s == "crossed" }.compact[items_crossed, items_not_crossed].each do |items_at_one_side|# false if one side wolf and sheep# false if one side sheep and cabbagereturn false if items_at_one_side.include?(:sheep) && items_at_one_side.include?(:cabbage) && !items_at_one_side.include?(:farmer)return false if items_at_one_side.include?(:sheep) && items_at_one_side.include?(:wolf) && !items_at_one_side.include?(:farmer)endreturn trueenddef was_done_before?@moving_log.values.any?{ |x|x[:farmer] == @item_positions[:farmer] &&x[:sheep] == @item_positions[:sheep] &&x[:wolf] == @item_positions[:wolf] &&x[:cabbage] == @item_positions[:cabbage]}enddef is_item_with_farmer?(item)@item_positions[item] == @item_positions[:farmer]enddef print_moving_log@moving_log.keys.sort.each do |key|action_str = "Step #{key}"item_statuses = @moving_log[key]items_not_crossed = item_statuses.collect{ |x, y| x if y.to_s == "not_crossed" }.compactitems_crossed = item_statuses.collect{ |x, y| x if y.to_s == "crossed" }.compactif key > 0prev_statuses = @moving_log[key - 1]prev_not_crossed = prev_statuses.collect{ |x, y| x if y.to_s == "not_crossed" }.compactprev_crossed = prev_statuses.collect{ |x, y| x if y.to_s == "crossed" }.compactdiff_crossed = items_crossed - prev_crosseddiff_not_crossed = items_not_crossed - prev_not_crossedif diff_not_crossed.empty?action_str += " <= #{diff_crossed}"elseaction_str += " #{diff_not_crossed} =>"endendputs action_strformat_str = " #{items_crossed.inspect} |~~~| #{items_not_crossed.inspect}"puts format_strendend@item_positions = { :farmer => :not_crossed, :wolf => :not_crossed, :sheep => :not_crossed, :cabbage => :not_crossed }@items = [:farmer, :wolf, :sheep, :cabbage]@step = 1@direction = :forward@moving_log = { 0 => @item_positions.dup } # moving log starts with all in one sidedef crossif is_everyone_across?print_moving_logputs "Done!"return # exit out of recursionend@items.each do |item|next unless is_item_with_farmer?(item)# next if item == :farmer && @direction == :forwardmove(item)if is_safe? && !was_done_before?@moving_log[@step] = @item_positions.dup@step += 1cross(); # next step, recursiveelseundo_move(item); # "not safe, revert"endendendcross()
...
Ask