I have a question about array in ruby
I have an array which contains many elements (strings with uppercases and down case ) and i want to know how many element (how many string) in this array contains an uppercase letters :
i obtain many element but i dont know how to calculate them
thank you.
array.each do |arr|
print arr.scan(/[A-Z]/)
end
Following your example, what you need is match? if you want a boolean result regarding if the element matches or not with an uppercase letter on it:
['foo', 'Foo', 'FoO'].each { |string| p string.match?(/[A-Z]/) }
# false
# true
# true
You can use count and pass a block to check if the current element returns true when evaluating if it contains uppercase characters. The result is the total of elements yielding a true value from the block:
['foo', 'Foo', 'FoO'].count { |string| /[[:upper:]]/ =~ string }
# 2
So i did:
a = ["HellO", "hello", "World", "worlD"]
b = 0
a.each do |x|
b += x.scan(/[A-Z]/).length
end
puts b # Which equals 4 in this case
The problem I had in some of the answers above with my array.
For Cary's answer I get 3 which somehow missing one of the capital letters.
For Sebastian's answer I get 3 as well which is also somehow missing one of the capital letters.
My array has 2 capitals in the first string, 1 in the third and 1 in the fourth.
Of course the more normal ruby way would be with b += x.scan(/[A-Z]/).count instead of .length but it worked for me in irb.
Some sample output from my console of the three methods:
030 > a.grep(/\p{Lu}/).size
=> 3
:031 > a.count {|string| /[[:upper:]]/ =~ string}
=> 3
:026 > a.each do |x|
:027 > b += x.scan(/[A-Z]/).length
:028?> end
=> ["HellO", "hello", "World", "worlD"]
:029 > b
=> 4
It appears as if the two regex examples above just check for any capital in the string and count it as one so if you had multiple in the same string like my first "HellO" then it only counts as one:
:039 > ["HellO"].grep(/\p{Lu}/).size
=> 1
:040 > ["HellO"].count {|string| /[[:upper:]]/ =~ string}
=> 1
Of course this may not matter to you but if the string is longer than one word it very well may:
2.5.3 :045 > a = ["Hello World"]
2.5.3 :047 > a.each do |x|
2.5.3 :048 > b += x.scan(/[A-Z]/).count
2.5.3 :049?> end
=> ["Hello World"]
2.5.3 :050 > b
=> 2
2.5.3 :051 > a.count {|string| /[[:upper:]]/ =~ string}
=> 1
2.5.3 :052 > a.grep(/\p{Lu}/).size
=> 1
With two words in one string it you can see the difference.
Of course I am counting the total capital letters when you asked,
i want to know how many element (how many string) in this array contains an uppercase letters
In which case either of the other two answers above do beautifully :)
Related
I'm creating a function that takes a string and creates an acronym but am running into errors.
When I input "Complementary metal-oxide semiconductor" I get "CS" in return when expecting "CMOS". Any suggestions why this might happen? I pass it plenty of other strings and it works, just doesn't work in this case.
class Acronym
def self.abbreviate(phrase)
letters = phrase.split("")
acronym = []
letters.each do |letter|
previous = letters.index(letter) - 1
if previous == -1
acronym.push(letter)
elsif letters[previous] == " " || letters[previous] == "-"
acronym.push(letter)
end
end
acronym.join("").upcase
end
end
Simplifies to
def acronym(str)
str.split(/ |-/).map(&:first).join.upcase
end
The above depends on the Rails activesupport library. Here's a Ruby-only variation:
str.split(/ |-/).map { |s| s[0] }.join.upcase
The issue with your code is that index() returns the first occurrence of the given letter. So, two problems:
The 'm' in 'metal' is not the first occurrence of 'm' in the string. It appears in the word 'complementary'. Thus, whenever it sees an 'm' in the string, previous will always be 'o' and thus not trigger a push().
Anytime the first letter in your string recurs (regardless of position), it will trigger your first condition. You can see the effect if you change the initial 'C' to 'c' in your test string. The result will be CSCC because there are two 'c's in 'semiconductor'.
As an alternative, here is an option that uses regex:
def self.abbreviate(phrase)
phrase.gsub('-', ' ')
.scan(/(\A\w|(?<=\s)\w)/)
.flatten
.join.upcase
end
Step by step:
Borrowing the .gsub from #DollarChills to turn the '-' into a space.
scan() returns an array of all matches. The regex matches the first word in the string and any word that is preceded by a space.
The result of the scan is actually an array of arrays, so flatten unnests them.
Combine into a string and upcase
You could try using gsub to ignore the hyphen.
<%= ('Complementary metal-oxide semiconductor').gsub('-', ' ') %>
Returns: Complementary metaloxide semiconductor
You have a bug in previous = letters.index(letter) - 1
see if you can spot it:
arr = [:a, :b, :c, :a]
previous_indexes = arr.map { |n| arr.index(n) - 1 }
you_are_expecting = [-1, 0, 1, 2]
previous_indexes == you_are_expecting
# => false
arr.index(:a) # => 0
arr.index(:b) # => 1
arr.index(:c) # => 2
arr.index(:a) # => 0
To get indexes with iterating, use with_index:
arr = %i[a b c a]
arr.map.with_index { |x, i| [x, i] }
# => [[:a, 0], [:b, 1], [:c, 2], [:a, 3]]
If you make that fix, your code does what you intended.
A suggestion though: you can often avoid dealing with the details of array indexes. Take a look at how #Mori's answer works by operating at a higher level.
I have achieved checking if capitalization exists with the first letter of each word with the below,
NAME_CAPS = /^\p{Lu}\S*{2,}(?:[[:space:]]+\p{Lu}\S*{2,})*$/
Maintaining that, I would like to also add to check if 2 words are inserted, you can see my attempt above by adding the {2,}. Currently the problem is, it will pass as correct with only one word inserted by the user with correct capitalization - 2 words must be inserted.
More relevant code:
def valid_name?(name)
!!name.match(NAME_CAPS)
end
puts "Now, go for it!"
while (name=gets)
names = name.split(" ", 2)
if valid_name?(name)
puts "Correct."
break
else
puts "Wrong."
end
end
I know you're trying to do this with a regex, but you can also do this:
def valid_name?(name)
names = name.split
names.size == 2 && names.all?{|n| n == n.capitalize}
end
valid_name?("john doe") #=> false
valid_name?("John Doe") #=> true
valid_name?("John") #=> false
valid_name?("John Q Doe") #=> false
You can change zero-or-more repetition (*) to one-or-more repetition (+) in the second word group:
NAME_CAPS = /^\p{Lu}\S*{2,}(?:[[:space:]]+\p{Lu}\S*{2,})+$/
Btw this patter will match longer sequences as well:
pry> 'Joe'.match(NAME_CAPS)
=> nil
pry> 'Joe Doe'.match(NAME_CAPS)
=> #<MatchData "Joe Doe">
pry> 'Joe Doe Zoe'.match(NAME_CAPS)
=> #<MatchData "Joe Doe Zoe">
To avoid it (and simplify the pattern) you can resign from the repetition:
NAME_CAPS = /^\p{Lu}\S*{2,}(?:[[:space:]]+\p{Lu}\S*{2,})$/
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.
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}
Why isnt that working:
>> s = "hi"
=> "hi"
>> s == ("hi"|"ho")
NoMethodError: undefined method `|' for "hi":String
from (irb):2
>>
I don't get it.. Is there a solution for this kind of syntax? Because
s == ("hi"|"ho")
#is shorther than
s == "hi" || s == "ho"
Yes, the bitwise operator | is not defined in the String class: http://ruby-doc.org/core/classes/String.html
Consider this for expressiveness:
["hi", "ho"].include? myStr
irb(main):001:0> s = "hi"
=> "hi"
irb(main):002:0> ["hi", "ho"]
=> ["hi", "ho"]
irb(main):003:0> ["hi", "ho"].include? s
=> true
irb(main):004:0> s = "foo"
=> "foo"
irb(main):005:0> ["hi", "ho"].include? s
=> false
In most high level languages that syntax will not work, you have to stick to the longer syntax of:
s == "hi" || s == "ho"
Note that | is a bitwise or, whereas || is a regular or
You could use the include? method on array if you've got several == tests to do:
["hi", "ho"].include?(s)
Not shorter for two checks admittedly but it will be shorter for three or more.
This syntax doesn't exist in any language as far as I know.
What you are saying
s == ("hi"|"ho")
Literally translates to 'bitwise OR the strings "hi" and "ho" together and then compare them with s'. If you can't see why this is not what you are looking for, try writing down the ASCII codes for "hi" and "ho" and then bitwise ORing them together. You are going to get complete gibberish.
You could make it work that way:
irb> class Pair
def initialize(strA,strB)
#strA,#strB = strA,strB
end
def ==(string)
string == #strA || string == #strB
end
def |(other)
Pair.new(self,other)
end
end
#=> nil
irb> class String
def |(other)
Pair.new(self,other)
end
alias old_equals :==
def ==(other)
if other.kind_of? Pair
other == self
else
old_equals other
end
end
end
#=> nil
irb> ("one"|"two") == "one"
#=> true
irb> ("one"|"two") == "two"
#=> true
irb> ("one"|"two") == "three"
#=> false
irb> "one" == ("one"|"two")
#=> true
irb> "three" == ("one"|"two"|"three")
#=> true
But since this involves some monkey-patching of a fairly lowlevel class, I wouldn't advise relying on it. Other people will hate reading your code.
Ruby supports binary 'or' and other binary operations on values of type Fixnum and Bignum, meaning any integer. Bitwise operations aren't supported on strings or any other type, as far as I know.
As other people have mentioned, you probably want something other than binary operations altogether. However, you can easily get integer representations of characters, so you can compare characters like so:
a = "Cake"
b = "Pie"
puts a[0] | b[0] # Prints "83" - C is 67 and P is 80.
You can get an array of the comparisons easily with some conversions.
a = "Cake"
b = "Pie " # Strings of uneven length is trivial but more cluttered.
a_arr = a.split(//)
b_arr = b.split(//)
c_arr = []
a.each_with_index { |char, i| c.push(a[i].to_i | b[i].to_i) }
# If you *really* want an ASCII string back...
c = c_arr.collect(&:chr).join
You could use a regex:
Like so:
regex = /hi|ho/
s = "hi"
t = "foo"
s =~ regex
#=> 0
t =~ regex
#=> nil