Форум программистов, компьютерный форум, киберфорум
C# для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 4.73/37: Рейтинг темы: голосов - 37, средняя оценка - 4.73
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3463 / 2784 / 575
Регистрация: 04.09.2018
Сообщений: 8,757
Записей в блоге: 3
.NET 4.x

Быстрейший способ отделить число от букв

13.09.2024, 13:11. Показов 9775. Ответов 117
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Всем привет.
Есть строки такого вида:
Code
1
2
3
4
1.5224V
1.5225mV
1.526 mV
1.527 V
Соответственно, число - это значение (всегда double!), а буквы - ед. измерения.
Необходимо наиболее быстро распарсить их.

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

Сначала первый вариант (без пробела) я парсил так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (RegexPattern.IsFiltered(message))
{
    const string numbers = "0123456789.";
    var numberBuilder = new StringBuilder();
    var unitBuilder = new StringBuilder();
 
    foreach (char c in message)
    {
        if (numbers.IndexOf(c) > -1)
            numberBuilder.Append(c);
        else
            unitBuilder.Append(c);
    }
 
    double.TryParse(numberBuilder.ToString(), out double value);
    rawVoltage = value;
    Mv = unitBuilder.ToString() == "mV";
}
а второй просто Regex'ом: @"^\d+\.\d+\s{1}m?V$"

Затем сделал "общий" Regex для обоих этих случаев: @"^\d+\.\d+\s?m?V$"
и теперь их значения достаю вот так:
C#
1
2
3
4
5
6
if (IsVoltage(s))
{
    Console.Write($"Voltage: {RegexVoltageValue().Match(s).Value}\t");
    Console.Write($"Unit: {RegexUnitValue().Match(s).Value}");
    Console.WriteLine();
}
где сами регулярки такие:
C#
1
2
3
4
5
[GeneratedRegex(@"\d+\.\d+")]
private static partial Regex RegexVoltageValue();
 
[GeneratedRegex(@"(m?V)$")]
private static partial Regex RegexUnitValue();
По скорости работы StringBuilder VS Regex, вроде бы, немного выигрывает Regex, однако где-то я читал, что они медленнее, чем выборка нужного из цикла..
На простом коде сложно определить, т.к. разница незначительна, но вдруг случится "что-то", что намного затормозит этот процесс с Regex'ом?
Т.е. не поменял ли я шило на мыло?
1
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
13.09.2024, 13:11
Ответы с готовыми решениями:

Отделить цифры от букв и найти сумму цифр
Есть массив,N размера.В массиве присутствуют буквы и цифры.Нужно отделить цифры от букв и найти сумму цифр: отделяем буквы от цифр: ...

Автомобильные номера состоят из 1,2, или 3 букв и 4 цифр. используется 30 букв, найти число таких букв
Автомобильные номера состоят из 1,2, или 3 букв и 4 цифр. используется 30 букв, найти число таких букв. помогите решить, или объясните...

отделить крупное число от мелкого
например ввожу 4530 результат 4500 и 30 3456 3400 и 56 и т. д.

117
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3463 / 2784 / 575
Регистрация: 04.09.2018
Сообщений: 8,757
Записей в блоге: 3
19.09.2024, 13:26  [ТС]
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от kolorotur Посмотреть сообщение
(double, string)
Цитата Сообщение от kolorotur Посмотреть сообщение
чтобы писькомерство
да да

Добавлено через 4 минуты
Code
1
2
3
4
5
6
| Method | source   | Mean     | Error    | StdDev   | Gen0   | Allocated |
|------- |--------- |---------:|---------:|---------:|-------:|----------:|
| Parse  | 1.5224V  | 63.75 ns | 0.178 ns | 0.157 ns | 0.0050 |      24 B |
| Parse  | 1.5225mV | 61.26 ns | 0.217 ns | 0.203 ns | 0.0068 |      32 B |
| Parse  | 1.526 mV | 58.36 ns | 0.311 ns | 0.276 ns | 0.0068 |      32 B |
| Parse  | 1.527 V  | 59.35 ns | 0.204 ns | 0.190 ns | 0.0050 |      24 B |
наравне с вариантом от belalugoci идет..
0
 Аватар для belalugoci
