Нейросети против Тинькоф-контеста

в 16:03, , рубрики: GigaChat, алиса, задачи, ИИ, контест, нейросети, тинькофф, чат-бот

Недавно я проходил контест задач от т-банка и это было не слишком сложно, поэтому я решил посмотреть, как наши нейросети, а именно GigaChat от сбера и алиса от яндекса(платная версия), справятся с ними. Сначала я скармливал им полный текст задачи, если были трудности или нейронка вообще не хотела работать, то я упрощал задачу убирая всю воду и формулируя задачу более прямолинейно, и снова пытался получить вразумительный ответ. Полный текст задач можно найти в интернете, вот, например. Здесь будет краткое условие. Так же тут не будет правильных решений, это можете глянуть у меня в гитхабе, если надо. Приступим-с!

Задача 1. Интервалы

Даны интервалы последовательности “1-6,8-9,11” нужно получить исходную ряд чисел «1 2 3 4 5 6 8 9 11»

Алиса
string inputString = "1-6,8-9,11"; 
string[] intervals = inputString.Split(',');
List<int> numbers = new List<int>(); 
foreach (string interval in intervals)
        {
            Match match = Regex.Match(interval, @"(d+)-(d+)");
            if (match.Success)
        {
                int start = int.Parse(match.Groups[1].Value); 
                int end = int.Parse(match.Groups[2].Value); 
                for (int i = start; i <= end; i++)
                {
                    numbers.Add(i); 
                }
            }
        }
        numbers.Sort(); 
        Console.WriteLine(string.Join(" ", numbers));

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

Сбер
string input = Console.ReadLine();
string[] intervals = input.Split(',');
List<int> numbers = new List<int>();

foreach (string interval in intervals)
{
    string[] range = interval.Split('-');
    int start = int.Parse(range[0]);
    int end = int.Parse(range[1]);
    for (int i = start; i <= end; i++)
    {
        numbers.Add(i);
    }
}

numbers.Sort();
Console.WriteLine("Отсортированный список: ");
foreach (int num in numbers)
{
    Console.Write(num + " ");
}
Console.WriteLine();

       

Тоже не учел, что интервал может быть единичный и в целом упадет на тестах, ибо даст IndexOutOfRange, после небольших правок скрипт сработал.

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

Задача 2. Падающий снег

Надо найти последовательность с которой выпадал снег, либо сказать, что данные подтасованы. Но часть измерений испорчена.

Ввод 1 3 -1 10 -1

Вывод YES 1 2 3 4 5 , то есть в первый день выпало 1 мм снега, потом 2 и тд. Соответственно, ввод подразумевает замер снега в каждый день, если заменить -1 на корректные данные то ввод такой 1 3 6 10 15

Обе нейронки не смогли понять, что от них хотят. Сбер просто сложил все числа, потом проверил равно ли это сумме всех чисел totalSnowFall == actualValues.Sum().

Сбер
for (int i = 0; i < n; i++)
        {
            if (readings[i] == "-1")
            {
                continue;
            }
            else
            {
                int snowDepth = int.Parse(readings[i]);
                totalSnowFall += snowDepth;
                actualValues.Add(snowDepth);
            }
        }

        if (totalSnowFall == actualValues.Sum())
        {
            Console.WriteLine("YES");
            Console.WriteLine(string.Join(" ", actualValues));
        }
        else
        {
            Console.WriteLine("NO");
        }
if (totalSnowFall == actualValues.Sum()) 
{ 
  Console.WriteLine("YES");
  Console.WriteLine(string.Join(" ", actualValues)); 
}

 алиса тоже решила сложить все исходные данные, заменив -1 на 1

Алиса
for (int i = 0; i < n; i++)
    {
        if (a[i] == "-1")
        {
            a[i] = 1;
        }
        Console.Write(a[i] + " ");
    }

После упрощения условия задачи, ситуация не поменялась. Так что вторая задача в целом мимо. Но не стоит винить нейронки, тут само условие было дано не очень корректно от тинькова, по тексту и заданным тестам нельзя однозначно сказать какую именно последовательность нужно указать, может ли она быть геометрической или только алгебраическая, и вообще последовательность ли, может ли там быть постоянно одно и то же число или вообще случайное. Я и сам решил эту задачу исключительно под заданные тесты и часть случаев, которым сам придумал правила поведения в этой ситуации, так как они просто не прописаны в условии. Так что не огорчайтесь, дальше условия хоть и посложнее, но более понятные и нейронки их кушают.

