Многие (особенно в постсоветских странах) относятся к списыванию довольно беззаботно. Ученики в школах, студенты в университетах, а затем и специалисты в своей работе заимствуют чужие идеи и решения, не чувствуя вины за обман. Между тем плагиат — это не безобидное «подумаешь, списал», а серьезная проблема, которая ведет к мошенничеству и коррупции [1, 2]. 

Существует множество инструментов, направленных на поиск плагиата в текстах, изображениях и промышленном коде, которые показывают отличные результаты. Но в программировании есть область — решение олимпиадных задач — где применение этих инструментов никогда не изучали. В посте я расскажу об одном из самых перспективных алгоритмов поиска плагиата GPLAG и как я исследовала его применимость в олимпиадном программировании.

Привет, Хабр! Меня зовут Карина Анисимова, я третьекурсница бакалаврской программы «Прикладная математика и информатика» петербургского кампуса НИУ ВШЭ. 

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

Когда этой осенью пришло время выбирать тему для научно-исследовательской работы, я решила, что хочу заняться чем-то, что принесет пользу людям. Поэтому меня заинтересовал проект «Поиск списываний в контестах по программированию» под руководством Александра Садовникова, аналитика Сириус.Курсов. Я была наслышана о серьезности проблемы списывания, и считаю, что даже приблизиться к ее решению было бы прекрасным вкладом в развитие олимпиадного движения.

Чем особенна задача поиска плагиата в контестах?

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

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

При попытках выявления плагиата в коде как правило выделяют пять видов основных модификаций:

  1. добавление или удаление комментариев;

  2. добавление незначимых строк кода;

  3. переименование переменных;

  4. перемещение выражений без изменения семантики;

  5. замена и видоизменение конструкций управления. Например, замена цикла for на цикл while или изменение условия внутри конструкции if else.

Покажу на примерах. Исходный код:

#include <iostream>
using namespace std;

int main()
{
	int a = 5, b = 2, c = 0;
	for (int i = 0; i < a; i++) {
		c += i;
		cout << i;
	}
	cout << c + a + b;
	return 0;
}
Добавление или удаление комментариев:
#include <iostream>
using namespace std;

int main()
/* comment */ {
	int a = 5, b = 2, c = 0;
	for (int i = 0; i < a; i++) {
		// comment
		c += i;
		cout << i;
	}
	cout << c + a + b;
	return 0;
}
Добавление незначимых строк кода:
#include <iostream>
using namespace std;

int main()
/* comment */ {
	int a, b, c, d;
	a = 5;
	b = 2;
	c = 0;
	d = 42;
	for (int i = 0; i < a; i++) {
		// comment
		c += i;
		cout << i;
	}
	int ans = c + a + b;
	cout << ans;
	return 0;
}
Переименование переменных:
#include <iostream>
using namespace std;

int main()
/* comment */ {
	int x, y, z, t;
	x = 5;
	y = 2;
	z = 0;
	t = 42;
	for (int j = 0; j < x; j++) {
		// comment
		z += j;
		cout << j;
	}
	int out = z + x + y;
	cout << out;
	return 0;
}
Перемещение выражений без изменения семантики:
#include <iostream>
using namespace std;

int main()
/* comment */ {
	int x = 5, y, z, t;
	z = 0;
	y = 2;
	for (int j = 0; j < x; j++) {
		// comment
		cout << j;
		z += j;
	}
	int out = z + x + y;
	cout << out;
	t = 42;
	return 0;
}
Замена и видоизменение конструкций управления:
#include <iostream>
using namespace std;

int main()
/* comment */ {
	int x = 5, y, z, t, j = 0;
	z = 0;
	y = 2;
	while (j < x) {
		// comment
		cout << j;
		z += j;
		j++;
	}
	int out = z + x + y;
	cout << out;
	t = 42;
	return 0;
}

Существующие аналоги

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

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

Следующий подход основан на сравнении токенов. Он предполагает токенизацию исходных программ и дальнейшее сравнение последовательности токенов. Данный подход наиболее популярен среди существующих инструментов для выявления плагиата, одни из известных инструментов, использующие данный подход, это SIM  и MOSS.

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

В таблице представлено сравнение различных подходов к выявлению плагиата при разных модификациях:

Абстрактные синтаксические деревья

Токены

Графы зависимостей программ

Добавление/ удаление комментариев

+

+

+

Переименование переменных

+

+

+

Добавление незначимых строк кода

-

-

+

Перемещение выражений

-

±

+