475 / 294 / 29
Регистрация: 01.06.2018
Сообщений: 3,676
19.09.2024, 13:43
Цитата Сообщение от wizard41 Посмотреть сообщение
Вот про этот.
там не нашёл реализации return double

Цитата Сообщение от kolorotur Посмотреть сообщение
Тогда сигнатуру и возвращаемое значение надо подправить, чтобы писькомерство было честным
поддерживаю, а то не могу понять линейку брать дюймовую или сантиметровую )
0
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3463 / 2784 / 575
Регистрация: 04.09.2018
Сообщений: 8,757
Записей в блоге: 3
19.09.2024, 13:45  [ТС]
Цитата Сообщение от belalugoci Посмотреть сообщение
там не нашёл реализации return double
ну я естественно "допиливал" метод до нужного вида..
0
 Аватар для belalugoci
475 / 294 / 29
Регистрация: 01.06.2018
Сообщений: 3,676
19.09.2024, 13:50
Цитата Сообщение от wizard41 Посмотреть сообщение
наравне с вариантом от belalugoci идет..
засел за double.Parse() свой, попробую по бенчам ориентироваться, а то StopWatch в данном случае не помощник.

Добавлено через 48 секунд
Цитата Сообщение от wizard41 Посмотреть сообщение
ну я естественно "допиливал" метод до нужного вида..
скиньте, чтобы я мог своё тестировать и сравнивать
0
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3463 / 2784 / 575
Регистрация: 04.09.2018
Сообщений: 8,757
Записей в блоге: 3
19.09.2024, 14:12  [ТС]
C#
1
2
3
4
5
6
7
8
9
    [Benchmark]
    public (double, string) Parse() => January29_Method(source);
 
    static (double, string) January29_Method(string str)
    {
        var value = string.Concat(str.Where(char.IsLetter));
        string unit = str[..str.IndexOf(value)].TrimEnd();
        return (double.Parse(value), unit);
    }
0
 Аватар для belalugoci
475 / 294 / 29
Регистрация: 01.06.2018
Сообщений: 3,676
19.09.2024, 14:46
wizard41, хм, что-то не работает у меня ни мой вариант ни January29
на мои пишет
Specified argument was out of the range of valid values.
The input string '1.5225mV' was not in a correct format.
The input string '1.526 mV' was not in a correct format.
Specified argument was out of the range of valid values.

на January29
The input string 'V' was not in a correct format.
The input string 'mV' was not in a correct format.
The input string 'mV' was not in a correct format.
The input string 'V' was not in a correct format.

при том что без бенча всё работает.
0
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3463 / 2784 / 575
Регистрация: 04.09.2018
Сообщений: 8,757
Записей в блоге: 3
19.09.2024, 15:00  [ТС]
belalugoci, Params правильно оформлен?
C#
1
2
    [Params("1.5224V", "1.5225mV", "1.526 mV", "1.527 V")]
    public string source { get; set; }
0
 Аватар для belalugoci
475 / 294 / 29
Регистрация: 01.06.2018
Сообщений: 3,676
19.09.2024, 15:17
wizard41, так же.

в общем пока запустил без бенча и ошибка в January29_Method

System.FormatException: "The input string 'V' was not in a correct format." для строки return (double.Parse(value), unit);

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
string[] msg =
{
  "1,5224V", "1,5225mV", "1,526 mV", "1,527 V"
};
 
 
foreach (var m in msg)
{
    var c1 = Parse(m);
    Console.WriteLine("bl = {0}\t{1}", c1.Item1, c1.Item2);
 
    var c2 = January29_Method(m);
    Console.WriteLine("jn = {0}\t{1}", c2.Item1, c2.Item2);
 
}
 
