How can I return the uncommon characters between two separate strings in Ruby?

Viewed 199

I've been working on a coding challenge to return the uncommon characters between two separate strings. However I haven't had much luck in figuring it out. I've tried a number of methods that I have found when googling, none have quite worked. I did however come across a hash mapping method but the examples only included C++, Python, C — seeing as Ruby is my first programming language it's been difficult trying to translate such a complex challenge without making mistakes.

I'm not in any rush to figure this coding challenge out and would rather appreciate getting some feedback on what everyone else thinks may be worth me reading into in order to approach this question successfully.

Here is my code currently, please don't think much of it, I know it's far off from what it should be:

# Find concatenated string  
# with uncommon characters of given strings
def solve(a,b)
   res = "" # result  
    map = {} 
  
# store all characters of b in map
  [a,b].each.map{| character, element | for characters(1..26) << element[+1]
 }
# Find characters of a that are not 
# present in b and append to result
    
# Find characters of b that are not  
# present in a
    
    return res
end

Here is an example test case of what I'm trying to do, click here for link to Kata challenge:

solve("xyab","xzca") = "ybzc" 
--The first string has 'yb' which is not in the second string. 
--The second string has 'zc' which is not in the first string. 
3 Answers

I'd treat strings as arrays of characters. You can easily find differences between arrays:

def solve(a, b)
  first_array = a.chars
  second_array = b.chars
  ((first_array - second_array) + (second_array - first_array)).uniq.join
end

Another approach is to use Set:

require 'set'

def solve(a, b)
  (b.chars.to_set ^ a.chars.to_set).to_a.join
end

UPD uniq was added to the first approach as @3limin4t0r suggested.

Similar to the answer of Yakov instead of adding the two differences together you could also subtract the intersection from the union between the two. (See symmetric difference)

def solve(a, b)
 a = a.chars
 b = b.chars

 ((a | b) - (a & b)).join
end

Both the union method | and intersection method & remove duplicates so there is no need to call uniq before joining.

Here is a way that does not convert the strings to arrays of characters.

def non_common_chars(str1, str2)
  g = str1.each_char.with_object({}) { |c,h| h[c] = 1 }
  str2.each_char.with_object(g) do |c,h|
    if h.key?(c)
      h.delete(c) if h[c] == 1
    else
      h[c] = 2
    end
  end.keys.join
end
non_common_chars("abc-defaa", "abcm.nopp")
  #=> "-defm.nop"

If desired, the hash g could be factored out.

The steps are as follows.

str1 = "abc-defaa"
str2 = "abcm.nopp"

g = str1.each_char.with_object({}) { |c,h| h[c] = 1 }
  #=> {"a"=>1, "b"=>1, "c"=>1, "-"=>1, "d"=>1, "e"=>1, "f"=>1}

The keys of g comprise the unique characters in str1

e0 = str2.each_char
  #=> #<Enumerator: "abcm.nopp":each_char> 
e1 = e0.with_object(g)
  #=> #<Enumerator: #<Enumerator: "abcm.nopp":each_char>:with_object(
  #     {"a"=>1, "b"=>1, "c"=>1, "-"=>1, "d"=>1, "e"=>1, "f"=>1})> 

h = e1.each do |c,h|
      if h.key?(c)
        h.delete(c) if h[c] == 1
      else
        h[c] = 2
      end
    end
  #=> {"-"=>1, "d"=>1, "e"=>1, "f"=>1, "m"=>2, "."=>2, "n"=>2,
  #    "o"=>2, "p"=>2} 

The keys of h are now the unique characters in str1 that are not in str2 and unique characters in str2 that are not in str1. Just two more steps:

a = h.keys
  #=> ["-", "d", "e", "f", "m", ".", "n", "o", "p"] 
a.join
  #=>"-defm.nop"
Related