media
media

Reputation: 135

Ruby Parsing an array of string

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

Answers (6)

Cary Swoveland
Cary Swoveland

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

the Tin Man
the Tin Man

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

Ilya
Ilya

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

Dmitry Cat
Dmitry Cat

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

yoones
yoones

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

Ilya Lavrov
Ilya Lavrov

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

Related Questions