static (double, string) January29_Method(string str)
{
    var value = string.Concat(str.Where(char.IsLetter));
    string unit = str[..str.IndexOf(value)].TrimEnd();
    return (double.Parse(value), unit);
}
Добавлено через 8 минут
wizard41, в общем моему методу нужно скармливать числа с запятыми, а January29_Method ни так ни так не работает.
0
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3463 / 2784 / 575
Регистрация: 04.09.2018
Сообщений: 8,757
Записей в блоге: 3
19.09.2024, 15:20  [ТС]
belalugoci, попутано местами. Должно быть так:
C#
1
2
3
4
5
6
    static (double, string) January29_Method(string str)
    {
        string? unit = string.Concat(str.Where(char.IsLetter));
        string value = str.Substring(0, str.IndexOf(unit)).TrimEnd();
        return (double.Parse(value), unit);
    }
Добавлено через 2 минуты
Цитата Сообщение от belalugoci Посмотреть сообщение
нужно скармливать числа с запятыми
чтобы скармливать любые разделители, нужно в double.Parse добавить параметр CultureInfo.InvariantCulture
0
 Аватар для belalugoci
475 / 294 / 29
Регистрация: 01.06.2018
Сообщений: 3,676
19.09.2024, 15:28
wizard41,
на моей машине, парсер в моем методе стандартный, ничего нового не добавлял еще, метод такой же как и был ранее, даже немного хуже наверное, я пробелы лишним while убираю.
Code
1
2
3
4
5
6
7
8
9
10
| Method          | source   | Mean       | Error    | StdDev   | Gen0   | Allocated |
|---------------- |--------- |-----------:|---------:|---------:|-------:|----------:|
| Parse           | 1,5224V  |   126.1 ns |  0.55 ns |  0.49 ns | 0.0253 |      80 B |
| Parse_January29 | 1,5224V  | 2,555.4 ns | 15.53 ns | 14.53 ns | 0.0458 |     152 B |
| Parse           | 1,5225mV |   126.1 ns |  0.76 ns |  0.71 ns | 0.0253 |      80 B |
| Parse_January29 | 1,5225mV | 2,904.3 ns | 14.30 ns | 13.37 ns | 0.0496 |     160 B |
| Parse           | 1,526 mV |   118.8 ns |  1.60 ns |  1.50 ns | 0.0253 |      80 B |
| Parse_January29 | 1,526 mV | 2,897.1 ns | 24.23 ns | 22.66 ns | 0.0610 |     192 B |
| Parse           | 1,527 V  |   118.9 ns |  1.05 ns |  0.88 ns | 0.0253 |      80 B |
| Parse_January29 | 1,527 V  | 2,724.9 ns | 12.11 ns | 10.11 ns | 0.0572 |     184 B |
Добавлено через 1 минуту
просто я залип на это так как не верю что LINQ будет быстрее обычного кода.
0
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3463 / 2784 / 575
Регистрация: 04.09.2018
Сообщений: 8,757
Записей в блоге: 3
19.09.2024, 15:34  [ТС]
Цитата Сообщение от belalugoci Посмотреть сообщение
не верю что LINQ будет быстрее обычного кода
Ну, смотря как реализовывать..
Обычный код тоже можно "наворотить" так, что linq покажется ракетой.. Учитывая то, что в нем самом (условно) все методы "оптимизированы" до нельзя.
0
 Аватар для belalugoci
475 / 294 / 29
Регистрация: 01.06.2018
Сообщений: 3,676
19.09.2024, 15:37
wizard41, это да, но я скорее о том, что вызов и работа str.Where(char.IsLetter), str.IndexOf(unit)).TrimEnd() и double.Parse достаточно просты, но при этом напичканы кучей перевызовов и параметров, в итоге вместо N итерацией мы получаем 3*N на пустом месте. BigO не поменялся, а скорость страдает.
0
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3463 / 2784 / 575
Регистрация: 04.09.2018
Сообщений: 8,757
Записей в блоге: 3
19.09.2024, 15:40  [ТС]
belalugoci, ну ясно дело, это и по времени выполнения видно. Способ January29 пока самый медленный.
0
 Аватар для belalugoci
475 / 294 / 29
Регистрация: 01.06.2018
Сообщений: 3,676
19.09.2024, 15:42
wizard41, кстати какой возможный диапазон значений может быть? Например может быть 8437732.87235V или 0.0000000000564563563mV?
0
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3463 / 2784 / 575
Регистрация: 04.09.2018
Сообщений: 8,757
Записей в блоге: 3
19.09.2024, 15:45  [ТС]
Цитата Сообщение от belalugoci Посмотреть сообщение
какой возможный диапазон значений
"0-300" целая часть, дробная - макс 4 знака.
0
19.09.2024, 15:47

