А точнее — неспособностью анализировать алгоритмы. Вот, из последнего:
Анализируем функцию
функция
private static void FoundKey(string source, int index, LinkedList<string> keys, ref string foundKey)
{
char ch = source[index];
var node = keys.First;
while (node != null)
{
var key = node.Value;
var endOfKeyReached = index + 1 == key.Length;
var lastCharsEquals = key[index] == ch;
if (endOfKeyReached && lastCharsEquals)
foundKey = key;
if (false == lastCharsEquals
|| endOfKeyReached)
{
keys.Remove(node);
}
node = node.Next;
}
}
Спрашиваю про проблемы производительности.
Q:
Насколько эта функция эффективна в плане скорости и потребления памяти? Можно ли эффективнее?
получаю вот такой ответ
Проблемы текущей реализации:
1. Сложность O(n²) в худшем случае
Для каждого символа строки (source) вы проходите по всем ключам (keys)
Если ключи длинные, это становится очень медленно
2. Неэффективная структура данных
LinkedList — медленный доступ, нет быстрого поиска
Удаление из середины списка — O(n) операций
3. Избыточные проверки
var endOfKeyReached = index + 1 == key.Length;
var lastCharsEquals = key[index] == ch;
Вы проверяете ВСЕ ключи на каждом символе, даже те, которые уже не могут совпасть.
И это сразу
фэйл!
Потому что в процессе выполнения алгоритма не будет продвижения по source дальше чем длинна самого длинного ключа, т.о. сложность составляет O(L × n), где L — длина самого длинного ключа, n — количество ключей
Обоснование:
Алгоритм проверяет только первые L символов source — именно поэтому длинна source в этой формуле не участвует.
После позиции L-1 все ключи либо найдены, либо отсеяны — дальнейшие символы source не обрабатываются
Удаление из середины списка — O(n) операций
Двойной фэйл!
Remove(LinkedListNode<T>) имеет сложность O(1) — он его перепутал с методом bool Remove(T value), который действительно имеет сложность O(n)
Спрашиваю, изменится ли ситуация, если удаление из середины будет O(1).
Далее, напоминаю, что ключи таки удаляются:
Q:
Пункт 1 — неверно: заведомо несовпадающие ключи удаляются. Посмотри на эти строки
if (false == lastCharsEquals
|| endOfKeyReached)
{
keys.Remove(node);
}
Подумай ещё раз.
A:
Вы абсолютно правы! Я был невнимателен. Давайте переанализируем.
и снова фэйл!
Вы абсолютно правы! Я был невнимателен. Давайте переанализируем.
## Текущий алгоритм работает умнее, чем я initially предположил:
// Ключ НЕ совпал по текущему символу ИЛИ ключ полностью найденif (false == lastCharsEquals || endOfKeyReached)
{
keys.Remove(node); // Удаляем невалидные ключи сразу!
}
**Фактически алгоритм делает:**
1. Проверяет текущий символ для каждого активного ключа
2. Если символ не совпадает — ключ удаляется
3. Если ключ полностью найден — он тоже удаляется
4. Только ключи, которые всё ещё могут совпасть, остаются в списке
## Эффективность с O(1) удалением:
С O(1) удалением из LinkedList этот алгоритм становится **довольно эффективным**!
**Сложность:** O(m × k), где:
— m — длина строки `source`
— k — среднее количество **активных** ключей (тех, что ещё могут совпасть)
В худшем случае (все ключи активны до конца) — O(m × n), но на практике k быстро уменьшается.
## Преимущества текущего подхода:
1. **Автоматическая фильтрация** — невалидные ключи удаляются сразу
2. **Минимальная память** — хранятся только строки, нет дополнительных структур
3. **Простота** — легко понять и поддерживать
## Единственные возможные улучшения:
private static void FoundKeyOptimized(string source, int index, LinkedList<string> keys, ref string foundKey)
{
char ch = source[index];
var node = keys.First;
while (node != null)
{
var key = node.Value;
var nextNode = node.Next; // Сохраняем ДО возможного удаления
// Более читаемая проверкаbool charMatches = key[index] == ch;
bool isComplete = index + 1 == key.Length;
if (isComplete && charMatches)
{
foundKey = key;
keys.Remove(node);
}
else if (!charMatches)
{
keys.Remove(node);
}
// Если charMatches И НЕ isComplete - ключ остается для след. итерации
node = nextNode;
}
}
**Вывод:** С O(1) удалением ваш алгоритм уже достаточно эффективен для большинства практических случаев! Основной недостаток — сложность понимания логики, а не производительность.
Извините за первоначальную невнимательность — ваш код умнее, чем я его оценил.
....Извините за первоначальную невнимательность — ваш код умнее, чем я его оценил.
Тащемта, снова косяк с анализом алгоритма!
**Сложность:** O(m × k), где:
— m — длина строки `source`
— k — среднее количество **активных** ключей (тех, что ещё могут совпасть)
Это неправильно. Я чуть выше писал как правильно.
Ну и ещё — вот на эту ошибку в коде мне можно было бы и указать.
keys.Remove(node);
}
node = node.Next;
Ошибка в том, что после того как ноду удалили из списка, из неё нельзя доставать node.Next — это особенности реализации (она инвалидируется — node.Next после этого null).
Есть у него такое. Ф>А точнее — неспособностью анализировать алгоритмы. Вот, из последнего:
Если твой пост не просто поругать дипсик, то посмотри (сам не разбирал), что ответил Qwen — тоже бесплатный и тоже китайский:
Анализ текущей реализации
Ваша функция FoundKey реализует фильтрацию списка ключей (LinkedList<string> keys) по текущему символу ch на позиции index, удаляя несовпадающие или уже полностью проверенные ключи и сохраняя первый полностью совпавший как foundKey.
Рассмотрим её эффективность с точки зрения скорости и потребления памяти, а также предложим возможные улучшения.
🔍 Анализ текущей реализации
✅ Что делает функция:
Проходит по всем ключам в keys.
Для каждого ключа проверяет:
Совпадает ли символ на позиции index с ch.
Достигнут ли конец ключа (index + 1 == key.Length).
Если ключ полностью совпал — присваивает его foundKey.
Удаляет из списка все ключи, которые:
Не совпали по символу ИЛИ
Уже достигли конца (даже если совпали).
⚠️ Примечание: удаление совпавшего ключа после присвоения — ок, но если в списке несколько ключей одинаковой длины и значения, то найдётся только первый.
⏱️ Скорость (временная сложность)
Проход по всем ключам: O(N), где N — количество ключей в списке.
Удаление из LinkedList<T> по узлу — O(1).
Доступ к символу в строке key[index] — O(1).
Итого: O(N) на вызов.
Но! Если вызывать эту функцию символ за символом (например, при вводе текста), и каждый раз фильтровать оставшиеся ключи — это нормально, НО:
LinkedList — очень плохой выбор для частого перебора, т.к. кэш-неэффективен.
Удаление из середины — да, O(1), но итерация с удалением в LinkedList — медленная из-за отсутствия локальности данных.
💾 Память
Хранение LinkedList<string> — накладные расходы на каждый узел (~32+ байт на узел в .NET).
Строки могут быть общими (интернированными), но это не гарантируется.
При большом количестве ключей (N > 1000) LinkedList проигрывает List<T> по скорости итерации в 2–5 раз.
Вариант 1: Заменить LinkedList<string> на List<string>
💡 Вариант 1: Заменить LinkedList<string> на List<string>
private static void FoundKey(string source, int index, List<string> keys, ref string foundKey)
{
char ch = source[index];
for (int i = keys.Count - 1; i >= 0; i--)
{
var key = keys[i];
if (index >= key.Length) // уже проверили этот ключ полностью?
{
keys.RemoveAt(i);
continue;
}
bool matches = key[index] == ch;
if (!matches)
{
keys.RemoveAt(i);
}
else if (index + 1 == key.Length)
{
foundKey = key; // можно break, если нужен только первый
keys.RemoveAt(i); // или оставить, если нужны все совпадения
}
}
}
Плюсы:
Лучшая локальность памяти → быстрее итерация.
Меньше накладных расходов.
RemoveAt(i) при обратном проходе — безопасно и эффективно.
Минусы:
Удаление из середины List<T> — O(N), но при обратном проходе это нормально, и если удаляется много элементов — это не хуже LinkedList.
На практике List<T> почти всегда быстрее, чем LinkedList<T>, особенно при N < 10 000.
💡 Вариант 2: Использовать префиксное дерево (Trie)
Дальше копировать не стал, если интересно сам попробуй.
Вывод
### 📌 Вывод
| Аспект | Текущая реализация | Улучшение |
|------------------|------------------------------------------------------------|--------------------------------------|
| Скорость | Умеренная (O(N), но медленная итерация из-за `LinkedList`) | Замена на `List<T>` → 2–5× ускорение |
| Память | Высокие накладные расходы на узлы | `List<T>` — меньше overhead |
| Масштабируемость | Плохая при N > 1000 | Trie — линейная от длины ключа |
| Читаемость | Средняя | Улучшается с `List` или `Trie` |
> ✅ **Рекомендация**: > — Если у вас **мало ключей** (<100) — замените `LinkedList` на `List`. > — Если **много ключей** или это **часть парсера/автодополнения** — реализуйте **Trie**. > — Избегайте модификации исходной коллекции, если это не требуется семантикой.
Если подскажете контекст использования (автодополнение? парсинг команд? что за `source` и `keys`?), могу предложить оптимальное решение под ваш сценарий.
Re: Задолбал Dick Seek со своей "невнимательностью"
Здравствуйте, Философ, Вы писали:
Ф>Потому что в процессе выполнения алгоритма не будет продвижения по source дальше чем длинна самого длинного ключа, т.о. сложность составляет O(L × n), где L — длина самого длинного ключа, n — количество ключей
Я вообще продвижения по source не вижу, проверяется лишь символ номером index
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re: Задолбал Dick Seek со своей "невнимательностью"
Здравствуйте, Философ, Вы писали:
Ф>Remove(LinkedListNode<T>) имеет сложность O(1) — он его перепутал с методом bool Remove(T value), который действительно имеет сложность O(n)
А ты исходник Remove да и самого LinkedListNode дал? Или надеешься, что он знает всё в мире?
Я тебе без исходников тоже предположил бы, что LinkedListNode — это однонаправленный список у которого O(n)в худшем случае.
В общем, надо понимать, что:
1. ИИ не всезнающие. Им нужен весь код на котором нужно делать предположения.
2. У ИИ нет опыта реального выполнения код и он может что-то теоретически сказать.
3. Как и люди, ИИшки склонны к поверхностным выводам на основе названий методов и типов. Их нужно подталкивать для более глубокого анализа.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Задолбал Dick Seek со своей "невнимательностью"
Здравствуйте, VladD2, Вы писали:
VD>А ты исходник Remove да и самого LinkedListNode дал? Или надеешься, что он знает всё в мире?
VD>Я тебе без исходников тоже предположил бы, что LinkedListNode — это однонаправленный список у которого O(n)в худшем случае.
Вот анализ от Gemini (прямо из гугл-поиска): Оценка производительности кода: Квадратичная сложность O(n²)
Данный код имеет квадратичную сложность O(n²) в худшем случае, где 'n' — количество элементов в списке keys.
Итерация по списку дает базовую сложность O(N).
Ключевая проблема: Хотя keys.Remove(node) выполняется за O(1) (константное время, так как передается сам узел), алгоритм модифицирует список во время итерации.
При многократном вызове метода общая производительность становится очень низкой.
Код неэффективен из-за использования LinkedList для такой операции.
Рекомендация: Использовать более эффективные структуры данных, такие как HashSet<string> или Dictionary<string, ...>, или специализированные структуры вроде Trie (префиксное дерево), что позволило бы достичь эффективности O(1) для поиска и удаления.
Пепел анализом я его заставил найти код Remove. Потому он делает правильные предположения о сложности удаления.
И в общем, я с его выводами согласен. Хотя твой приведенный фрагмент и O(N), но он же ведь явно где-то еще вызывается.
И код явно дерьмовый. Какие ref (причем без указания на то нашелся узел или нет) и "false ==". LinkedList какой-то. В общем, дичь какая-то.
Почему, действительно, не использовать HashSet или словарь?
Покажи код взывающий эту функцию.
Вот этот же index он не из воздуха берётся? Его же по этому списку вычисляют? Ну вот тебе и низкая производительность.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Задолбал Dick Seek со своей "невнимательностью"
Здравствуйте, T4r4sB, Вы писали:
Ф>>Потому что в процессе выполнения алгоритма не будет продвижения по source дальше чем длинна самого длинного ключа, т.о. сложность составляет O(L × n), где L — длина самого длинного ключа, n — количество ключей
TB>Я вообще продвижения по source не вижу, проверяется лишь символ номером index
Однако, согласись, что его там логично предполагать.
Всё сказанное выше — личное мнение, если не указано обратное.
Re[2]: Задолбал Dick Seek со своей "невнимательностью"
Здравствуйте, VladD2, Вы писали:
VD>Я тебе без исходников тоже предположил бы, что LinkedListNode — это однонаправленный список у которого O(n)в худшем случае.
WAT!??? Удаление из связного списка ноды — O(n)?????
VD>В общем, надо понимать, что: VD>1. ИИ не всезнающие. Им нужен весь код на котором нужно делать предположения.
А они знают что такое связный список?
Всё сказанное выше — личное мнение, если не указано обратное.
Re[2]: Задолбал Dick Seek со своей "невнимательностью"
Вполне разумный анализ. Думаю, что если ты и в силах сделать такой же, то уйдет у тебя на это не один день и ты будешь вынужден ознакомиться с кучей алгоритмов и их вычислительной сложности.
Почему такой большой и сложный код ДипСик проанализировал довольно качественно, а на твоём споткнулся? А вот именно потому, что ты вырвал свой код из контекста и не предоставил ИИшке деталей реализации. А я ему дал весь нужный код.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Задолбал Dick Seek со своей "невнимательностью"
Здравствуйте, VladD2, Вы писали:
VD>Вот анализ от Gemini (прямо из гугл-поиска): VD>Оценка производительности кода: Квадратичная сложность O(n²)
VD>
VD>Данный код имеет квадратичную сложность O(n²) в худшем случае, где 'n' — количество элементов в списке keys. VD>... VD>
мдеее..
VD>Пепел анализом я его заставил найти код Remove.
Зачем? Базовые ADT не нуждаются в коде — это же первый семестр АЯП...
VD>И в общем, я с его выводами согласен. Хотя твой приведенный фрагмент и O(N), но он же ведь явно где-то еще вызывается.
Безусловно вызывается
VD>И код явно дерьмовый. Какие ref (причем без указания на то нашелся узел или нет) и "false ==". LinkedList какой-то. В общем, дичь какая-то.
Ты LinkedList никогда не видел? У меня был выбор: 1) и передавать старый , и возвращать новый 2) сделать через ref.
Сделал через ref потому что там пофигу, изменила ли функция ключ, или нет. Важно толь то, есть ли хотя-бы какой-нибудь.
VD>Почему, действительно, не использовать HashSet или словарь?
Потому что HashSet более тяжеловесный — по крайней мере он опирается на хэш, который тут нафиг не упал, и добавление O(1) он не гарантирует. Потому что заранее известно, что ключи уникальны и как-либо гарантировать их уникальность не нужно.
VD>Покажи код взывающий эту функцию.
Пока не могу — кода на работе. Завтра покажу.
VD>Вот этот же index он не из воздуха берётся? Его же по этому списку вычисляют? Ну вот тебе и низкая производительность.
Это индекс очередного символа в source — само собой там символы из source итерируются.
Всё сказанное выше — личное мнение, если не указано обратное.
Re[2]: Задолбал Dick Seek со своей "невнимательностью"
Здравствуйте, VladD2, Вы писали:
VD>Почему такой большой и сложный код ДипСик проанализировал довольно качественно, а на твоём споткнулся? А вот именно потому, что ты вырвал свой код из контекста и не предоставил ИИшке деталей реализации. А я ему дал весь нужный код.
А как я могу судить о качестве анализа, если не имею возможности читать то, что анализировалось? Файлы, которые ты ему скормил, он назад не отдаёт.
Всё сказанное выше — личное мнение, если не указано обратное.
Re[3]: Задолбал Dick Seek со своей "невнимательностью"
Здравствуйте, Философ, Вы писали:
N>>...Это ты в режиме DeepThink спрашивал? Ф>Нет, в обычном режиме.
Современные LLM однопроходные. Есть эмпирическое правило. Если ты их спрашиваешь о каком-либо факте, то запускать в обычном режиме. Если требуется хотя бы один откат назад — в глубокой. При анализе кода — требуется.
С ребенком недавно решали задачу:
Известно, что ЖЖ + Ж = МЁД.
Одинаковыми буквами обозначены одинаковые цифры, разными — разные.
На какую цифру оканчивается произведение:
В · И · Н · Н · И · П · У · Х?
В обычном режиме не решает, в глубоком — легко.
Re[3]: Задолбал Dick Seek со своей "невнимательностью"
Здравствуйте, Философ, Вы писали:
Ф>А как я могу судить о качестве анализа, если не имею возможности читать то, что анализировалось? Файлы, которые ты ему скормил, он назад не отдаёт.
Я ему отдал файлы из проекта: https://github.com/rsdn/CsNitra/
Сам парсер и примеры (тесты). Сначала только один, потом еще два.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Задолбал Dick Seek со своей "невнимательностью"
А что "мдеее". Он же тебе пояснил почему? Это не весь код. А если представить код взывающий эту функцию там квадрат неминуемо выходит.
Ф>Зачем?
Чтобы он понимал как устроен этот метод. Ваш КО
Ф>Базовые ADT не нуждаются в коде — это же первый семестр АЯП...
Я тебе уже отвечал, что по одному названию можно предположить, что список однонаправленный. Не факт, что при обучении ИИ скормили стандартную библиотеку дотнета.
Могу еще раз тебе подчеркнуть, что если ты хочешь качественной работы любого ИИ, ты должен дать весь необходимый для анализа код или в промте уточнить детали. Иначе они (как и люди) будут вынуждены домысливать и принимать решения на базе названий и предоложений.
Ф>Безусловно вызывается
Ну вот вместе с ним квадрат и выходи. 21 век на дворе, а тут какие-то переборные алгоритмы.
Ф>Ты LinkedList никогда не видел? У меня был выбор: 1) и передавать старый , и возвращать новый 2) сделать через ref. Ф>Сделал через ref потому что там пофигу, изменила ли функция ключ, или нет. Важно толь то, есть ли хотя-бы какой-нибудь.
Я с многими ФЯ работал где простой односвязанный список базовая конструкция. Про LinkedList знаю. Но уже забыл, что он двунаправленный.
В реальной жизни, в C# или Nemerle он мне ни разу не понадобился.
Судя по source у тебя там какой-то парсинг, который выявляет некие ключи в тексте. Скорее всего эффективнее будет использовать какие-то стандартные алгоритмы парсинга, а ключи заложить в дикшонари или хэш-сет. Именно это и подсказывают все ИИ.
Ф>Потому что HashSet более тяжеловесный — по крайней мере он опирается на хэш, который тут нафиг не упал, и добавление O(1) он не гарантирует. Потому что заранее известно, что ключи уникальны и как-либо гарантировать их уникальность не нужно.
Чушь полная. HashSet и Dictionary очень даже лехковесный. Данные там хранятся в одном списке, а доступ идет за ~константное время. Для современных процессоров LinkedList вообще не лучший выбор, так как ухудшает локальность памяти (узлы шире и могут располагаться в разных участках памяти).
Ф>Это индекс очередного символа в source — само собой там символы из source итерируются.
Вот именно! И там явно перебор еще один. А два вложенных цикла — это O(n²)
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Задолбал Dick Seek со своей "невнимательностью"
Здравствуйте, Философ, Вы писали:
Ф>WAT!??? Удаление из связного списка ноды — O(n)?????
А что ты хотел для односвязенного списка? В нём хранится только ссылка на следующий элемент.
И в отличии от двунаправленного списка — это очень полезная конструкция, она неизменяемая и позволяет почти бесплатно порождать версии списков (леса). Плюс он легко выражается в виде АлгТД и отлично разбирается паттерн-матчингом в ФЯ.
public variant list[T] :
SCG.IEnumerable[T],
Nemerle.Collections.ICovariantList[T],
System.IEquatable[list[T]]
{
| Cons
{
head : T;
tail : list [T];
}
| Nil
}
А вот полезность LinkedList (дотнетного) заканчивается тем самым константным удалением (и вставкой в произвольное место). Во всём остальном он плох.
Ф>А они знают что такое связный список?
Да, но я тебя выше описал, что они под этим понимают по умолчанию. Я вот тоже давно не видел LinkedList и тоже забыл, что он двусвязный. Из названия это ведь не вытекает.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Задолбал Dick Seek со своей "невнимательностью"
Даже если ИИшка знакома с документацию или даже реализацией, он ИИшка. Она подтягивает разные знания. Если ты акцентируешь её "внимание" на чем-то, получишь лучший результат. Проще того же ДипСика заставить это обяснять: https://chat.deepseek.com/share/i8esnh5kcxslmaa266
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.