Задача 3. Подбор пароля

Нужно вычислить пароль из последних нажатых символов. Дана последовательность случайных букв, набор букв из которых должен состоять пароль и максимальная длина пароля, выбрать нужно максимально длинный пароль, либо сказать, что его нельзя получить(-1).
последние введенные символы: abacaba

 разрешенные символы: abc

 максимальная длинна: 4

Ответ caba

Sber
 string sequence = Console.ReadLine();
string symbols = Console.ReadLine();
int k = int.Parse(Console.ReadLine());
char[] seqSymbols = sequence.ToCharArray();
HashSet<char> usedSymbols = new HashSet<char>(seqSymbols);
HashSet<char> neededSymbols = new HashSet<char>(symbols);
List<string> possiblePasswords = new List<string>();

foreach (char firstSymbol in usedSymbols)
{
    if (usedSymbols.Count - 1 == neededSymbols.Count)
    {
        continue;
    }
    string password = BuildPassword(firstSymbol, usedSymbols, neededSymbols, k);
    if (password != null)
    {
        possiblePasswords.Add(password);
    }
}

if (possiblePasswords.Count == 0)
{
    Console.WriteLine("-1");
}
else
{
    string bestPassword = possiblePasswords.OrderByDescending(p => p.Length).ThenBy(p => sequence.IndexOf(p)).First();
    Console.WriteLine(bestPassword);
}
private static string BuildPassword(char firstSymbol, HashSet<char> usedSymbols, HashSet<char> neededSymbols, int k)
{
    if (usedSymbols.Count < neededSymbols.Count)
    {
        return null;
    }
    if (usedSymbols.Count == neededSymbols.Count && k == 1)
    {
        return firstSymbol.ToString();
    }
    for (int i = 0; i < k - 1; i++)
    {
        if (usedSymbols.Count == neededSymbols.Count)
        {
            return firstSymbol.ToString() + sequence.Substring(i + 1, k - 1);
        }
        usedSymbols.Add(sequence[i + 1]);
    }
    return null;
}

Вынес часть кода в отдельную функцию и попробовал сделать это красиво, присвоив атрибуты private static, но не учел, что так обозначаются функции, которые не взаимодействуют с внутренними данными объекта, а потом запрашивает последовательность введенных букв из основного класса. На этом ИИ не остановился, он решил использовать хешсеты, в том числе для введенных символов с клавиатуры, в итоге испортил изначальные данные. Задачу он не решил, только угадал в 2 тестах из 5 правильный ответ “-1”. Чтобы исправить этот код нужно его почти полностью переписать, заменить хешсеты на листы, например, добавить логику перебора пароля. Поэтому эту задачу тоже не засчитываю. Если упростить задачу и перефразировать, то результат тот же.

Алиса
string sequence = "abacaba";
char[] allowedChars = { 'a', 'b', 'c' };
int maxLength = 4;
// Создаём все возможные комбинации символов из последовательности
var combinations = sequence.SelectMany(c => allowedChars, (c, a) => new { c, a });
Console.WriteLine(combinations);
// Отфильтровываем комбинации, длина которых превышает максимальную длину
combinations = combinations.Where(c => c.Length <= maxLength);
// Сортируем комбинации по длине в порядке убывания
combinations = combinations.OrderByDescending(c => c.Length);
// Выбираем первую комбинацию, которая соответствует всем условиям
var result = combinations.First();
return result.c.ToString();

Мадам решила, что это задачу на комбинаторику, при этом использовала SelectMany, этим методом не получится перебрать строку на комбинации. Я уж молчу, что код вообще не компилируется из-за попытки узнать длину у char “c.Length”. А выбор нужного пароля это вообще песня

// Выбираем первую комбинацию, которая соответствует всем условиям
var result = combinations.First();

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

Тут однозначный минус, задача была максимально упрощена, но даже так алиса не смогла написать хоть что-то дельное.

Задача 4. Простые числа