Не по теме:

Цитата Сообщение от wizard41 Посмотреть сообщение
There are only 10 types of people in the world — those who understand binary, and those who don't.
первая мысль - а кто остальные 8? но потом поржал :D

0
19.09.2024, 19:13

Не по теме:

Цитата Сообщение от wizard41 Посмотреть сообщение
Способ January29 пока самый медленный.
Печалька.

0
 Аватар для belalugoci
475 / 294 / 29
Регистрация: 01.06.2018
Сообщений: 3,676
21.09.2024, 03:49
wizard41,
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| Method       | source   | Mean      | Error    | StdDev   | Gen0   | Allocated |
|------------- |--------- |----------:|---------:|---------:|-------:|----------:|
| Parse        | 1,5224V  | 124.24 ns | 0.688 ns | 0.610 ns | 0.0253 |      80 B |
| Parse_Custom | 1,5224V  | 343.32 ns | 1.919 ns | 1.701 ns |      - |         - |
| ParseNew     | 1,5224V  | 100.31 ns | 0.032 ns | 0.027 ns |      - |         - |
| ParseNew2    | 1,5224V  |  92.37 ns | 0.249 ns | 0.221 ns |      - |         - |
 
| Parse        | 1,5225mV | 121.23 ns | 0.827 ns | 0.774 ns | 0.0253 |      80 B |
| Parse_Custom | 1,5225mV | 318.36 ns | 1.348 ns | 1.261 ns |      - |         - |
| ParseNew     | 1,5225mV |  94.87 ns | 0.216 ns | 0.192 ns |      - |         - |
| ParseNew2    | 1,5225mV |  95.35 ns | 0.322 ns | 0.301 ns |      - |         - |
 
| Parse        | 1,526 mV | 117.01 ns | 0.817 ns | 0.764 ns | 0.0253 |      80 B |
| Parse_Custom | 1,526 mV | 245.81 ns | 0.423 ns | 0.330 ns |      - |         - |
| ParseNew     | 1,526 mV |  89.29 ns | 0.405 ns | 0.379 ns |      - |         - |
| ParseNew2    | 1,526 mV |  87.88 ns | 0.149 ns | 0.132 ns |      - |         - |
 
| Parse        | 1,527 V  | 118.21 ns | 1.089 ns | 1.019 ns | 0.0253 |      80 B |
| Parse_Custom | 1,527 V  | 238.58 ns | 0.383 ns | 0.339 ns |      - |         - |
| ParseNew     | 1,527 V  |  94.12 ns | 0.097 ns | 0.076 ns |      - |         - |
| ParseNew2    | 1,527 V  |  89.02 ns | 0.118 ns | 0.092 ns |      - |         - |
Parse это тот мой метод что вы уже видели.
Parse_Custom это моя попытка сделать свой парсер. Она провалилась, нашел ошибки, исправил, и скорость выглядит убручающе.
ParseNew - это новописанный мой метод, но с парсингом double.Parse() и с обрезкой пробелов "руками"
ParseNew2 - это новописанный мой метод, но с парсингом double.Parse() и без обрезки пробелов "руками"

C#
1
2
3
4
5
6
7
8
9
10
11
    [Benchmark]
    public (double, string) ParseNew() => ParceNewMathod(source);
 
    static (double, string) ParceNewMathod(ReadOnlySpan<char> s)
    {
        int selected_unit = 0;
        if ((int)s[^2] == 109) selected_unit++;
        int overdotdigit_pos = s.Length - selected_unit - 2;
        while ((int)s[overdotdigit_pos] == 32) overdotdigit_pos--;
        return (double.Parse(s.Slice(0, overdotdigit_pos + 1)), selected_unit == 0 ? "V" : "mV");
    }