Видоизменение конструкций управления

-

±

+

Граф зависимостей программ

Граф зависимостей программ – это представление программы в виде графа, вершинами которого являются базовые выражения. 

Существует два вида ребер:

  • Ребра зависимости по данным соединяют вершины, в которых используются одинаковые данные. 

  • Ребра передачи управления соединяют две вершины, если контролирующая вершина определяет, будет ли выполняться выражение в зависимой вершине. 

Например, для короткой программы:

program
	x := 5
	y := 5
	a := 1
	while a < 5 do
		x := x + y
		y := x - y
		a := a + 1
	end
end(x, y, a)

Граф зависимостей будет выглядеть так:

Жирные стрелки – ребра передачи управлений, тонкие –  ребра зависимости по данным
Жирные стрелки – ребра передачи управлений, тонкие –  ребра зависимости по данным

Цель работы

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

Целью моего исследования была оценка применимость алгоритма GPLAG к решению задачи поиска контестного плагиата.

Было выделено три основные задачи: 

  • Реализация алгоритма из статьи GPLAG;

  • Сбор датасета для проверки применимости к решению задачи выявления контестного плагиата;

  • Проведение тестирования и анализ работы полученного решения.

Описание алгоритма

Алгоритм состоит из трех основных этапов. 

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

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

Визуализация алгоритма (собственный рисунок)
Визуализация алгоритма (собственный рисунок)

Построение графа

Для реализации первого этапа алгоритма нужно было научиться строить граф зависимостей программ. Для этого существует довольно много готовых инструментов, например Progex и TinyPDG. Однако оба этих инструмента работают только с  Java, и добавить другой язык программирования оказалось либо проблематично, либо вовсе невозможно. Поэтому я выбрала инструмент Joern. Он предоставляет возможность работы с C/C++ и Java, что сразу добавляет гибкости моей реализации, потому что охватывает наиболее популярные для решения контестных задач языки.

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

Сравнение графов

Следующий этап – это сравнение графов. Он оказался наиболее сложным для реализации. Задача поиска изоморфизмов на подграфах np-полная, но есть известные решения, которые выдают неплохую скорость работы. Наиболее распространенным сейчас является алгоритм VF2, он реализован в большинстве библиотек для работы с графами. Однако выяснилось, что он решает задачу поиска изоморфизмов вида граф-подграф, то есть во втором графе ищется подграф, изоморфный первому. А для моей задачи требуется алгоритм вида подграф-подграф, то есть поиск всех изоморфных пар подграфов. 

Я пробовала изучать другие алгоритмы, например алгоритм Ульмана, но сталкивалась с такой же проблемой. Тогда я решила свести задачу к поиску изоморфизмов вида граф-подграф. Для этого нужно у одного из графов построить все подграфы, а затем для каждой пары «подграф первого графа» и «второй граф» можно искать изоморфизмы вида граф-подграф известными алгоритмами. 

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

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

После изучения большого количества графов я заметила одну особенность. При построении появляется очень много подграфов, которые выглядят как несколько абсолютно не связанных частей программы и вершина, соответствующая точке входа в функцию, которая их объединяет. Мне показалось это не логичным, так как при построении хотелось получать подграфы, которые соответствуют полноценному участку программы. Тогда я решила попробовать удалить эту вершину. Результат меня удивил: количество подграфов сократилось в сотни раз. Возможно, это снизило качество сравнения программ, но позволило обрабатывать значительно бо́льшие программы.

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

Для поиска изоморфизмов я воспользовалась библиотекой networkX, в которой реализован алгоритм VF2. Однако на этом этапе возникла проблема. Очевидно, что структурный изоморфизм дает слишком грубое сравнение, и нужно обязательно учитывать типы вершин. Исходные типы вершин содержат детальную информацию о конкретной программе, например название переменных, поэтому две вершины, которые содержат один и тот же тип информацию, имеют разные типы. Поэтому было решено сузить типы вершин до 60 основных.

Примеры сужения типов:

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

Поддержка разных языков программирования

Обычно контесты пишут на C++, Java или Python. Я допускаю, что со временем этот список может расшириться, поэтому мне хотелось поддержать гибкость в работе с разными языками программирования. В итоге у меня получилось добиться возможности добавлять новый язык без особого труда. Для этого достаточно подобрать инструмент для построения графа по программе на нужном языке программирования, а также выделить основные типы вершин. 

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

Метрика

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

Похожесть принимает значения в промежутке от 0 до 1, где 0 соответствует полному отсутствию плагиата, а 1 – наличию значительного плагиата.

