4

I have a string "teststring". I want to find first non-recurring character in Ruby.

I have this Python code:

def first_non_repeat_character(teststring)
    unique=[]
    repeated=[]
    for character in teststring:
        if character in unique:
            unique.remove(character)
            repeated.append(character)
        else:
            if not character in repeated:
                unique.append(character)
    if len(unique):
        return unique[0]
    else: 
        return false
Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
vidal
  • 345
  • 3
  • 18
  • Fix your indentation, as of now, this is completely unreadable. This is not Ruby, this is attempt at Python. Also what do you mean by *first non repeating character*? A character which was not found so far? Or a character, not followed by itself (`aa`)? – ndnenkov Sep 30 '15 at 10:53
  • i am trying to find out character which was not found so far i know i did it in python.....as i am new to ruby i dont know how to do this in ruby – vidal Sep 30 '15 at 10:56

6 Answers6

7
def first_non_repeat_character(string)
  string.chars.find { |character| string.count(character) == 1 }
end

first_non_repeat_character('teststring') # => "e"
ndnenkov
  • 35,425
  • 9
  • 72
  • 104
  • +1, because I suspect this is the most performant answer so far because it relies on fast String methods rather than chained Hash/Array methods. – Todd A. Jacobs Sep 30 '15 at 11:47
  • 2
    @CodeGnome depending on the string's length and the position of the non-recurring character (if any), this approach traverses the string multiple times because of the nested `count`. – Stefan Sep 30 '15 at 12:02
1

That should do the trick:

def first_non_repeat_character(teststring)
  group = teststring.chars.group_by {|i| i}.find {|letter, group| group.one? }
  group && group.first
end

Since ruby 2.2.1 you can use group_by(&:itself)

BroiSatse
  • 44,031
  • 8
  • 61
  • 86
1

With a nod to @BroiSatse for the basic idea, I suggest the following variation in his answer for chaining in an irb or pry console if you have Ruby 2.2+ with the new Object#itself method:

'teststring'.chars.group_by(&:itself).values.select { |v| v.one? }.flatten.first
#=> "e"

The nice thing about this approach (as opposed to defining a method) is that it's easy to backtrack up the method chain for debugging. If you don't get the answer you expect, just keep removing the tail of the method chain until you find the problem.

Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
1

Another one:

'teststring'.each_char.with_object(Hash.new(0)) { |c, h| h[c] += 1 }.key(1)
#=> "e"

Step by step:

each_char traverses each character:

'teststring'.each_char.to_a
#=> ["t", "e", "s", "t", "s", "t", "r", "i", "n", "g"]

with_object passes a Hash with a default value of 0 to the block on order to count each character (returning the hash):

'teststring'.each_char.with_object(Hash.new(0)) { |c, h| h[c] += 1 }
#=> {"t"=>3, "e"=>1, "s"=>2, "r"=>1, "i"=>1, "n"=>1, "g"=>1}

key returns the key (i.e. character) for the given value (i.e. with a count of 1)

'teststring'.each_char.with_object(Hash.new(0)) { |c, h| h[c] += 1 }.key(1)
#=> "e"

Possible caveat: although the implementation returns the first key for the given value, the documentation says:

Returns the key of an occurrence of a given value.

Stefan
  • 109,145
  • 14
  • 143
  • 218
1

You could write:

str = 'teststring'

arr = str.chars
(arr - (arr.difference(arr.uniq))).first
  #= "e"

where Array#difference is defined in my answer here.

Array#difference is similar to Array#-. The difference is illustrated in the following example:

a = [1,2,3,4,3,2,2,4]
b = [2,3,4,4,4]

a     -      b #=> [1]
a.difference b #=> [1, 3, 2, 2]

arr.difference(arr.uniq) therefore returns an array of all elements e of arr for which arr.count(e) > 1.

arr.difference(arr.uniq) returns the recurring characters, so

arr - arr.difference(arr.uniq)

returns the non-recurring characters, ordered as they are ordered in arr.

Community
  • 1
  • 1
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
0

Here is a sample that takes into account if there are capital letters. For example if you pass in

"sTresst" => "r"

def  first_non_repeating_letter(s) 
  s.each_char do |char|
    return char if s.downcase.count(char.downcase) < 2
  end
  ""
end
tnaught
  • 301
  • 4
  • 13