C#
1
2
3
4
5
6
7
8
9
    [Benchmark]
    public (double, string) ParseNew2() => ParceNewMathod2(source);
 
    static (double, string) ParceNewMathod2(ReadOnlySpan<char> s)
    {
        int selected_unit = 0;
        if ((int)s[^2] == 109) selected_unit++;
        return (double.Parse(s.Slice(0, s.Length - selected_unit - 1)), selected_unit == 0 ? "V" : "mV");
    }
0
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3463 / 2784 / 575
Регистрация: 04.09.2018
Сообщений: 8,757
Записей в блоге: 3
21.09.2024, 11:07  [ТС]
ну тогда делаем вывод, что стандартный double.Parse уже максимально быстрый?
0
178 / 33 / 17
Регистрация: 02.02.2014
Сообщений: 373
21.09.2024, 18:47
ну тогда делаем вывод, что стандартный double.Parse уже максимально быстрый?
Ноуп. Поковырял задачу, получилось выжать 5 наносекунд на парсинг 1 строки. Надеюсь, правильно уловил суть.

FastParser, время парсинга 1 строки:
Debug: 90 нс
Release: 78 нс

FastParser2, время парсинга 1 строки:
Debug: 47 нс
Release: 31 нс

FastParser3, время парсинга 1 строки:
Debug: 17 нс
Release: 5 нс

Во втором и третьем парсинг происходит руками, выкинуты по максимуму все вычисления + не используется арифметический сопроцессор. В третьем единицы измерения указываются как enum, а не string, что ускоряет код в 6 раз (!). Просто создание объекта string - операция, мягко говоря, небыстрая.

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

FastParser:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class FastParser
{
    static FastParser()
    {
        _isNumberOrDotArray = new bool[char.MaxValue + 1];
        for (char c = '0'; c <= '9'; c++)
            _isNumberOrDotArray[c] = true;
        _isNumberOrDotArray['.'] = true;
    }
    static readonly bool[] _isNumberOrDotArray;
 
    public (double v, string unitName)[] Parse(string[] strings)
    {
        var result = new (double v, string unitName)[strings.Length];
 
        unsafe
        {
            fixed (bool* isNumOrDot = _isNumberOrDotArray)
            {
                for (int i = 0; i < strings.Length; i++)
                {
                    string? s = strings[i];
                    var l = s.Length;
                    fixed (char* sBegin = s)
                    {
                        char* sEnd = sBegin + l;
                        char* sPtr = sEnd - 1;
                        while (sPtr >= sBegin)
                        {
                            char symbol = *sPtr;
 
                            if (isNumOrDot[symbol])
                            {
                                var numberPartOfText = new ReadOnlySpan<char>(sBegin, (int)(sPtr - sBegin + 1));
                                var number = double.Parse(numberPartOfText, CultureInfo.InvariantCulture);
                                if (symbol == ' ')
                                    sPtr++;
                                var unit = new string(sPtr + 1, 0, (int)(sEnd - sPtr - 1));
                                result[i] = (number, unit);
                                break;
                            }
                            sPtr--;
                        }
                    }
                }
            }
        }
 
        return result;
    }
}
FastParser2:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
public class FastParser2
{
    static FastParser2()
    {
        _isNumberOrDotArray = new bool[char.MaxValue + 1];
        for (char c = '0'; c <= '9'; c++)
            _isNumberOrDotArray[c] = true;
        _isNumberOrDotArray['.'] = true;
 
        //Каждому символу (не цифре, а именно символу) от '0' до '9' включительно сопоставляется некоторое число - эта цифра умножить на 1 со сколькими-то нулями
        _i4 = new int[char.MaxValue + 1];
        _i3 = new int[char.MaxValue + 1];
        _i2 = new int[char.MaxValue + 1];
        _i1 = new int[char.MaxValue + 1];
        _ones = new int[char.MaxValue + 1];
 
        //Заполняем
        for (int i = 0; i <= 9; i++)
        {
            _i1[i.ToString()[0]] = 1000 * i;
            _i2[i.ToString()[0]] = 100 * i;
            _i3[i.ToString()[0]] = 10 * i;
            _i4[i.ToString()[0]] = i;
            _ones[i.ToString()[0]] = 10 * 1000 * i;
        }
        //Всё остальное - нули
 
        //А это табличка, на вход жрёт int, на выходе даёт этот int * 0.0001. Чтобы не трогать вычисления с плавающей точкой
        _doubles_array = new double[100 * 1000 + 1];
        for (int i = 0; i < _doubles_array.Length; i++)
            _doubles_array[i] = i * 0.0001;
    }
    static readonly bool[] _isNumberOrDotArray;
 