Датасет

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

Собранный датасет состоит из двух частей. Первая часть нужна для оценки способности алгоритма находить плагиат, а также чтобы оценить чувствительность к разным видам модификаций. Я написала скрипт, с помощью которого собрала 373 программы на языке C++ из 23 разных контестов с платформы Codeforces. Затем для каждой программы было нужно получить файлы с разными типами модификаций. Для этого я воспользовалась инструментом «Горшочек» и с его помощью получила файлы с тремя основными модификациями: добавление/удаление комментариев, переименование, замена взаимозаменяемых конструкций управления. К сожалению, другие модификации «Горшочек» не поддерживает, однако я смогла добавить в него возможность создать модификации вставки кода, поэтому файлы с такой модификацией тоже появились в датасете. Кроме того, я хотела получить файлы, приближенные к реальному плагиату. Для этого я построила файлы с комбинацией разных модификаций с помощью «Горшочка».

Вторая часть датасета нужна для оценки работы алгоритма в случаях отсутствия плагиата. Для него я выбрала две задачи: простую и сложную. Для простой задачи  собрала 23 решения (в каждом около 25 строк кода), а для сложной – 12 (в каждом около 60 строк кода). В дальнейшем планировалось попарное сравнение программ, решающих одну и туже задачу.

Пример решения простой задачи:

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

int main(){
    ios_base::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);
    int t;
    cin>>t;
    while(t--){
        int n;
        cin>>n;
        int a[n],max1=0,min1=1e9+1;;
        for(int i=0;i<n;i++) {
            cin>>a[i];
            max1=max(max1,a[i]);
            min1=min(min1,a[i]);
        }
        cout<<max1-min1<<"\n";
    }
    return 0;
}

Пример решения сложной задачи:

#include<bits/stdc++.h>
using namespace std;

int main()
{	int t;
	cin>>t;
	while(t--)
	{	int i,n,j,a=1,b,c;
		cin>>n;
		vector<int> v(n+1);
		unordered_map<int,int> m;
		v[0]=0;
		for(i=1;i<=n;i++)
		{	cin>>v[i];
			if(v[i]<=n)
				m[v[i]]++;
		}

		for(i=1;i<=n;i++)
		{	if(m[v[i]]!=1)
			{	int temp=v[i];
				while(temp>0)
				{	temp/=2;
					if(temp<=n)
					{	if(m[temp]==0)
						{	m[temp]=1;
							m[v[i]]--;
							v[i]=temp;
							break;
						}
					}
				}
				if(temp==0)
				{	cout<<"NO\n";
					a=0;
					break;
				}
			}
		}
		if(a)
			cout<<"YES\n";
	}
}

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

Ссылка на датасет: https://github.com/Karina5005/Plagiarism/tree/main/dataset/test 

Тестирование

Тестирование и анализ результатов заняли большую часть времени работы над проектом. Всего мы провели более 2500 сравнений.

Результаты тестирования представлены в таблице:

Средняя похожесть

Добавление/удаление комментариев

1 ± 0

Вставка незначимых строк кода

0.99 ± 0.01

Замена взаимозаменяемых конструкций

0.94 ± 0.02

Переименование

0.93 ± 0.02

Комбинация модификаций

0.82 ± 0.03

Простая задача

0.15 ± 0.03

Сложная задача

0.05 ± 0.03

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

К сожалению, при сравнении решений простой задачи выявилась неточность. Нашелся набор программ, на которых значение похожести оказалось больше, чем хотелось бы. Это объясняется небольшим размером программ, а также простотой задачи – все решения действительно чем-то похожи. Из этого можно сделать вывод, что на маленьких программах алгоритм GPLAG может вести себя некорректно. 

Для оценки погрешности среднего я строила доверительные интервалы с уровнем доверия 95%. Можно заметить, что во всех случаях погрешность не слишком большая.

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

Итоги

Мне удалось реализовать алгоритм из статьи GPLAG с поддержкой разных языков программирования и разными подходами к построению графа зависимостей программ. 

Для этого я построила датасет из 372 программ с 4 видами модификаций и собрала 673 пар программ для проверки случаев отсутствия плагиата. Этот датасет теперь позволяет проводить тестирования и других алгоритмов для поиска контестного плагиата.

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

Конечно, эту тему можно развивать и дальше: проверить и другие алгоритмы, сравнить разные подходы между собой. Еще можно сравнить разные подходы к построению графа зависимостей программ, например, по исходному и скомпилированному коду.

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

