C# | Как подправить алгоритм?

Я захотел написать свой генератор пароля на C#, который спрашивал: длину пароля, сколько спец. символов, сколько чисел. Конечно, всё было без проблем, кроме одной. Если я ставил, например, длину 7, спец. символов 2, чисел 2, то он длину сделал правильно, кол-во чисел правильно, но вот только спец. символ только один. Он сгенерировал 3, 4 индекс (буквы), но не оставшийся спец. символ. Так вот. Как мне сделать чтобы все условия были бы выполнены? Мой код

using System;
using System.Collections.Generic;
using static System.Console;

var random = new Random();

var userPasswordData = ReadLine()?.Split(' ');
var lengthRequired = int.Parse(userPasswordData?[0] ?? "7");
var specialSymbolsRequired = int.Parse(userPasswordData?[1] ?? "2");
var numbersRequired = int.Parse(userPasswordData?[2] ?? "2");

var charactersList = new List<string>
{
    "0123456789",
    "!@#$%&*",
    // ReSharper disable once StringLiteralTypo
    "abcdefghijklmnopqrstuvwxyz",
    // ReSharper disable once StringLiteralTypo
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
};
var generatedPasswordData = new Dictionary<string, int>
{
    {"SpecialSymbols", 0},
    {"Numbers", 0}
};
var generatedPassword = "";

for (var i = 1; i <= lengthRequired; i++)
{
    var rowIndex = 0;
    var columnIndex = 0;
    
    // If it's first letter of generation password.
    if (i == 1)
    {
        rowIndex = random.Next(2, charactersList.Count);
        columnIndex = random.Next(0, charactersList[rowIndex].Length);

        generatedPassword += charactersList[rowIndex][columnIndex];
        continue;
    }
    
    do
    {
        rowIndex = random.Next(0, charactersList.Count);

        // If row at numbers
        if (rowIndex == 0)
        {
            if (generatedPasswordData["Numbers"] < numbersRequired)
            {
                columnIndex = random.Next(0, charactersList[rowIndex].Length);

                generatedPassword += charactersList[rowIndex][columnIndex];
                generatedPasswordData["Numbers"] += 1;
                break;
            }
        }
        // If row at Special Symbols
        else if (rowIndex == 1)
        {
            if (generatedPasswordData["SpecialSymbols"] < specialSymbolsRequired)
            {
                columnIndex = random.Next(0, charactersList[rowIndex].Length);

                generatedPassword += charactersList[rowIndex][columnIndex];
                generatedPasswordData["SpecialSymbols"] += 1;
                break;
            }
        }
        // If row at letters.
        else
        {
            columnIndex = random.Next(0, charactersList[rowIndex].Length);

            generatedPassword += charactersList[rowIndex][columnIndex];
            break;
        }
    } while (true);
    
    
}
WriteLine(generatedPassword);

Ответы (3 шт):

Автор решения: ebw1910

Лови

namespace ConsoleApplication11
{
    class Program
    {
        static void Main(string[] args)

