Home > OS >  Ruby Array Elements
Ruby Array Elements

Time:09-17

I am trying to create password Generate in ruby. At the moment all is working just got stuck at the final piece of generating the password.

I asked user if he/she would like the password to include numbers, lowercase or uppercase. If YES, user will enter 1 and 0 for NO.

I used the code below to generate password if everything is 1. Meaning user want to include numbers, lowercase and uppercase.

 if numbers == 1 && lowercase == 1 && uppercase == 1
     passGen = [(0..9).to_a   ('A'..'Z').to_a   ('a'..'z').to_a].flatten.sample(10)
 end

 p passGen

This works 90% of the time. 10% of the time the generated password will not include say any numbers. But everything else present. I am not sure if this is because of the size or length of Array from which the password is sampled.

Anyway lets go to the main problem below

Here is the problem, I am struggling to write the code to generate password if one or more of input is 0. That's if user don't want to include numbers. Or no numbers and uppercase etc . As I can't predict what user may want or not want. I need help on this please. Thank you.

CodePudding user response:

You will need to make your input array more dynamic:

passGen = []
passGen  = (0..9).to_a if numbers == 1
passGen  = ('A'..'Z').to_a if uppercase == 1
passGen  = ('a'..'z').to_a if lowercase == 1
passGen.sample(10).join

Now, to tackle your other issue with missing characters - this is caused as you are simply taking 10 random characters from an array. So it can just take, for example, all digits.

To tackle this you need to get one character from each generator first and then generate the remaining characters randomly and shuffle the result:

def generators(numbers:, lowercase:, uppercase:)
  [
    (0..9 if numbers),
    ('A'..'Z' if uppercase),
    ('a'..'z' if lowercase)
  ].compact.map(&:to_a)
end

def generate_password(generators:, length:, min_per_generator: 1)
  chars = generators.flat_map {|g| Array.new(min_per_generator) { g.sample }}
  chars  = Array.new(length - chars.length) { generators.sample.sample }
  chars.shuffle.join
end

gens = generators(numbers: numbers == 1, uppercase == 1, lowercase: lowercase == 1)
Array.new(10) { generate_password(generators: gens, length: 10) }

CodePudding user response:

By saying (0..9).to_a ('A'..'Z').to_a ('a'..'z').to_a, you're creating an Array of 10 26 26 = 62 elements, and then you pick only 10 elements out of it.

At your place I'd wrap password generation around an until block:


def generate_password_with_digits_and_caps
  [(0..9).to_a   ('A'..'Z').to_a   ('a'..'z').to_a].flatten.sample(10).join
end


passGen = ''
until passGen.match(/[A-Z]/) && passGen.match(/[a-z]/) && passGen.match(/\d/)
  passGen = generate_password_with_digits_and_caps
end

This could also work (closer to your snipppet):

if numbers == 1 && lowercase == 1 && uppercase == 1
  passGen = ''
     
  until passGen.match(/[A-Z]/) && passGen.match(/[a-z]/) && passGen.match(/\d/)
    passGen = [(0..9).to_a   ('A'..'Z').to_a   ('a'..'z').to_a].flatten.sample(10).join
  end
end

CodePudding user response:

The code doesn't know it needs to include a digit/letter from every group. The sample takes random signs and since you a basically sampling 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz there is a possibility that all the signs will not be digits.

The easiest way to fix it is to check if a sign from every group is in the "password" and then replace a random sign with a sign from group that is not present.

If I were to program this I would do it like that

def random_from_range(range)
    range.to_a.sample.to_s
end

def passGen(numbers, lowercase, uppercase)
    result = ''
    possibleSigns = []
    if numbers == 1
        range = (0..9)
        result  = random_from_range(range)
        possibleSigns  = range.to_a
    end
    if lowercase == 1
        range = ('A'..'Z')
        result  = random_from_range(range)
        possibleSigns  = range.to_a
    end
    if uppercase == 1
        range = ('a'..'z')
        result  = random_from_range(range)
        possibleSigns  = range.to_a
    end
    desired_lenth = 10
    while result.length < desired_lenth
        result  = possibleSigns.sample.to_s
    end
    result
end

puts passGen(1,1,1)

CodePudding user response:

Start with something simple and stupid:

passGen = (('0'..'9').to_a.sample(1)  ('A'..'Z').to_a.sample(1) ('a'..'z').to_a.sample(8).shuffle).join

Technically speaking, this already fulfills your requirement. From the viewpoint of aesthetics and security, the disadvantage here is that the number of upper case characters is always 8. A more elegant solution would be to find three non-zero integers which add up to 10, and can be used as the arguments for the sample call. Also, if no numbers are requested, you simply pass 0 as argument to sample.

Since this exceeds the scope of your question, and I don't even know whether you want to go so far, I don't elaborate on this here further.

  • Related