Ссылка на репозиторий.

Комментарии (15)


  1. warhamster
    15.03.2022 10:20

    А как в олимпиадных задачах вообще возможен какой-то плагиат? Можно пример в двух словах хотя бы? Хочется понять, что там вообще можно списывать, по идее алгоритмы же на стэковерфлоу не валяются.


    1. sleirsgoevy
      15.03.2022 14:38

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


      1. warhamster
        15.03.2022 14:56

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


        1. yeputons
          15.03.2022 19:31

          На победу или хотя бы top-100 99% участников (цифра с потолка) в онлайн-соревнованиях не рассчитывают. А заиметь какой-то определённый рейтинг может быть нужно по каким-то причинам. Поэтому читерство цветёт и процветает, вот чудесный пост на Codeforces про то как автор контеста пошёл ловить читеров в их чаты: https://codeforces.com/blog/entry/95415

          А вот про списывание на школьном этапе всероссийской олимпиады: https://codeforces.com/blog/entry/96490 . Общего рейтинга нет, но плюшки за проход на региональный этап могут выдавать в зависимости от школы, начиная от "можно не делать домашку".


          1. Vladimir_Putin
            15.03.2022 19:39
            +1

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


    1. Aracon
      15.03.2022 16:45
      +1

      Довольно много участвовал в олимпиадах в 00-х, в школьных и вузовских, личных и командных, правда только своего региона. И пока не понимаю решаемой проблемы. Возможно, что-то изменилось за это время, или это мы такие честные были и поэтому про читерские способы не знали. Но даже технически, если регламенты только не изменились, не понятно, как на олимпиаде возможен плагиат, если только не говорим о каких-то заочных форматах. Да даже если и говорим. Потому что на нормальной олимпиаде/контесте задачи пишутся под конкретное мероприятие. Базовые алгоритмы всем известны, но суть задачи не в том, чтобы написать стандартный алгоритм (не считая "утешительных" задач). Соответственно, списать можно разве что у соседа. А этот способ читерства решается организационно - расстановкой рабочих мест, наблюдением со стороны оргкомитета.

      Если же говорить о стандартных алгоритмах, которые входят в решение, то здесь ситуация другая. Это спорт, и как в спорте заучиваются до автоматизма базовые движения, так и в олимпиадах по программированию Дейкстра и подобные алгоритмы пишутся наизусть, заученным шаблоном. И тут не то что граф зависимостей, а сам код символ в символ может совпадать у тех, кто готовился вместе или по одному учебнику. А ещё код скорее всего будет максимально похож у "сильных" участников на простых и средних по сложности задачах, где участники почти сразу видят оптимальный алгоритм и его и пишут без лишних действий в коде. Здесь говорю не по наслышке, был опыт ещё и проведения собственных контестов и проверки решений на некоторых олимпиадах, нередко код разных участников, которые точно писали сами, очень и очень похож, в том числе на нигде не опубликованное авторское решение.

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


    1. Karina5005
      15.03.2022 21:42

      Добрый вечер, спасибо за проявленный интерес.

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

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


    1. Kohelet
      16.03.2022 09:30

      У них домашние задания по программированию, по алгоритмам надо сдавать. Там задачи очень похожие на олимпиадные…


    1. wataru
      17.03.2022 14:29

      Это, видимо, не совсем про олимпиады, а скарее про практические курсы. В последнее время стало модно проводить практику в университетах на фактически олимпиадных системах. Только задачи тривиальные, да ограничений по времени нет.


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


  1. Igor-Ro
    15.03.2022 14:28
    +4

    Проверялся ли метод на ложные срабатывания? Потому как:

    1. Если говорить именно об олимпиадах, списать там от "очень трудно" до "вообще невозможно".

    2. Использовать куски кодов третьих лиц в принципе возможно. Но их ещё надо найти. И потом, достаточно помнить чужой алгоритм (а это не возбраняется), и написать по нему свой код, похожий на чужой.

    3. Главное. Мы все учимся в одних и тех же школах, читаем одни и те же книги и статьи. Поэтому наше мышление достаточно шаблонно. Особенно в решении известных задач. А любую новую задачу в первую очередь сводишь к тривиальным частям. И эти тривиальные части одинаковы у разных людей. Как раз и различаются циклами for и while.

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

    То есть, не получится ли так, что тех, кто сделал самостоятельно и правильно, но мыслил сходным образом, объявят в плагиате. А того, кто сделал через одно место индийским кодом объявят победителем?


    1. yeputons
      15.03.2022 19:42
      +1

      Не автор, но частично отвечу: списывания на онлайн-соревнованиях много. А это в том числе и обычные олимпиады из-за COVID-19, например, школьный этап всероссийской олимпиады. Там совершенно чудесные истории про списывание, когда копировали код с номерами строк и это работало: https://codeforces.com/blog/entry/96490

      И эти тривиальные части одинаковы у разных людей.

      Похожи - да. Но не одинаковы и почти всегда что-нибудь сделать либо "через одно место", либо у кого-то свой хитрый трюк. По моему опыту решения и просмотра глазами решений в задаче на >= 50 строк кода два участника просто не могут получить одинаковое решение (даже с точностью до небольших преобразований). Да даже в задаче на пять строк и две формулы это маловероятно (хоть и случается): кто-то напишет всё в одну строчку, кто-то вынесет все промежуточные вычисления в отдельные переменные, кто-то только часть, а ещё про оператор % вспомнит только половина, а вторая половина напишет `a - a / b * b`.

      А тупого списывания на уровне "немного переименовали переменные, поменяли for на while" очень много: его просто сделать и ничего не сломать. Даже что-нибудь в функцию переносить - уже появляется риск сломать.

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

      Но смотрим-то мы при поиске списывания не на "общую идею решения"/"алгоритм", а именно на конкретный код. Провода от задних фар у вас идут сбоку, сверху или обматываются вокруг мотора два раза по часовой и два раза против часовой, "чтобы помехи компенсировать"? Аккумулятор слева от руля в салоне или в багажнике? Обивка на сиденьях одинаковая?


    1. Karina5005
      15.03.2022 21:53

      Добрый вечер, спасибо за проявленный интерес.

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

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


  1. emaxx
    16.03.2022 00:48
    +2

    Соглашусь с другими комментаторами, что наверняка у подобного рода методов будет много false positive и false negative. Например:

    • Если задача сводится к тому, чтобы неочевидным способом применить стандартный алгоритм, то само решение может на 95% состоять из кода этого алгоритма, который выучен наизусть или (если правила позволяют) скопирован откуда-нибудь из общедоступного источника.

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

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

    Мне кажется, более перспективным направлением был бы сравнительный анализ по нескольким решениям, т. е. реализация критерия "участник X реализовал все задачи в одном стиле A, а одну задачу реализовал в совершенно непохожем стиле B". И то, здесь надо быть осторожным, потому что, может быть, этот "непохожий стиль" - какой-нибудь развесистый стандартный алгоритм, выученный или скопированный из общедоступного источника.


  1. rg_software
    16.03.2022 03:48
    +1

    Мой опыт работы с такими системами и обсуждение с коллегами приводит к мысли, что все подходы на основе tree matching -- это несколько "из пушек по воробьям": то есть да, теоретически всё верно и интересно (насчёт "перспективности" -- я про такое читал ещё в статье 2004 года), но на практике и более простые методы работают достаточно хорошо.

    В частности, JPlag и аналоги пытаются найти некое общее пересечение подстрок, покрывающее пару входных текстов (разновидность алгоритма RKR-GST). Чем обширнее покрытие, тем выше уровень плагиата. При этом используется токенизация с целью отловить переименование переменных, замена for-циклов на while и т.п.

    Сам я неспешно в редкие часы досуга делаю GUI оболочку для JPlag с целью выводить и анализировать результаты в более удобном виде. На мой субъективный взгляд, алгоритмов сравнения уже достаточно интересных, а вот UI хромает чудовищно. Если у вас перед глазами сотни работ, да ещё и имеются усложнения (типа разрешённых кусков кода, выданных организатором), на первый план выходит уже процесс ручного анализа результатов. А качество уходит немного на второй план: в конце концов, всё равно нельзя раздавать оценки тупо на основе скоринга системы, нужна дополнительная проверка.


  1. ov7a
    16.03.2022 15:48

    Кажется, что многие из этих изменений будут ловиться даже простым токенизатором, который выкидывает комментарии. И для случая, когда списывают 1-в-1 или просто переименовывают переменные, это достаточно. Вставка случайных элементов, возможно, решается добавлением "забывчивости" в токенизатор. (Говнокод на эту тему можно посмотреть тут, будет время, может прогоню на датасете). Но хотелось бы видеть сравнение с JPLAG и аналогами, чтобы было понятно, насколько предложенное решение лучше.

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