Find if all letters in a string are unique - ruby

I need to know if all letters in a string are unique. For a string to be unique, a letter can only appear once. If all letters in a string are distinct, the string is unique. If one letter appears multiple times, the string is not unique.
"Cwm fjord veg balks nth pyx quiz."
# => All 26 letters are used only once. This is unique
"This is a string"
# => Not unique, i and s are used more than once
"two"
# => unique, each letter is shown only once
I tried writing a function that determines whether or not a string is unique.
def unique_characters(string)
for i in ('a'..'z')
if string.count(i) > 1
puts "This string is unique"
else
puts "This string is not unique"
end
end
unique_characters("String")
I receive the output
"This string is unique" 26 times.
Edit:
I would like to humbly apologize for including an incorrect example in my OP. I did some research, trying to find pangrams, and assumed that they would only contain 26 letters. I would also like to thank you guys for pointing out my error. After that, I went on wikipedia to find a perfect pangram (I wrongly thought the others were perfect).
Here is the link for reference purposes
http://en.wikipedia.org/wiki/List_of_pangrams#Perfect_pangrams_in_English_.2826_letters.29
Once again, my apologies.

s = "The quick brown fox jumps over the lazy dog."
.downcase
("a".."z").all?{|c| s.count(c) <= 1}
# => false
Another way to do it is:
s = "The quick brown fox jumps over the lazy dog."
(s.downcase !~ /([a-z]).*\1/)
# => false

I would solve this in two steps: 1) extract the letters 2) check if there are duplicates:
letters = string.scan(/[a-z]/i) # append .downcase to ignore case
letters.length == letters.uniq.length

Here is a method that does not convert the string to an array:
def dupless?(str)
str.downcase.each_char.with_object('') { |c,s|
c =~ /[a-z]/ && s.include?(c) ? (return false) : s << c }
true
end
dupless?("Cwm fjord veg balks nth pyx quiz.") #=> true
dupless?("This is a string.") #=> false
dupless?("two") #=> true
dupless?("Two tubs") #=> false

If you want to actually keep track of the duplicate characters:
def is_unique?(string)
# Remove whitespaces
string = string.gsub(/\s+/, "")
# Build a hash counting all occurences of each characters
h = Hash.new { |hash, key| hash[key] = 0 }
string.chars.each { |c| h[c] += 1 }
# An array containing all the repetitions
res = h.keep_if {|k, c| c > 1}.keys
if res.size == 0
puts "All #{string.size} characters are used only once. This is unique"
else
puts "Not unique #{res.join(', ')} are used more than once"
end
end
is_unique?("This is a string") # Not unique i, s are used more than once
is_unique?("two") # All 3 characters are used only once. This is unique

To check if a string is unique or not, you can try out this:
string_input.downcase.gsub(/[^a-z]/, '').split("").sort.join('') == ('a' .. 'z').to_a.join('')
This will return true, if all the characters in your string are unique and if they include all the 26 characters.

def has_uniq_letters?(str)
letters = str.gsub(/[^A-Za-z]/, '').chars
letters == letters.uniq
end
If this doesn't have to be case sensitive,
def has_uniq_letters?(str)
letters = str.downcase.gsub(/[^a-z]/, '').chars
letters == letters.uniq
end

In your example, you mentioned you wanted additional information about your string (list of unique characters, etc), so this example may also be useful to you.
# s = "Cwm fjord veg balks nth pyx quiz."
s = "This is a test string."
totals = Hash.new(0)
s.downcase.each_char { |c| totals[c] += 1 if ('a'..'z').cover?(c) }
duplicates, uniques = totals.partition { |k, v| v > 1 }
duplicates, uniques = Hash[duplicates], Hash[uniques]
# duplicates = {"t"=>4, "i"=>3, "s"=>4}
# uniques = {"h"=>1, "a"=>1, "e"=>1, "r"=>1, "n"=>1, "g"=>1}

Related

Ruby - Remove 1 or 2 character from a string to make it a palindrome