    static readonly int[] _i4, _i3, _i2, _i1, _ones;
    static readonly double[] _doubles_array;
    
 
    public (double v, string unitName)[] Parse(string[] strings)
    {
        var result = new (double v, string unitName)[strings.Length];
 
        unsafe
        {
            //Прибиваем гвоздями наши заготовленные таблички и избегаем проверки выхода за пределы границ массива
            fixed (int* i1 = _i1)
            fixed (int* i2 = _i2)
            fixed (int* i3 = _i3)
            fixed (int* i4 = _i4)
            fixed (int* ones = _ones)
            fixed (double* doubles = _doubles_array)
            fixed (bool* isNumOrDot = _isNumberOrDotArray)
            {
                for (int i = 0; i < strings.Length; i++)
                {
                    string? s = strings[i];
                    //Берём строку
                    var l = s.Length;
                    fixed (char* sBegin = s)
                    {
                        char* sEnd = sBegin + l;
                        char* sPtr = sEnd - 1;
 
                        //Проходим от последнего до первого символа
                        while (sPtr >= sBegin)
                        {
                            if (isNumOrDot[*sPtr]) //Если символ - число или точка, значит мы прошли единицы измерения и добрались до числа
                            {
                                int number_mul_10_000 = 0;
                                string unit;
                                if (sPtr[1] == ' ') //Если символ, идущий ПОСЛЕ данного - пробел, значит имеем дело с тремя знаками после запятой
                                {
                                    unit = new string(sPtr + 2, 0, (int)(sEnd - sPtr - 2));
 
                                    //Набиваем целое число, дёргая коэффициенты из таблички, которые соответствуют нашим символам
                                    number_mul_10_000 += i3[*sPtr--]; //0.001
                                    number_mul_10_000 += i2[*sPtr--]; //0.01
                                    number_mul_10_000 += i1[*sPtr--]; //0.1
                                    sPtr--; //точка
                                    number_mul_10_000 += ones[*sPtr];
                                }
                                else  //Если символ, идущий ПОСЛЕ данного, не пробел, значит имеем дело с четырьмя знаками после запятой
                                {
                                    unit = new string(sPtr + 1, 0, (int)(sEnd - sPtr - 1));
 
                                    //Набиваем целое число, дёргая коэффициенты из таблички, которые соответствуют нашим символам
                                    number_mul_10_000 += i4[*sPtr--]; //0.0001
                                    number_mul_10_000 += i3[*sPtr--]; //0.001
                                    number_mul_10_000 += i2[*sPtr--]; //0.01
                                    number_mul_10_000 += i1[*sPtr--]; //0.1
                                    sPtr--; //точка
                                    number_mul_10_000 += ones[*sPtr];
                                }
 
                                var number = doubles[number_mul_10_000];  //Делим полученное число на 10_000 с помощью выборки готового значения
                                result[i] = (number, unit);
                                break;
                            }
                            sPtr--;
                        }
                    }
                }
            }
        }
 
        return result;
    }
}
FastParser3:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
public enum Units : int
{
    mV,
    V
}
public class FastParser3
{
    static FastParser3()
    {
        _isNumberOrDotArray = new bool[char.MaxValue + 1];
        for (char c = '0'; c <= '9'; c++)
            _isNumberOrDotArray[c] = true;
        _isNumberOrDotArray['.'] = true;
 
        //Каждому символу (не цифре, а именно символу) от '0' до '9' включительно сопоставляется некоторое число - эта цифра умножить на 1 со сколькими-то нулями
        _i4 = new int[char.MaxValue + 1];
        _i3 = new int[char.MaxValue + 1];
        _i2 = new int[char.MaxValue + 1];
        _i1 = new int[char.MaxValue + 1];
        _ones = new int[char.MaxValue + 1];
 
        //Заполняем
        for (int i = 0; i <= 9; i++)
        {
            _i1[i.ToString()[0]] = 1000 * i;
            _i2[i.ToString()[0]] = 100 * i;
            _i3[i.ToString()[0]] = 10 * i;
            _i4[i.ToString()[0]] = i;
            _ones[i.ToString()[0]] = 10 * 1000 * i;
        }
        //Всё остальное - нули
 
        //А это табличка, на вход жрёт int, на выходе даёт этот int * 0.0001. Чтобы не трогать вычисления с плавающей точкой
        _doubles_array = new double[100 * 1000 + 1];
        for (int i = 0; i < _doubles_array.Length; i++)
            _doubles_array[i] = i * 0.0001;
    }
    static readonly bool[] _isNumberOrDotArray;
 