        {
            var random = new Random();
            int gh = 0;
            int gy = 0;
            var userPasswordData = ReadLine()?.Split(' ');
            var lengthRequired = 7; //int.Parse(userPasswordData?[0] ?? "7");
            var specialSymbolsRequired = 5; //int.Parse(userPasswordData?[1] ?? "2");
            var numbersRequired = 2;// int.Parse(userPasswordData?[2] ?? "2");

            var charactersList = new List<string>
{
    "0123456789",
    "!@#$%&*",
    // ReSharper disable once StringLiteralTypo
    "abcdefghijklmnopqrstuvwxyz",
    // ReSharper disable once StringLiteralTypo
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
};
            var generatedPasswordData = new Dictionary<string, int>
{
    {"SpecialSymbols", 0},
    {"Numbers", 0}
};
            var generatedPassword = "";

            for (var i = 1; i <= lengthRequired; i++)
            {
                var rowIndex = 0;
                var columnIndex = 0;

                do
                {
                    rowIndex = random.Next(0, charactersList.Count);

                    // If row at numbers
                    if (rowIndex == 0)
                    {
                        if (generatedPasswordData["Numbers"] < numbersRequired)
                        {
                            gy++;
                            columnIndex = random.Next(0, charactersList[rowIndex].Length);

                            generatedPassword += charactersList[rowIndex][columnIndex];
                            generatedPasswordData["Numbers"] += 1;
                            break;
                        }
                    }
                    // If row at Special Symbols
                     if (rowIndex == 1)
                    {
                        if (generatedPasswordData["SpecialSymbols"] < specialSymbolsRequired)
                        {
                            gh++;
                            columnIndex = random.Next(0, charactersList[rowIndex].Length);
                            
                            generatedPassword += charactersList[rowIndex][columnIndex];
                            generatedPasswordData["SpecialSymbols"] += 1;
                            break;
                        }
                    }
                    // If row at letters.
                    else if (gh + gy == specialSymbolsRequired+numbersRequired)
                    {
                        columnIndex = random.Next(0, charactersList[rowIndex].Length);

                        generatedPassword += charactersList[rowIndex][columnIndex];
                        break;
                    }
                } while (true);


            }
            WriteLine(generatedPassword);
            Console.ReadLine();
        }
    }
}
→ Ссылка
Автор решения: Laukhin Andrey

Я предлагаю по другому подойти к решению. Сначала создаём маску будущего пароля, которая соответствует нашим условиям. Эта маска содержит индексы на наборы символов. Затем "перетасовываем" маску и в цикле вычисляем новые символы для пароля.

Random random = new Random();

var lengthRequired = 7;
var specialSymbolsRequired = 2;
var numbersRequired = 2;

var charactersList = new List<string>
{
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
    "0123456789",
    "!@#$%&*"
};

String mask = (new String('1', numbersRequired) + new String('2', specialSymbolsRequired)).PadRight(lengthRequired-1, '0');
String smask = '0' + new string(mask.ToCharArray().OrderBy(x => random.NextDouble()).ToArray());
            
String generatedPassword = "";

for (int i = 0; i < lengthRequired; i++)
{
    int rowIndex = smask[i] - 48;
    int columnIndex = random.Next(0, charactersList[rowIndex].Length);

    generatedPassword += charactersList[rowIndex][columnIndex];
}

Console.WriteLine(generatedPassword);

smask - это перетасованная маска. '0' в начале гарантирует, что первый символ будет буква. Рекомендую найти более качественный shuffle-алгоритм.

→ Ссылка
Автор решения: Andrei Khotko

Ошибка в вашем алгоритме кроется в том, что вы не контролируете количество сгенерированных букв. Что вам нужно поменять в вашем алгоритме, чтобы все работало правильно?

  1. Вычислить требуемое количество букв, которое должно содержать итоговая строка:

    var lettersRequired = lengthRequired - specialSymbolsRequired - numbersRequired - 1; // -1 учитывает первый символ пароля, который обязательно должен быть буквой
    
  2. Добавить в словарь generatedPasswordData счетчик "Letters":

    var generatedPasswordData = new Dictionary<string, int>
    {
        {"SpecialSymbols", 0},
        {"Numbers", 0},
        {"Letters", 0}
    };
    
  3. В месте, где вы генерируете буквы, добавьте аналогичное условие на ограничение количества сгенерированных букв:

    // If row at letters.
    else if (generatedPasswordData["Letters"] < lettersRequired)
    {
        columnIndex = random.Next(0, charactersList[rowIndex].Length);
    
        generatedPassword += charactersList[rowIndex][columnIndex];
        generatedPasswordData["Letters"]++;
        break;
    }
    

P.S. Я бы на вашем месте вынес код генерации символов в отдельный класс. Возможно, чуть позже дополню ответ с улучшениями вашего кода.

→ Ссылка