1

Не могу справится с, на первый взгляд,простой задачей: В строке найти количество вхождений каждой буквы.Создал класс с двумя свойствами

class Struct1
{
    public  char Itsch { get; set; }
    public  int Itscon { get; set; } 
}

строку отсортировал перевел в 2 чаровских массива,в дном оставил только уникальные буквы и на его размере создал массив елементов класса

Struct1[] arr = new Struct1[size];

Теперь пытаюсь сделать так

int i = 0;
for (int j = 0; j <ch.Length -1; j++)
{
    arr[i] = new Struct();
    count = 1;  arr[i].Itsch = ch[j];
    while (ch[j] == ch[j + 1])
    {
        count++;
        j++;
    }
    arr[i].Itscon = сount;
    i++;
}

не получается! Пытаюсь выкрутиться двумя циклами

for (int i = 1; i < ch2.Length; i++)
{
    arr[i] = new Struct1();
    arr[i].Itsch = ch2[i];
}

а затем

for(int j=0;j<ch.Length;j++)
{
    if (arr[j].Itsch == ch[j])
    arr[j].Itscon++;
}

Выбивает исключение NullRefrences во втором цикле, но как инициализировать arr[j]??? Если опять arr[j] = new Struct1(); все обнулиться и (arr[j].Itsch == ch[j]) станет бесполезна. Люди умные, помогите несчастному кто чем может!

B. Vandyshev
  • 1,236

4 Answers4

5

С помощью Linq задача решается в одну строку:

string text = "qwertyuiodfghj9ij6ygrd3es7hiprrreeuuuqqqhhhhqqkkk";
var res = text.GroupBy(c => c).Select(g => new { Symbol = g.Key, Count = g.Count() }).ToList();
foreach (var r in res)
    Console.WriteLine("Количество символов {0} = {1}", r.Symbol, r.Count);

Группируем по символу и берем количество элементов в группе

  • Да верно , не уверен , что оппонент поймёт лямдавыражение, важно не решить задачу за оппонента, а дать понять принцип решения. – Digital Core Nov 25 '17 at 09:36
  • @DigitalCore, а регекспы поймет? Почему не просто text.Count(c => c == letter)? – Андрей NOP Nov 25 '17 at 09:37
1

Идею решения вы уловили правильно, поэтому останавливаться на подробном алгоритме не стану. А вот реализовать эту идею можно многими способами, включая массу неэффективных во всех отношениях вариантов.

Никогда не торопитесь создавать новый класс. В BCL .NET для большинства распространенных задач все необходимое уже есть, нужно только потратить немного времени на чтение документации (или мастерски владеть Google-Fu =)) .

Если учесть, что от вас скорее всего хотят не однострочное решение, которое вы вряд ли сможете объяснить, то стоило обратить внимание на класс Dictionary.

Тогда подсчет количества одинаковых букв будет выглядеть так:

var inputString = "hghjkgdjfbdgfjafsdjagfuieruisdcjbvhuidh";
var result = new Dictonary<char, int>();
foreach(var c in inputString)
{
    if(result.ContainsKey(c))
        result[c]++;
    else
        result.Add(c, 1);
}
foreach(var c in result)
{
    Console.WriteLine($"{c.Key} = {c.Value}");
}

Это хороший вариант, достаточно быстрый, но очень вероятно, что от вас хотят увидеть не это, т.к. я хорошо знаком со спецификой обучения информатике в наших школах.

Тогда можно использовать решение совсем "в лоб", которое, к тому же, будет еще и на порядок быстрее любых LiNQ, Dictionary и тем более RegExp-ов.

Вспоминаем, что char - это на самом деле int, в том смысле, что символы хранятся в виде их числовых кодов. Скорее всего, в вашей задаче есть ограничение на диапазон символов, но для простоты, возьмем все символы двухбайтового unicode, который используется в .NET

//Для 2х-байтового юникода это всего 2^16 возможных символов.
//Элементы массива по умолчанию инициализируются 0, в отличие от C/C++.
int[] counters = new int[65536];
var inputString = "hghjkgdjfbdgfjafsdjagfuieruisdcjbvhuidh";
for(int i = 0; i < inputString.Count(); i++)
{
    counters[(int)inputString[i]]++;
}
for(int i = 0; i < 65536; i++)
{
    if(counters[i] > 0)
        Console.WriteLine($"{(char)i} = {counters[i]}");
}

Главная идея заключается в том, что у элемента массива уже есть два независимых значения: индекс элемента и его содержимое. На Паскале это выглядит более очевидно, т.к. в качестве индексов можно использовать символы. В Си-подобных языках индекс всегда число, поэтому нужно предварительно преобразовать char в int, а потом уже использовать его в качестве индекса.

Да, тут есть избыточность по используемой памяти, но она компенсируется скоростью доступа по индексу к массиву и отсутствием создания дополнительных объектов "за кадром", как в случае с LiNQ и RegExp. К тому же, набор входных символов, скорее всего, будет ограничен набором однобайтовых символов, в этом случае можно обрезать длину массива до 256, или ASCII-символов, в этом случае можно обрезать длину массива до 128. Также можно вычесть первые 32 непечатных символа, только не забудьте, что вычитать их нужно при каждом преобразовании char в int, т.к. индексация массива всегда начинается с 0.

Красивое короткое решение уже написал @Андрей, будет замечательно, если вы разберетесь как оно работает, только не вздумайте писать такое решение на ЕГЭ, проверяющие не поймут, а вам придется таскаться по апелляциям.

rdorn
  • 16,323
0

Всё на много проще, чем кажется на первый взгляд. Используем Linq, и RegularExpressions.

        static void Main(string[] args)
        {
            const string text = "qwertyuiodfghj9ij6ygrd3es7hiprrreeuuuqqqhhhhqqkkk";
            foreach (var letter in text.Distinct().ToArray())
            {
                var count = Regex.Matches(text, letter.ToString()).Count;
                Console.WriteLine("Количество символов {0} = {1}", letter, count);
            }
            Console.ReadKey();
        }

введите сюда описание изображения

Digital Core
  • 1,654
  • 1
    О_о, и регэкспы приплели сюда? – Андрей NOP Nov 25 '17 at 09:24
  • 2
    Ну, эээ. Это, конечно, решение, но оно пробегает строку столько раз, сколько в ней различных букв. – VladD Nov 25 '17 at 09:41
  • Да согласен, но я привёл пример в качестве обучения , не решение за оппонента, лямда - разворачивается тоже в немалую конструкцию. – Digital Core Nov 25 '17 at 10:15
  • 1
    @DigitalCore в качестве обучения чему? Как не надо делать? Это будет даже покруче чем конкатенация string-ов в цикле, которая встречается в каждом втором "типа учебнике" или самоучителе. – rdorn Nov 25 '17 at 11:07
  • ToArray не нужен – Grundy Nov 25 '17 at 11:36
0

Думаю в конкретно вашем случае решить можно проще. Хорошо, что вы отсортировали строку, но зачем после этого включать дополнительный цикл внутрь. Не проще ли первый элемент создать перед циклом, а не делать это внутри. И после каждый последующий символ сравнивать с текущим в вашем результирующем массиве.

int i = 0;
arr[i] = new Struct() { Itsch = ch[0], Itscon = 1 };

for (int j = 1; j <ch.Length; j++)
{
    if (arr[i].Itsch == ch[j])
        arr[i].Itscon++;
    else
    {
        i++;
        arr[i] = new Struct() { Itsch = ch[j], Itscon = 1 };
    }
}

Это решение только для конкретно вашего инструментария. Использование лямбд, как показали выше, действительно упростит решение.

Иван
  • 1,109