I had a technical test for an entry-level job 2 days ago. It went well apart from the last assessment.
I will go over the assessment with the CTO tomorrow and was hoping I could get help to get my head around this one, so I do not sound clueless.
It was something like this:
Given string as an argument, give us palindrome method that would check if a palindrome of a minimum 3 characters can be created by removing 1 or 2 characters. The string has no special characters, numbers or whitespace basically only letters (eg: str = "abecbea")
If true, print letters removed, if false print "not possible"
"remove 1 or 2 characters" and "print letters removed" is giving me a headache legit
I have tried a lot of different things but for the last 2 days but i am completely stuck!
[EDIT]
Ideas i started with below
def is_palindrome(s)
s == s.reverse && s.length >= 3
end
def possible_palin_by_removing_one_char(s)
array_chars = s.chars
first_char = array_chars[0]
last_char = array_chars[-1]
while first_char < last_char
if first_char != last_char
first_char += 1
last_char -= 1
else
if is_palindrome
puts ????
else
puts "not possible"
end
end
end
end
or
def palindrome?(string)
deleted_chars = []
candidates = 0.upto(string.length-1).map do |index|
array_chars = string.chars
deleted_chars << array_chars.delete_at(index)
array_chars
end
if candidates.any? { |c| c.reverse == c } && string.length >= 3
puts "It is a palindrome with letters '#{deleted_chars.join(",")}' removed !"
# puts deleted_chars
else
if string.length <= 3
puts "String '#{string}' is too short"
else
puts "This is not possible to create palindrome with string '#{string}'"
end
end
end
palindrome?("abcecbae")
I would love someone to help me solve this one
Thanks heaps for your help
Put all chars in an Array (ar = str.chars)
Try all combinations which are 2 less than
the size of the array (so 5 in the example "abcecbae")(ar.combination(5))
Select all combinations which happen to be equal to their reverse
Map the result(s) back from arrays to strings
Similar for 1 removed char.
This task might be a little bit trickier without Ruby's rich standard library support.
Here is how I'd solve this task in the 1st iteration:
Check if the original string is a palindrome, return immediately if it is
Build a list of possible combinations of indices to remove
Iterate over this list, check if removing the chars at given indices makes our input string a palindrome
The trickiest part here is (2), but Enumerable#permutation almost does the job.
So, something like
def palindrome?(s)
s == s.reverse
end
def indices_to_remove(string)
ary = (0..string.size-1).to_a
# Permutations like [0,1] and [1,0] means the same in our context, so we need to remove this duplication.
(ary.map { |x| [x] } + ary.permutation(2).map(&:sort)).uniq
end
def remove_chars(str, *indices)
str.dup.tap do |s|
indices.sort.reverse_each { |i| s[i] = '' }
end
end
def find_palindrome(str)
return if palindrome?(str)
indices_to_remove(str).each do |indices|
new_str = remove_chars(str, *indices)
return "Letters removed: #{indices.inspect}; palindrome: '#{new_str}'" if palindrome?(new_str)
end
end
That's it. Let's see if i works:
pry(main)> find_palindrome("abecbea")
=> "Letters removed: [1, 3]; palindrome: 'aebea'"
It does. Adjust the filtering logic (check palindroms' size as needed) and output if necessary.
Thanks all for your help. With the help of a senior dev, we came up with the below solution
def palindrome_check(str)
length = str.length
# convert to array of chars
chars = str.chars
# check if palindrome by deleting 1 characters
length.times do |i|
char = chars.delete_at(i)
return "Letter '#{char}' has been removed from String '#{str}' to create palindrome" if chars == chars.reverse
# return the array to original condition
chars.insert(i, char)
end
# only do remove 2 characters check if length > 4, as otherwise removing would result in a string less than 3 characters
if length > 4
length.times do |i|
length.times do |j|
# avoid repeating the same checks
next if j <= i
# since j is always greater than i, remove j first to avoid affecting which character is at position i
char_two = chars.delete_at(j)
char_one = chars.delete_at(i)
return "Letters '#{[char_one, char_two].join(' and ')}' has been removed from String '#{str}' to create palindrome" if chars == chars.reverse
# return the array to original condition
chars.insert(i, char_one)
chars.insert(j, char_two)
end
end
end
return "'#{str}' can't be a Palindrome"
end
palindrome_check('string') # 'can't be a Palindrome'
palindrome_check('abcdcfba') # 'f'
palindrome_check('rqubyxyburx') # 'q, x'

Having trouble adding new elements to my hash (Ruby)

new to Ruby, new to coding in general...
I'm trying to add new elements into my hash, incrementing the value when necessary. So I used Hash.new(0) and I'm trying to add new values using the "+=" symbol, but when I do this I get an error message -
"/tmp/file.rb:6:in `+': String can't be coerced into Integer (TypeError)
from /tmp/file.rb:6:in `block in stockList'
from /tmp/file.rb:3:in `each'
from /tmp/file.rb:3:in `each_with_index'
from /tmp/file.rb:3:in `stockList'
from /tmp/file.rb:24:in `<main>'
"
Here's my code:
def stockList(stock, cat)
hash = Hash.new(0)
stock.each_with_index do |word, i|
if cat.include?(word[i])
char = word[i]
hash[char] += num(word)
end
end
new_arr = []
hash.each do |k, v|
new_arr.push(k,v)
end
return new_arr
end
def num(word)
nums = "1234567890"
word.each_char.with_index do |char, i|
if nums.include?(char)
return word[i..-1]
end
end
end
puts stockList(["ABAR 200", "CDXE 500", "BKWR 250", "BTSQ 890", "DRTY 600"], ["A", "B"])
Does anyone know why this is happening?
It's a codewars challenge -- I'm basically given two arrays and am meant to return a string that adds the numbers associated with the word that starts with the letter(s) listed in the second array.
For this input I'm meant to return " (A : 200) - (B : 1140) "
Your immediate problem is that num(word) returns a string, and a string can't be added to a number in the line hash[char] += num(word). You can convert the string representation of a numeric value using .to_i or .to_f, as appropriate for the problem.
For the overall problem I think you've added too much complexity. The structure of the problem is:
Create a storage object to tally up the results.
For each string containing a stock and its associated numeric value (price? quantity?), split the string into its two tokens.
If the first character of the stock name is one of the target values,
update the corresponding tally. This will require conversion from string to integer.
Return the final tallies.
One minor improvement is to use a Set for the target values. That reduces the work for checking inclusion from O(number of targets) to O(1). With only two targets, the improvement is negligible, but would be useful if the list of stocks and targets increase beyond small test-case problems.
I've done some renaming to hopefully make things clearer by being more descriptive. Without further ado, here it is in Ruby:
require 'set'
def get_tallies(stocks, prefixes)
targets = Set.new(prefixes) # to speed up .include? check below
tally = Hash.new(0)
stocks.each do |line|
name, amount = line.split(/ +/) # one or more spaces is token delimiter
tally[name[0]] += amount.to_i if targets.include?(name[0]) # note conversion to int
end
tally
end
stock_list = ["ABAR 200", "CDXE 500", "BKWR 250", "BTSQ 890", "DRTY 600"]
prefixes = ["A", "B"]
p get_tallies(stock_list, prefixes)
which prints
{"A"=>200, "B"=>1140}
but that can be formatted however you like.
The particular issue triggering this error is that your def num(word) is essentially a no-op, returning the word without any change.
But you actually don't need this function: this...
word.delete('^0-9').to_i
... gives you back the word with all non-digit characters stripped, cast to integer.
Note that without to_i you'll still receive the "String can't be coerced into Integer" error: Ruby is not as forgiving as JavaScript, and tries to protect you from results that might surprise you.
It's a codewars challenge -- I'm basically given two arrays and am
meant to return a string that adds the numbers associated with the
word that starts with the letter(s) listed in the second array.
For this input I'm meant to return " (A : 200) - (B : 1140) "
This is one way to get there:
def stockList(stock, cat)
hash = Hash.new(0)
stock.each do |word|
letter = word[0]
if cat.include?(letter)
hash[letter] += word.delete('^0-9').to_i
end
end
hash.map { |k, v| "#{k}: #{v}" }
end
Besides type casting, there's another difference here: always choosing the initial letter of the word. With your code...
stock.each_with_index do |word, i|
if cat.include?(word[i])
char = word[i]
... you actually took the 1st letter of the 1st ticker, the 2nd letter of the 2nd ticker and so on. Don't use indexes unless your results depend on them.
stock = ["ABAR 200", "CDXE 500", "BKWR 250", "BTSQ 890", "DRTY 600"]
cat = ["A", "B"]
I concur with your decision to create a hash h with the form of Hash::new that takes an argument (the "default value") which h[k] returns when h does not have a key k. As a first step we can write:
h = stock.each_with_object(Hash.new(0)) { |s,h| h[s[0]] += s[/\d+/].to_i }
#=> {"A"=>200, "C"=>500, "B"=>1140, "D"=>600}
Then Hash#slice can be used to extract the desired key-value pairs:
h = h.slice(*cat)
#=> {"A"=>200, "B"=>1140}
At this point you have all the information you need to display the result any way you like. For example,
" " << h.map { |k,v| "(#{k} : #{v})" }.join(" - ") << " "
#=> " (A : 200) - (B : 1140) "
If h before h.slice(*cat) is large relative to h.slice(*cat) you can reduce memory requirements and probably speed things somewhat by writing the following.
require 'set'
cat_set = cat.to_set
#=> #<Set: {"A", "B"}>
h = stock.each_with_object(Hash.new(0)) do |s,h|
h[s[0]] += s[/\d+/].to_i if cat_set.include?(s[0])
end
#=> {"A"=>200, "B"=>1140}

Counting all occurrences of digits in a string

You are given a string S. Count the number of occurrences of all the digits in the string S.
Input:
First line contains string S
Output:
Count the number of occurences of 1 in the entered number and print the output.
I tried to attempt the given problem like this:
number=$stdin.gets.chomp
number.split('').map(&:to_i)
number.each do |numbers|
i==0
while numbers===1
i+=1
end
end
puts i
But, it's not executing.
Can someone please suggest how may I do it?
A clean way to do what you want in ruby
number=$stdin.gets.chomp
number.count('1')
str = "1a2b &32T2*3"
You can count the total number of digits in the string like so:
str.count('0123456789')
#=> 6
If you wish to count the number of occurrences of each digit in the string, you could use a counting hash (see Hash::new):
str.each_char.with_object(Hash.new(0)) { |c,h| h[c] += 1 if c =~ /\d/ }
#=> {"1"=>1, "2"=>3, "3"=>2}
or use the method Enumerable#group_by:
h = str.each_char.group_by(&:itself).select { |k,_| k =~ /\d/ }
#=> {"1"=>["1"], "2"=>["2", "2", "2"], "3"=>["3", "3"]}
h.update(h) { |*,v| v.count }
#=> {"1"=>1, "2"=>3, "3"=>2}
This uses the form of Hash#update (aka merge!) that employs a block to determine the values of keys that are present in both hashes being merged (here all keys). See the doc for details.
There were several problems with your code. First you didn't actually set numbers to be an array, you can do that by saying numbers = numbers.split('').map(&:to_i)
Then you should declare the i variable outside of the loop. Instead of saying i == 0, use i = 0 for the assignment operator
Instead of using a while loop, you should use an if statement.
This code is assuming your string is only all numbers.
numbers = $stdin.gets.chomp
numbers = numbers.split('').map(&:to_i)
i = 0
numbers.each do |number|
if number === 1
i += 1
end
end
If your string consists of letters, numbers or special characters, you could do this
numbers = $stdin.gets.chomp
numbers.split('').select { |c| c=~ /1/ }.length

Check whether a string contains all the characters of another string in Ruby

Let's say I have a string, like string= "aasmflathesorcerersnstonedksaottersapldrrysaahf". If you haven't noticed, you can find the phrase "harry potter and the sorcerers stone" in there (minus the space).
I need to check whether string contains all the elements of the string.
string.include? ("sorcerer") #=> true
string.include? ("harrypotterandtheasorcerersstone") #=> false, even though it contains all the letters to spell harrypotterandthesorcerersstone
Include does not work on shuffled string.
How can I check if a string contains all the elements of another string?
Sets and array intersection don't account for repeated chars, but a histogram / frequency counter does:
require 'facets'
s1 = "aasmflathesorcerersnstonedksaottersapldrrysaahf"
s2 = "harrypotterandtheasorcerersstone"
freq1 = s1.chars.frequency
freq2 = s2.chars.frequency
freq2.all? { |char2, count2| freq1[char2] >= count2 }
#=> true
Write your own Array#frequency if you don't want to the facets dependency.
class Array
def frequency
Hash.new(0).tap { |counts| each { |v| counts[v] += 1 } }
end
end
I presume that if the string to be checked is "sorcerer", string must include, for example, three "r"'s. If so you could use the method Array#difference, which I've proposed be added to the Ruby core.
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
str = "aasmflathesorcerersnstonedksaottersapldrrysaahf"
target = "sorcerer"
target.chars.difference(str.chars).empty?
#=> true
target = "harrypotterandtheasorcerersstone"
target.chars.difference(str.chars).empty?
#=> true
If the characters of target must not only be in str, but must be in the same order, we could write:
target = "sorcerer"
r = Regexp.new "#{ target.chars.join "\.*" }"
#=> /s.*o.*r.*c.*e.*r.*e.*r/
str =~ r
#=> 2 (truthy)
(or !!(str =~ r) #=> true)
target = "harrypotterandtheasorcerersstone"
r = Regexp.new "#{ target.chars.join "\.*" }"
#=> /h.*a.*r.*r.*y* ... o.*n.*e/
str =~ r
#=> nil
A different albeit not necessarily better solution using sorted character arrays and sub-strings:
Given your two strings...
subject = "aasmflathesorcerersnstonedksaottersapldrrysaahf"
search = "harrypotterandthesorcerersstone"
You can sort your subject string using .chars.sort.join...
subject = subject.chars.sort.join # => "aaaaaaacddeeeeeffhhkllmnnoooprrrrrrssssssstttty"
And then produce a list of substrings to search for:
search = search.chars.group_by(&:itself).values.map(&:join)
# => ["hh", "aa", "rrrrrr", "y", "p", "ooo", "tttt", "eeeee", "nn", "d", "sss", "c"]
You could alternatively produce the same set of substrings using this method
search = search.chars.sort.join.scan(/((.)\2*)/).map(&:first)
And then simply check whether every search sub-string appears within the sorted subject string:
search.all? { |c| subject[c] }
Create a 2 dimensional array out of your string letter bank, to associate the count of letters to each letter.
Create a 2 dimensional array out of the harry potter string in the same way.
Loop through both and do comparisons.
I have no experience in Ruby but this is how I would start to tackle it in the language I know most, which is Java.

Compare string against array and extract array elements present in ruby

I have the following string:
str = "This is a string"
What I want to do is compare it with this array:
a = ["this", "is", "something"]
The result should be an array with "this" and "is" because both are present in the array and in the given string. "something" is not present in the string so it shouldn't appear. How can I do this?
One way to do this:
str = "This is a string"
a = ["this","is","something"]
str.downcase.split & a
# => ["this", "is"]
I am assuming Array a will always have keys(elements) in downcase.
There's always many ways to do this sort of thing
str = "this is the example string"
words_to_compare = ["dogs", "ducks", "seagulls", "the"]
words_to_compare.select{|word| word =~ Regexp.union(str.split) }
#=> ["the"]
Your question has an XY problem smell to it. Usually when we want to find what words exist the next thing we want to know is how many times they exist. Frequency counts are all over the internet and Stack Overflow. This is a minor modification to such a thing:
str = "This is a string"
a = ["this", "is", "something"]
a_hash = a.each_with_object({}) { |i, h| h[i] = 0 } # => {"this"=>0, "is"=>0, "something"=>0}
That defined a_hash with the keys being the words to be counted.
str.downcase.split.each{ |k| a_hash[k] += 1 if a_hash.key?(k) }
a_hash # => {"this"=>1, "is"=>1, "something"=>0}
a_hash now contains the counts of the word occurrences. if a_hash.key?(k) is the main difference we'd see compared to a regular word-count as it's only allowing word-counts to occur for the words in a.
a_hash.keys.select{ |k| a_hash[k] > 0 } # => ["this", "is"]
It's easy to find the words that were in common because the counter is > 0.
This is a very common problem in text processing so it's good knowing how it works and how to bend it to your will.

Resources