Алиса
static void Main()
    {
        int n = int.Parse(Console.ReadLine()); // считываем количество процессов
        List<Process> processes = new List<Process>();
        for (int i = 0; i < n; i++)
        {
            string[] input = Console.ReadLine().Split(' ');
            int ti = int.Parse(input[0]); // время исполнения процесса
            List<int> dependencies = new List<int>();
            for (int j = 1; j < input.Length; j++)
            {
                dependencies.Add(int.Parse(input[j]));
            }
            processes.Add(new Process(ti, dependencies));
        }

        // Сортировка процессов по времени исполнения
        processes.Sort((p1, p2) => p2.Time.CompareTo(p1.Time));
        // Инициализация времени выполнения всех процессов
        long totalTime = 0;
        foreach (Process process in processes)
        {
            totalTime += process.Time;
        }
        // Обработка зависимостей
        for (int i = 0; i < n; i++)
        {
            Process currentProcess = processes[i];
            if (currentProcess.Dependencies.Count == 0) // Если процесс не зависит от других
            {
                totalTime -= currentProcess.Time; // Уменьшаем общее время выполнения на время выполнения текущего процесса
            }
            else
            {
                foreach (int dependency in currentProcess.Dependencies) // Проверяем зависимости
                {
                    Process dependencyProcess = processes[dependency - 1];
                    if (dependencyProcess.Time > totalTime) // Если время выполнения зависимости больше общего времени выполнения
                    {
                        totalTime = dependencyProcess.Time; // Обновляем общее время выполнения
                        break; // Переходим к следующему процессу
                    }
                }
            }
        }
        Console.WriteLine(totalTime); // Выводим минимальное время выполнения всех процессов
    }
}

class Process
{
    public long Time { get; set; }
    public List<int> Dependencies { get; set; }

    public Process(long time, List<int> dependencies)
    {
        Time = time;
        Dependencies = dependencies;
    }
}

Она решила схалтурить и написала код, который работает, но крайне редко как надо. Вот, например, как она определяет простоту числа

static bool IsPrime(int divisorCount)
{
    return divisorCount == 2;
}

Или вот функция для определения составного числа (да это 2 разных функции)

static bool IsComposite(int num)
{
    return num > 1 && num % 2 == 0
        || num % 2 != 0 && num % 3 == 0
        || num % 5 == 0
        || num % 7 == 0; 
}

Если с первой функцией вс понятно, так можно определить только четное число(которое в целом не является простым), то во второй функции вариантов, что она сработает больше, однако, такой подход не учитывает редкие случаи, например, 121 у которого делители это 1, 11, 121.

Соответственно, 4 теста из 6 провалены, как то переписывать и доделывать этот код тоже смысла нет, так тут пропущена основная суть задачи: написать эффективный скрипт определения простоты числа.

Сбер
 static void Main()
    {
        int l = int.Parse(Console.ReadLine());
        int r = int.Parse(Console.ReadLine());
        int count = 0;
        for (int i = l; i <= r; i++)
        {
            int divisorsCount = CountDivisors(i);
            if (IsPrimeNumber(divisorsCount))
            {
                count++;
            }
        }

        Console.WriteLine(count);
    }

    private static int CountDivisors(int number)
    {
        int count = 0;
        for (int i = 1; i * i <= number; i++)
        {
            if (number % i == 0)
            {
                count++;
                if (i != number / i)
                {
                    count++;
                }
            }
        }
        return count;
    }

    private static bool IsPrimeNumber(int number)
    {
        if (number <= 1)
        {
            return false;
        }
        if (number <= 3)
        {
            return true;
        }
        if (number % 2 == 0 || number % 3 == 0)
        {
            return false;
        }
        for (int i = 5; i * i <= number; i += 6)
        {
            if (number % i == 0 || number % (i + 2) == 0)
            {
                return false;
            }
        }
        return true;
    }
}

Если алиса хотя бы 2 теста угадала, то тут попадание 0 из 6, но если добавить одно условие, которое ИИ не учел, то всё заработает и даже пройдет все тесты.

if (IsPrimeNumber(i))
        {
            continue;
        }

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

Далее идут довольно объемные задачи сами по себе, я изначально не надеялся что их смогут решить, поэтому код приводить не буду, только вырезки

