0
votes

I'm taking a course in functional programming and coming from OOP my brain hurts trying to solve something that I think is quite trivial but I'm just not understanding the concept here. This is an exercise I need to do for school

Given a phrase, in my case it is

"Pattern Matching with Elixir. Remember that equals sign is a match operator, not an assignment"

I need to check starting letters of every word for a matching pattern and apply specific modification depending on pattern.

Im not even entirely sure what my current code is producing, when I inspect x and y I sort of understand what the loops are doing, they have a list for every word of phrase and that inner list consists of checks against every single letter, nil if it doesnt start with that letter and modified word if it does.

OOP in me wants those loops not to return any "nil" and only return a single edited word in every iteration. In functional programming I cant break loops and force returns so I need to think about this in another way.

My question is how should I approach this problem from functional programming perspective? In the beginning I get a list of words which form a phrase, then I wish to edit every word and in the end get a list again containing these edited words. Maybe a pseudo-code-like structure on how to tackle this would help me understand underlying concepts.

Here is my current code:

#Task two
def taskTwo() do
    IO.puts "Task Two"
    IO.puts "Pattern Matching with Elixir. Remember that equals sign is a match operator, not an assignment"
    IO.puts "Task Two\n...Editing Words ..."
    phrase = String.downcase("Pattern Matching with Elixir. Remember that equals sign is a match operator, not an assignment") |> String.split()
   
    x =  for word <- phrase do
        checkVowels(word)
    end
 
    y =  for word <- phrase do
        checkConsonants(word)
    end

    IO.inspect x
    IO.inspect y

end

#check vowels
def checkVowels(word) do
    vowels = ["a","e","i","o","u"]
    for vowel <- vowels do
        if String.starts_with?(word, vowel) do
           word <> "ay "
        end
    end
end

#check consonants
def checkConsonants(word) do
    consonants = ["b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","z","y"]        
    for consonant <- consonants do
        if String.starts_with?(word, consonant) do
           edited = String.replace_prefix(word, consonant, "")
           edited <> consonant <> "ay "
        end
    end
end

MOdifications I need to apply: First check for starting letter and apply modification, then check again an see if inside word there are any of the multiletter combinations

Words beginning with consonants should have the consonant moved to the end of the word, followed by "ay".
Words beginning with vowels (aeiou) should have "ay" added to the end of the word.
Some groups of letters are treated like consonants, including "ch", "qu", "squ", "th", "thr", and "sch".
Some groups are treated like vowels, including "yt" and "xr".
1
"I need to check starting letters of every word for a matching pattern and apply specific modification depending on pattern." What is the pattern and what modifications do you need to apply? - Adam Millerchip
@AdamMillerchip I edited original post, in the end is requirements. First I need to check starting letter and do the modification. This will be done to every word, but after first modification I need to check again if word contains any of the multiletter combinations and then do that one as well. - Jeekim
"I cant break loops" yes, that's one of the many reasons why typically in FP you don't use them. I don't know elixir to suggest an alternative, but would shocked if none existed. Again I don't know elixir, but the code you posted looks purely procedural, little to nothing functional about it. As far as I can tell you could translate this almost line for line into Python. Are you sure this how you're supposed to be solving this? - Jared Smith
@JaredSmith You are right! We did couple of lessons so far and before this assignment we covered pattern matching, those examples we worked are basically all as they are in Elixir getting started guide. Then we got the assignment, but Im still so stuck to OOP I dont understand how should I go about FP like. Im missing some fundamental concept or ideas here. - Jeekim
Part of the confusion might be due to the for/if syntaxes: for is actually more of a Enum.map/2 in disguise. So it will return a list with as many elements as the original enumerable (except if you filter). In the same way, if is always going to return a value, if you have no else it is going to be nil. I strongly recommend to get familiar with the Enum module which should provide you almost everything you need to replace typical "loops". In your case I think that Enum.find/2 is what you are looking for. - sabiwara

1 Answers

0
votes

One possible way (maybe not the most efficient, but using some of the Elixir powers) is to use recursion and pattern matching. So one possible way to solve the first part of your exercise.

defmodule Example do
  @vowels ~w[a e i o u y]
  @phrase "Pattern Matching with Elixir. Remember that equals sign is a match operator, not an assignment"

  def task_two() do
    phrase =
      @phrase
      |> String.downcase()
      |> String.split()
      |> Enum.reverse()
      # splitting the first letter of each word
      |> Enum.map(&String.split_at(&1, 1))

    # pass the phrase and an accumulator as arguments
    result = check_words(phrase, [])
    IO.inspect(result)
  end

  # using recursion to traverse the list

  # when the phrase have no more words, it will match the empty list
  def check_words([], phrase) do
    Enum.join(phrase, " ")
  end

  # pattern match on the first letter and using guards to decide if vowel
  def check_words([{first_letter, rest_of_word} | rest_of_list], accumulator)
      when first_letter in @vowels do
    # call the function again, with the rest of the list
    new_word = first_letter <> rest_of_word <> "ay"
    check_words(rest_of_list, [new_word | accumulator])
  end

  # when the pattern does not match for vowels, it should be a consonant

  def check_words([{first_letter, rest_of_word} | rest_of_list], accumulator) do
    new_word = rest_of_word <> first_letter <> "ay"
    check_words(rest_of_list, [new_word | accumulator])
  end
end

You will need to skip/handle the commas and possible some more tweaks to be fully functional. But hope it helps as a general idea.

Notes:

  1. write tests. They will really help you understand what the code is doing
  2. Elixir is an amazing language. If you come from OOP may look strange in the beginning. This book: Programming Elixir is really good and will help you a lot if you want to progress quickly.