1
\$\begingroup\$

Wrote this program out of curiosity after initially solving the Word Chains puzzle. Explanation of puzzle in link below.

http://rubyquiz.com/quiz44.html

NWordChains#find_word_chains will take in a number as an arg and return that amount of random word chains from a given dictionary. Initialize NWordChains.new and then run find_word_chains with the number of pairs that you would like returned.

My question is are there any quick glaring spots where this code could be DRYed up? Looking for any quick feedback, trying to learn how to think DRYily.

NOTE: You will need a dictionary.txt file of your own to run the code. I found one here if you'd like to use it: https://github.com/dwyl/english-words/blob/master/words.txt

require_relative "word_chains"
require 'set'

class NWordChains < WordChains
    
    def initialize
        words = File.readlines("dictionary.txt").map(&:chomp)
        @dictionary = Array.new(words)
        @all_paths = Hash.new { |h, k| h[k] = [] }
        @invalid_comps = Set.new
    end

    def find_word_chains(num)
        count = 0
        until count == num
            word_pair = two_rand_eql_length_words
      
            redo if invalid_pair?(word_pair)
                
                word1 = word_pair[0]
                word2 = word_pair[1]
                
                path_search = WordChains.new(word1, word2)
                
                if path_search.find_path == false
                    @invalid_comps << [ word1, word2 ]
                else
                    all_paths_key = "#{word1.upcase + ' ' + "to" + ' ' + word2.upcase}"
                    @all_paths[all_paths_key] << path_search.path
                    count += 1
                end
        end
        @all_paths
    end

    attr_reader :all_paths

    def two_rand_eql_length_words 
        word1 = @dictionary.sample
        narrowed_dict = self.narrow_dict_params(word1)
        word2 = narrowed_dict.sample

        [ word1, word2 ]
    end

    def invalid_pair?(word_pair)
        @invalid_comps.include?(word_pair) || 
        @invalid_comps.include?(word_pair.reverse)
    end

end

#----------------------------------------------------

class WordChains

    def initialize(beginning_word, target_word)
        @origin_word = beginning_word 
        @current_word = beginning_word
        @target_word = target_word  
        words = File.readlines("dictionary.txt").map(&:chomp)
        @dictionary = Array.new(words)
        @narrowed_dict = self.narrow_dict_params(@origin_word) 
        @path = [ @origin_word ]
    end

    attr_accessor :current_word, :target_word, :dictionary, :path, :dead_ends, :invalid_words
  
    def find_path
        return false if @origin_word == @target_word
        until @path.include?(@origin_word) && @path.include?(@target_word)
            valid_words = find_valid_words(@current_word) 
            if !valid_words.empty?
                find_and_move_to_next_word(@current_word, valid_words) 
            else  
                # In my version of the puzzle solution, if a dead end has been reached 
                # than the program assumes no path could be found.
                return false
            end
        end
        return false if @path.length == 2
        @path
    end

    def narrow_dict_params(origin_word)
        narrowed_dict = @dictionary.select { |word| word.length == origin_word.length }
        narrowed_dict
    end
    
    def find_and_move_to_next_word(current_word, valid_words)
        next_word = word_moving_towards_target(valid_words)
        @path << next_word
        @current_word = next_word
    end

    def word_moving_towards_target(valid_words)                           
        count_hash = Hash.new { |h, k| h[k] = 0 }

        valid_words.each do |word|
            count = 0
            (0...word.length).each do |i|
                count += 1 if word[i] == @target_word[i]
            end
            count_hash[word] += count
        end

        sorted_hash = count_hash.sort_by { |k, v| v }
        sorted_hash[-1][0]
    end

    def find_valid_words(current_word)
        valid_words = @narrowed_dict.select { |word| word if valid_word?(word) }
        valid_words 
    end

    def valid_word?(word)
        word != @current_word &&
        last_word_compatible?(word) && 
        target_compatible?(word) &&
        !path.include?(word)
    end

     def last_word_compatible?(word)
        count = 0 
        (0...word.length).each do |i|
            count += 1 if word[i] != @current_word[i]
        end

        return false if count > 1
        true
    end
    
    def target_compatible?(word)
        count = 0 
        (0...word.length).each do |i|
            count += 1 if word[i] == @target_word[i]
        end

        return true if count >= 1
        false
    end

end


\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.