Задача 5. Хакеры

Нужно было распарсить результаты соревнования хакеров, где даны данные по запросам к целевым серверам "Team1 01:10:00 A FORBIDEN". То есть имя команды, время, название сервера, результат взлома. Дается n таких запросов, нужно выявить кто победил и вывести таблицу результатов.

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

Алиса отказалась решать какие то там задачи, не царское это дело. Платная версия, что поделать...

Задача 6. Графы

Есть граф процессы зависимые друг от друга, нам дано какие процессы от кого зависят, и сколько времени занимает каждый из них. Нужно вычислить минимальное время, когда все процессы завершатся.

Сбер
            void Visit(int node)
            {
                if (visitedNodes.Contains(node))
                {
                    return;
                }
                visitedNodes.Add(node);
                foreach (var childNode in dependencyGraph[node])
                {
                    Visit(childNode);
                }
                stack.Push(node);
            }

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

А потом еще вот такую строчку выдает

var pathTimes = criticalPathTimes.Select(times => times.Aggregate(0L, (acc, next) => acc + criticalPathTimes[next].Aggregate(0L, (sum, t) => sum + criticalPathTimes[t].Aggregate(0L, (innerSum, _) => innerSum + t)))).ToList();

Выглядит ужасно, ещё и не работает. Даже если заменить вызов элемента у хешлиста по индексу criticalPathTimes[next] на criticalPathTimes.ElementAt(next) то это всё ещё не будет работать, так как сам граф составлен неверно.

С хешесетами как всегда проблемы, с рекурсией тем более

Алиса
        public string main(string inputing)
        {
            var inputString = inputing.Split("_");
            int n = int.Parse(inputString[0]);// считываем количество процессов
            List<Process> processes = new List<Process>();
            for (int i = 1; i < n; i++)
            {
                string[] input = inputString[i].Split(' ');
                int ti = int.Parse(input[0]); // время исполнения процесса
                List<int> dependencies = new List<int>();
                for (int j = 1; j < input.Length; j++)
                {
                    dependencies.Add(int.Parse(input[j]));
                }
                processes.Add(new Process(ti, dependencies));
            }
            // Сортировка процессов по времени исполнения
            processes.Sort((p1, p2) => p2.Time.CompareTo(p1.Time));

            // Инициализация времени выполнения всех процессов
            long totalTime = 0;
            foreach (Process process in processes)
            {
                totalTime += process.Time;
            }
            // Обработка зависимостей
            for (int i = 0; i < n; i++)
            {
                Process currentProcess = processes[i];
                if (currentProcess.Dependencies.Count == 0) // Если процесс не зависит от других
                {
                    totalTime -= currentProcess.Time; // Уменьшаем общее время выполнения на время выполнения текущего процесса
                }
                else
                {
                    foreach (int dependency in currentProcess.Dependencies) // Проверяем зависимости
                    {
                        Process dependencyProcess = processes[dependency - 1];
                        if (dependencyProcess.Time > totalTime) // Если время выполнения зависимости больше общего времени выполнения
                        {
                            totalTime = dependencyProcess.Time; // Обновляем общее время выполнения
                            break; // Переходим к следующему процессу
                        }
                    }
                }
            }
            return totalTime.ToString(); // Выводим минимальное время выполнения всех процессов
        }
    }
    class Process
    {
        public long Time { get; set; }
        public List<int> Dependencies { get; set; }

        public Process(long time, List<int> dependencies)
        {
            Time = time;
            Dependencies = dependencies;
        }
    }

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

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

ИТОГО:

В целом нейронки не так плохо справились, даже пару задач решили, почти без помощи. У них довольно хорошо получается писать алгоритмы, но с пониманием задачи есть трудности. Алису, например, приходилось каждый раз упрашивать написать мне код. Сбер же в этом плане чуть более сговорчивый, но иногда просто уверено говорит неправильный ответ.
Однако если сравнивать их, то сбер показал себя гораздо лучше, он пишет более грамотное решение, не пытается пойти в лоб и старается использовать технологии языка.
Так что ИИшки можно использовать, но максимально упрощать для них задачу, сводя её к оптимизации конкретного действия или алгоритма, а не всей задачи сразу.

Автор: Flild

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js