    static readonly int[] _i4, _i3, _i2, _i1, _ones;
    static readonly double[] _doubles_array;
    
 
    public (double v, Units unitName)[] Parse(string[] strings)
    {
        var result = new (double v, Units unitName)[strings.Length];
 
        unsafe
        {
            //Прибиваем гвоздями наши заготовленные таблички и избегаем проверки выхода за пределы границ массива
            fixed (int* i1 = _i1)
            fixed (int* i2 = _i2)
            fixed (int* i3 = _i3)
            fixed (int* i4 = _i4)
            fixed (int* ones = _ones)
            fixed (double* doubles = _doubles_array)
            fixed (bool* isNumOrDot = _isNumberOrDotArray)
            {
                for (int i = 0; i < strings.Length; i++)
                {
                    string? s = strings[i];
                    //Берём строку
                    var l = s.Length;
                    fixed (char* sBegin = s)
                    {
                        char* sEnd = sBegin + l;
                        char* sPtr = sEnd - 2;
                        Units units = Units.V;
                        if (*sPtr == 'm')
                        {
                            units = Units.mV;
                            sPtr--;
                        }
                        //Проходим от последнего до первого символа
                        while (sPtr >= sBegin)
                        {
                            if (isNumOrDot[*sPtr]) //Если символ - число или точка
                            {
                                int number_mul_10_000 = 0;
                                if (sPtr[1] == ' ') //Если символ, идущий ПОСЛЕ данного - пробел, значит имеем дело с тремя знаками после запятой
                                {
                                    //Набиваем целое число, дёргая коэффициенты из таблички, которые соответствуют нашим символам
                                    number_mul_10_000 += i3[*sPtr--]; //0.001
                                    number_mul_10_000 += i2[*sPtr--]; //0.01
                                    number_mul_10_000 += i1[*sPtr--]; //0.1
                                    sPtr--; //точка
                                    number_mul_10_000 += ones[*sPtr];
                                }
                                else  //Если символ, идущий ПОСЛЕ данного, не пробел, значит имеем дело с четырьмя знаками после запятой
                                {
 
                                    //Набиваем целое число, дёргая коэффициенты из таблички, которые соответствуют нашим символам
                                    number_mul_10_000 += i4[*sPtr--]; //0.0001
                                    number_mul_10_000 += i3[*sPtr--]; //0.001
                                    number_mul_10_000 += i2[*sPtr--]; //0.01
                                    number_mul_10_000 += i1[*sPtr--]; //0.1
                                    sPtr--; //точка
                                    number_mul_10_000 += ones[*sPtr];
                                }
 
                                var number = doubles[number_mul_10_000];  //Делим полученное число на 10_000 с помощью выборки готового значения
                                result[i] = (number, units);
                                break;
                            }
                            sPtr--;
                        }
                    }
                }
            }
        }
 
        return result;
    }
}
Тест:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  //Формируем миллион чисел
  string[] million_numbers;
  {
      var list = new List<string>();
      for (int i = 0; i < 250000; i++)
      {
          list.AddRange(new string[] { "1.5224V", "1.5225mV", "1.526 mV", "1.527 V" });
      }
      million_numbers = list.ToArray();
  }
 
 
  var fp = new FastParser2(); 
  var nums = fp.Parse(million_numbers); //Чтобы jit всё прогрузил нахолодную
 
  var stopw = new Stopwatch();
  stopw.Restart();
 
 
  nums = fp.Parse(million_numbers);
 
 
  decimal nanoseconds = stopw.ElapsedMilliseconds * 1m * 1000 * 1000;
  stopw.Stop();
 
  Trace.WriteLine($"Время: {stopw.ElapsedMilliseconds} мс, то есть {(double)nanoseconds / million_numbers.Length} нс на каждый парсинг");
