I'm working on a basic password generator for This Reddit Programming Exercise. I have very simple password generation working in Kotlin, but I'm struggling to come up with a good way to guarantee a specific minimum number of each character type (i.e. at least 2 each of Upper, Lower, Digit, and special character).
I could easily just do two of each to start the password, but I want them to be randomly distributed, not all front-loaded. I also thought of inserting the characters randomly into a character array, rather than appending to a string, but I was wondering if maybe there is a better way.
Here is what I have currently:
private fun generatePassword() {
var password = ""
val passwordLength = (8..25).random()
for (i in 0..passwordLength) {
val charType = (1..5).random()
when (charType) {
1 -> password += lettersLower[(0..lettersLower.size).random()]
2 -> password += lettersUpper[(0..lettersUpper.size).random()]
3 -> password += numbers[(0..numbers.size).random()]
4 -> password += symbols[(0..symbols.size).random()]
}
}
println(password)
}
This works and generates a random password of a random length of 8-24 characters, and contains a random combination of uppercase letters, lowercase letters, digits, and special characters, but it doesn't guarantee the presence of any specific number of each type, like most websites require nowadays.
I thought of doing something like this:
private fun generatePassword() {
var password: ArrayList<Char> = arrayListOf()
var newChar:Char
var numLow = 0
var numUpp = 0
var numDig = 0
var numSpe = 0
while (numLow < 2){
newChar = lettersLower[(0..lettersLower.size).random()]
password.add((0..password.size).random(), newChar)
numLow++
}
while (numUpp < 2){
newChar = lettersUpper[(0..lettersUpper.size).random()]
password.add((0..password.size).random(), newChar)
numUpp++
}
while (numDig < 2){
newChar = numbers[(0..numbers.size).random()]
password.add((0..password.size).random(), newChar)
numDig++
}
while (numSpe < 2){
newChar = symbols[(0..symbols.size).random()]
password.add((0..password.size).random(), newChar)
numSpe++
}
val passwordLength = (8..25).random() - password.size
for (i in 0..passwordLength) {
val charType = (1..5).random()
when (charType) {
1 -> {
newChar = lettersLower[(0..lettersLower.size).random()]
password.add(newChar)
}
2 -> {
newChar = lettersUpper[(0..lettersUpper.size).random()]
password.add(newChar)
}
3 -> {
newChar = numbers[(0..numbers.size).random()]
password.add(newChar)
}
4 -> {
newChar = symbols[(0..symbols.size).random()]
password.add(newChar)
}
}
}
for (i in password.indices){
print(password[i])
}
print("\n")
}
But that doesn't seem particularly concise. The other option I thought of is to check if the password contains two of each type of character and generate a new one if it doesn't, but that seems awfully inefficient. Is there a better way to ensure that if I run my RNG 8+ times it will return at least, but not exactly, two ones, two twos, two threes, and two fours?
Here's my random function, if that helps:
// Random function via SO user @s1m0nw1 (https://stackoverflow.com/a/49507413)
private fun ClosedRange<Int>.random() = (Math.random() * (endInclusive - start) + start).toInt()
I also found this SO post in Java, but it's also not very concise and I'm wondering if there is a more general purpose, more concise solution to ensure a minimum number of occurrences for each possible outcome of an RNG over a number of trials greater than the sum of those minimum occurrences.
Sorry for the rambling post. Thanks in advance for your insight.