Reputation: 135
This is a simple code to parse a simple string in ruby
str = "Amdh#34HB!x"
length = str.length
upper = str.scan(/[A-Z]/).count #count upper chars
lower = str.scan(/[a-z]/).count #count lower chars
digit = str.scan(/[0-9]/).count #count digits
special = str.scan(/[^a-z0-9]/i).count #count special chars
result = "Length : " + length.to_s + " Upper : " + upper.to_s + " Lower : " + lower.to_s + " Digit : " + digit .to_s + " Special : " + special.to_s
The result is given as "Length : 11 Upper : 3 Lower : 4 Digit : 2 Special : 2"
I want to do the same things to an array of strings
array = ["Amdh#34HB!x", "AzErtY45", "#1A3bhk2"]
and so I can know the details like above of each element of the array
Example :
array[0] = "Length : 11 Upper : 3 Lower : 4 Digit : 2 Special : 2"
array[1] = "Length : 8 Upper : 3 Lower : 3 Digit : 2 Special : 0"
array[2] = "Length : 8 Upper : 1 Lower : 3 Digit : 3 Special : 1"
....
I know the answer seems simple by using each
method but didn't find the right way to do it.
The code above is not optimised, if there is any better suggestion, you are welcome!
Upvotes: 0
Views: 375
Reputation: 110685
You need only make a single pass through the string to obtain the needed counts.
def obtain_counts(str)
str.each_char.with_object(Hash.new(0)) do |c,h|
h[case(c)
when /[[:upper:]]/ then :upper
when /[[:lower:]]/ then :lower
when /[[:digit:]]/ then :digit
else :special
end] += 1
end
end
def construct_array(arr)
arr.map! { |str|
"Length : #{str.length} Upper : %d Lower : %d Digit : %d Special : %d" %
obtain_counts(str).values_at(:upper, :lower, :digit, :special) }
end
array = ["Amdh#34HB!x", "AzErtY45", "#1A3bhk2"]
construct_array(array)
#=> ["Length : 11 Upper : 3 Lower : 4 Digit : 2 Special : 2",
# "Length : 8 Upper : 3 Lower : 3 Digit : 2 Special : 0",
# "Length : 8 Upper : 1 Lower : 3 Digit : 3 Special : 1"]
array
#=> ["Length : 11 Upper : 3 Lower : 4 Digit : 2 Special : 2",
# "Length : 8 Upper : 3 Lower : 3 Digit : 2 Special : 0",
# "Length : 8 Upper : 1 Lower : 3 Digit : 3 Special : 1"]
Note that
["Amdh#34HB!x", "AzErtY45", "#1A3bhk2"].map { |str| obtain_counts(str) }
#=> [{:upper=>3, :lower=>4, :special=>2, :digit=>2},
# {:upper=>3, :lower=>3, :digit=>2},
# {:special=>1, :digit=>3, :upper=>1, :lower=>3}]
Notice that the second hash in this array has no key :special
(because the second string contained no special characters). That explains why, in obtain_counts
, we need Hash.new(0)
(empty hash with default 0
), rather than simply {}
.
Upvotes: 2
Reputation: 160551
Here's a start to help you move into a more OO Ruby script:
class Foo
attr_reader :str, :str_len, :upper, :lower, :digit, :punctuation
def initialize(str)
@str = str
@str_len = str.length
@upper, @lower, @digit, @punctuation = %w[upper lower digit punct].map { |re| str.scan(/[[:#{re}:]]/).count }
end
def to_s
('"%s": ' % str) +
[:str_len, :upper, :lower, :digit, :punctuation].map { |s|
'%s: %s' % [s.to_s.upcase, instance_variable_get("@#{s}")]
}.join(' ')
end
end
array = ["Amdh#34HB!x", "AzErtY45", "#1A3bhk2"].map { |s| Foo.new(s) }
puts array.map(&:to_s)
Which, when run, outputs:
"Amdh#34HB!x": STR_LEN: 11 UPPER: 3 LOWER: 4 DIGIT: 2 PUNCTUATION: 2
"AzErtY45": STR_LEN: 8 UPPER: 3 LOWER: 3 DIGIT: 2 PUNCTUATION: 0
"#1A3bhk2": STR_LEN: 8 UPPER: 1 LOWER: 3 DIGIT: 3 PUNCTUATION: 1
The regular expression classes like [[:upper:]]
are POSIX definitions, which help relieve some of the visual noise of a traditional expression's classes. See the Regexp documentation for more information.
It can be DRYer but that's an exercise for the student. You should be able to coerce this into something closer to what you want.
Upvotes: 1
Reputation: 13487
More DRY solution:
array = ["Amdh#34HB!x", "AzErtY45", "#1A3bhk2"]
formats = { Upper: /[A-Z]/,
Lower: /[a-z]/,
Digit: /[0-9]/,
Special: /[^a-z0-9]/i }
array.map do |e|
"Length: #{e.length}, " +
formats.map {|k, v| "#{k}: #{e.scan(v).count}" }.join(', ')
end
#=> ["Length: 11, Upper: 3, Lower: 4, Digit: 2, Special: 2",
# "Length: 8, Upper: 3, Lower: 3, Digit: 2, Special: 0",
# "Length: 8, Upper: 1, Lower: 3, Digit: 3, Special: 1"]
Upvotes: 1
Reputation: 475
try this solution:
def string_check(str)
result = str.scan(/([A-Z])|([a-z])|([0-9])|([^a-z0-9])/).inject([0,0,0,0]) do
|sum,arr| sum.map.with_index{|e,i| e+(arr[i].nil? ? 0: 1) if !arr.nil?}
end
"Length : #{str.size} Upper : #{result[0]} Lower : #{result[1]} Digit : #{result[2]} Special : #{result[3]}"
end
array = ["Amdh#34HB!x", "AzErtY45", "#1A3bhk2"]
array.each {|s| puts string_check(s)}
outputs:
Length : 11 Upper : 3 Lower : 4 Digit : 2 Special : 2
Length : 8 Upper : 3 Lower : 3 Digit : 2 Special : 0
Length : 8 Upper : 1 Lower : 3 Digit : 3 Special : 1
Upvotes: 1
Reputation: 2474
I guess you want to do this:
array = ["Amdh#34HB!x", "AzErtY45", "#1A3bhk2"]
result = []
array.each do |str|
length = str.length
upper = str.scan(/[A-Z]/).count #count upper chars
lower = str.scan(/[a-z]/).count #count lower chars
digit = str.scan(/[0-9]/).count #count digits
special = str.scan(/[^a-z0-9]/i).count #count special chars
result << "Length : " + length.to_s + " Upper : " + upper.to_s + " Lower : " + lower.to_s + " Digit : " + digit .to_s + " Special : " + special.to_s
end
puts result
Upvotes: 1
Reputation: 2860
You may use simple map
to do this:
array = ["Amdh#34HB!x", "AzErtY45", "#1A3bhk2"]
result = array.map do |str|
length = str.length
upper = str.scan(/[A-Z]/).count #count upper chars
lower = str.scan(/[a-z]/).count #count lower chars
digit = str.scan(/[0-9]/).count #count digits
special = str.scan(/[^a-z0-9]/i).count #count special chars
{length: length, upper: upper, lower: lower, digit: digit, special: special}
end
[117] pry(main)> result
=> [{:length=>11, :upper=>3, :lower=>4, :digit=>2, :special=>2},
{:length=>8, :upper=>3, :lower=>3, :digit=>2, :special=>0},
{:length=>8, :upper=>1, :lower=>3, :digit=>3, :special=>1}]
Upvotes: 2