1
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
21.09.2024, 18:47

Строка: Есть ли способ одной строкой менять несколько букв?
var a, b : string; begin a := 'ПРИВЕТ'; b := StringReplace(a, 'П', 'В', ); b := StringReplace(a, 'Р', 'О', ); b :=...

Как преобразовать строку, удалив в ней группы цифр, а группы букв отделить пробелами. Упорядочить полученную последовательность слов
Дана текстовая строка, состоящая из букв и цифр. Преобразовать строку, удалив в ней группы цифр, а группы букв отделить пробелами....

Сумма кодов первой и последней букв: способ разрешения коллизий Бинарное дерево
Привет. Помогите, пожалуйста, с написанием программы: Тип хеш-функции (таблицы)-Сумма кодов первой и последней букв Способ разрешения...

Подсчитать число гласных и согласных букв в множестве и определить, каких букв больше
Подсчитать число гласных и согласных букв в множестве и определить, каких букв больше. Учесть, что в строке могут быть другие символы,...

Перед каждой группой одинаковых букв, вставить число, соответствующее количеству букв
Помогите пожалуйста, очень прощу! В слове, состоящим из цифр и букв и заканчивающимся точкой, перед каждой группой одинаковых букв,...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
80
Ответ Создать тему
Новые блоги и статьи
Беседа с ИИ о программистах, недопускающих к созданию и правке кода генеративные ИИ и причины этого
zorxor 21.09.2026
Раньше я радовался или получал некоторые эмоции, пусть небольшие, но всё же, от самого процесса написания кода, рекомпиляции и запуска, видя постепенное развитие программы и прочее. А теперь лень. . .
Мобильное приложение ColorStep
pavlinmavlin 17.09.2026
Реализовал приложение Красный, Зеленый, Синий в Unity3d + c#. Название изменил на ColorStep. Приложение прошло модерацию и теперь доступно для скачивания. Делал его сам, шаг за шагом — и вот,. . .
Запрет дублирования строк в табличной части
Maks 13.09.2026
Реализация из решения ниже выполнена на нетиповом справочнике "Нормы ТО" с табличной часть "Виды ТО", разработанного в КА2, со следующими реквизитами: - ВидТО (СправочникСсылка. ВидыТО); - ВидГСМ. . .
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр.
Jin X 06.09.2026
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр. Работая с форумом и нейросетями в браузере часто хочется что-то подкорректировать или добавить какого-то функционала. Ниже прикреплён. . .
Программа опроса у.з. расходомера SLS-720F
Argus19 02.09.2026
Программа опроса у. з. расходомера SLS-720F Программа опрашивает один раз в минуту три ультразвуковых расходомера SLS-720F через интерфейс RS-485 по протоколу Modbus RTU. Опрашиваются регистры. . .
Hyper-V: Компьютер должен поддерживать доверенный платформенный модуль 2.0.
Maks 31.08.2026
При установке Windows 11 на виртуальную машину Hyper-V 2-го поколения вылезла такая ошибка: Решение: в параметрах виртуальной машины, в разделе "Безопасность" (Security) активировать флаг. . .
Архитектура биовида Стива в Майнкрафте: Зачем бонобо кубический каннибализм
anaschu 30.08.2026
Кубический Вагинокапитализм в Minecraft: Математический инвариант ОДУ и рок Стивов-бонобо Главная задача разработанной «Модели Всего» — наглядно продемонстрировать наличие системной «судьбы». . .
Оттачиваю умение писать js программы.
russiannick 30.08.2026
Проектом выходного дня стало написание Книги шифров Виженера. Итогом стала версия 200, синий туман. Синий туман назван так, потому что замораживает текст под собой. Нажатие синих кнопок управляют. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru