Страницы

Поиск по вопросам

пятница, 20 декабря 2019 г.

Регулярное выражение различающее из какого набора строка

#алгоритм #регулярные_выражения


Есть два набора строк.
Белый список: 

bhdgffhfa
aggjjaaga
hbefebhfi
dbceeabih
ihifdbhhf
djacfjbae
ibjbeigac
jaffigdad
aaaaaaaaj
dgifecchi
dcbhgjaaf
eedfbcedb
aaaaaaabf
beaajbihg
aaaaaaaag
eacccdjeh
aaaaaaaad
hehcijfhc
aaaaaaaaa
aaaaaaabc
jfdhdeice


И черный список:

aaaaaaaai
ehigcbgjd
hgjdeajec
aaaaaaabe
haebgiggc
aaaaaaaba
aaaaaaaaf
bfjhehbcf
diieabefh
jhjcaeead
ifbjdgibf
dddbgcgai
bbcafhcif
hfjciccbi
fdbgidjdj
aaaaaaabb
jhdibgbfj
cfjbaijad
bhgjfacgi
ehhieihhh
abijjabda


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

UPDATE: https://www.hackerrank.com/contests/regular-expresso/challenges/match-maker
    


Ответы

Ответ 1



Возможно получение двух решающих паттернов: по попаданию в белый список (whitelist) и в чёрный список (blacklist). Паттерн белого списка: /ce|ag|fe|ff|aaj|cd|dc|aad|abc|abf|acf|bei|bhh|cij|aaaaaaaaa/ Паттерн чёрного списка: /bg|ai|bb|ie|jc|jd|bd|cg|jh|aba|abe|aaaaaaaaf/ Паттерн чёрного списка короче и допускает сжатие до 37 символов: /ai|b[bdg]|cg|ie|j[cdh]|ab[ae]|a{8}f/ Но использование двух паттернов позволяет провести диагностику. Решающие паттерны набираются из уникальных для соответствующего списка токенов по оптимизированному "жадному" алгоритму. К рассмотрению приняты двух- и трёхсимвольные токены, а также 9-символьные токены, начинающиеся с "aaaaaaa". Программа на языке PHP: $whitelist = " bhdgffhfa aggjjaaga hbefebhfi dbceeabih ihifdbhhf djacfjbae ibjbeigac jaffigdad aaaaaaaaj dgifecchi dcbhgjaaf eedfbcedb aaaaaaabf beaajbihg aaaaaaaag eacccdjeh aaaaaaaad hehcijfhc aaaaaaaaa aaaaaaabc jfdhdeice "; $blacklist = " aaaaaaaai ehigcbgjd hgjdeajec aaaaaaabe haebgiggc aaaaaaaba aaaaaaaaf bfjhehbcf diieabefh jhjcaeead ifbjdgibf dddbgcgai bbcafhcif hfjciccbi fdbgidjdj aaaaaaabb jhdibgbfj cfjbaijad bhgjfacgi ehhieihhh abijjabda "; //Генератор решающих токенов function tokens_gen($whitelist, $blacklist){ $white_tokens = []; $black_tokens = []; for($i=-1; $i< 12; $i++){ $c = ($i<0) ? "" : chr(ord("a")+$i); // сначала двухсимвольные токены $c = ($i==10) ? "aaaaaaa" : $c; // последними - токены c "aaaaaaa" for($ord2=ord("a"); $ord2 <= ord("j"); $ord2++){ for($ord3=ord("a"); $ord3 <= ord("j"); $ord3++){ $ccc = $c.chr($ord2).chr($ord3); $pm_white = preg_match("/$ccc/", $whitelist); $pm_black = preg_match("/$ccc/", $blacklist); if(($pm_white==1) && ($pm_black==0)) array_push($white_tokens, $ccc); if(($pm_black==1) && ($pm_white==0)) array_push($black_tokens, $ccc); } } } return [$white_tokens, $black_tokens]; } // "Жадный" рекурсивный отбор токенов function greedy($tokens, $list, $pat=""){ $tokens_new = []; $ppp_max = ""; foreach($tokens as $ppp){ $p = ($pat=="")? $ppp: "$pat|$ppp"; $sum = 0; foreach($list as $str) $sum += preg_match("/$p/", $str); if($sum > $sum_max){ $ppp_max = $ppp; $sum_max = $sum; } if($sum > 0) array_push($tokens_new, $ppp); } if($ppp_max == "") return $pat; $pat = ($pat=="")? $ppp_max: "$pat|$ppp_max"; $left = 'display: block; float: left; '; printf("Токенов:%d", count($tokens)); printf(" Строк, всего:%d по токену: $sum_max ", count($list)); print("Токен: $ppp_max   Паттерн: $pat
"); $list = preg_grep("/$ppp_max/", $list, PREG_GREP_INVERT); $pat = greedy($tokens_new, $list, $pat); return $pat; } // Перевод контрольных строк в массивы $white = explode(chr(10), trim($whitelist)); foreach($white as &$w) $w=trim($w); $black = explode(chr(10), trim($blacklist)); foreach($black as &$b) $b=trim($b); $white_black = array_merge($white, $black); // Получение решающих токенов list($white_tokens, $black_tokens) = tokens_gen($whitelist, $blacklist); // Получение жадных паттернов print("Жадные паттерны:

"); $white_pattern = greedy($white_tokens, $white); $black_pattern = greedy($black_tokens, $black); //$black_pattern = 'ai|b[bdg]|cg|ie|j[cdh]|ab[ae]|a{8}f'; print("

white_pattern = $white_pattern
black_pattern = $black_pattern
"); $left = 'display: block; float: left; '; print("
Проверка:
"); foreach($white_black as $str){ $in_white = preg_match("/$white_pattern/",$str); $in_black = preg_match("/$black_pattern/",$str); print("
$str:$in_white $in_black"); } Результаты: Жадные паттерны: Токенов:108 Строк, всего:21 по токену: 3 Токен: ce   Паттерн: ce Токенов:108 Строк, всего:18 по токену: 2 Токен: ag   Паттерн: ce|ag Токенов:88 Строк, всего:16 по токену: 2 Токен: fe   Паттерн: ce|ag|fe Токенов:81 Строк, всего:14 по токену: 2 Токен: ff   Паттерн: ce|ag|fe|ff Токенов:67 Строк, всего:12 по токену: 2 Токен: aaj   Паттерн: ce|ag|fe|ff|aaj Токенов:49 Строк, всего:10 по токену: 1 Токен: cd   Паттерн: ce|ag|fe|ff|aaj|cd Токенов:41 Строк, всего:9 по токену: 1 Токен: dc   Паттерн: ce|ag|fe|ff|aaj|cd|dc Токенов:33 Строк, всего:8 по токену: 1 Токен: aad   Паттерн: ce|ag|fe|ff|aaj|cd|dc|aad Токенов:28 Строк, всего:7 по токену: 1 Токен: abc   Паттерн: ce|ag|fe|ff|aaj|cd|dc|aad|abc Токенов:26 Строк, всего:6 по токену: 1 Токен: abf   Паттерн: ce|ag|fe|ff|aaj|cd|dc|aad|abc|abf Токенов:24 Строк, всего:5 по токену: 1 Токен: acf   Паттерн: ce|ag|fe|ff|aaj|cd|dc|aad|abc|abf|acf Токенов:22 Строк, всего:4 по токену: 1 Токен: bei   Паттерн: ce|ag|fe|ff|aaj|cd|dc|aad|abc|abf|acf|bei Токенов:18 Строк, всего:3 по токену: 1 Токен: bhh   Паттерн: ce|ag|fe|ff|aaj|cd|dc|aad|abc|abf|acf|bei|bhh Токенов:11 Строк, всего:2 по токену: 1 Токен: cij   Паттерн: ce|ag|fe|ff|aaj|cd|dc|aad|abc|abf|acf|bei|bhh|cij Токенов:5 Строк, всего:1 по токену: 1 Токен: aaaaaaaaa   Паттерн: ce|ag|fe|ff|aaj|cd|dc|aad|abc|abf|acf|bei|bhh|cij|aaaaaaaaa Токенов:118 Строк, всего:21 по токену: 5 Токен: bg   Паттерн: bg Токенов:118 Строк, всего:16 по токену: 2 Токен: ai   Паттерн: bg|ai Токенов:82 Строк, всего:14 по токену: 2 Токен: bb   Паттерн: bg|ai|bb Токенов:75 Строк, всего:12 по токену: 2 Токен: ie   Паттерн: bg|ai|bb|ie Токенов:67 Строк, всего:10 по токену: 2 Токен: jc   Паттерн: bg|ai|bb|ie|jc Токенов:53 Строк, всего:8 по токену: 2 Токен: jd   Паттерн: bg|ai|bb|ie|jc|jd Токенов:37 Строк, всего:6 по токену: 1 Токен: bd   Паттерн: bg|ai|bb|ie|jc|jd|bd Токенов:24 Строк, всего:5 по токену: 1 Токен: cg   Паттерн: bg|ai|bb|ie|jc|jd|bd|cg Токенов:18 Строк, всего:4 по токену: 1 Токен: jh   Паттерн: bg|ai|bb|ie|jc|jd|bd|cg|jh Токенов:12 Строк, всего:3 по токену: 1 Токен: aba   Паттерн: bg|ai|bb|ie|jc|jd|bd|cg|jh|aba Токенов:5 Строк, всего:2 по токену: 1 Токен: abe   Паттерн: bg|ai|bb|ie|jc|jd|bd|cg|jh|aba|abe Токенов:3 Строк, всего:1 по токену: 1 Токен: aaaaaaaaf   Паттерн: bg|ai|bb|ie|jc|jd|bd|cg|jh|aba|abe|aaaaaaaaf white_pattern = ce|ag|fe|ff|aaj|cd|dc|aad|abc|abf|acf|bei|bhh|cij|aaaaaaaaa black_pattern = bg|ai|bb|ie|jc|jd|bd|cg|jh|aba|abe|aaaaaaaaf Проверка: bhdgffhfa:1 0 aggjjaaga:1 0 hbefebhfi:1 0 dbceeabih:1 0 ihifdbhhf:1 0 djacfjbae:1 0 ibjbeigac:1 0 jaffigdad:1 0 aaaaaaaaj:1 0 dgifecchi:1 0 dcbhgjaaf:1 0 eedfbcedb:1 0 aaaaaaabf:1 0 beaajbihg:1 0 aaaaaaaag:1 0 eacccdjeh:1 0 aaaaaaaad:1 0 hehcijfhc:1 0 aaaaaaaaa:1 0 aaaaaaabc:1 0 jfdhdeice:1 0 aaaaaaaai:0 1 ehigcbgjd:0 1 hgjdeajec:0 1 aaaaaaabe:0 1 haebgiggc:0 1 aaaaaaaba:0 1 aaaaaaaaf:0 1 bfjhehbcf:0 1 diieabefh:0 1 jhjcaeead:0 1 ifbjdgibf:0 1 dddbgcgai:0 1 bbcafhcif:0 1 hfjciccbi:0 1 fdbgidjdj:0 1 aaaaaaabb:0 1 jhdibgbfj:0 1 cfjbaijad:0 1 bhgjfacgi:0 1 ehhieihhh:0 1 abijjabda:0 1 Проверка показала, что все паттерны работают. Схема с полным "жадным" перебором дала такие же результаты.

Ответ 2



Ответ: /ag|c[de]|dc|fe|g[df]|aa[dj]|ab[cf]|b[aeh]{2}|bjb|cij|a{9}/ Проверка: var W=['bhdgffhfa','aggjjaaga','hbefebhfi','dbceeabih','ihifdbhhf','djacfjbae','ibjbeigac','jaffigdad','aaaaaaaaj','dgifecchi','dcbhgjaaf','eedfbcedb','aaaaaaabf','beaajbihg','aaaaaaaag','eacccdjeh','aaaaaaaad','hehcijfhc','aaaaaaaaa','aaaaaaabc','jfdhdeice'], B=['aaaaaaaai','ehigcbgjd','hgjdeajec','aaaaaaabe','haebgiggc','aaaaaaaba','aaaaaaaaf','bfjhehbcf','diieabefh','jhjcaeead','ifbjdgibf','dddbgcgai','bbcafhcif','hfjciccbi','fdbgidjdj','aaaaaaabb','jhdibgbfj','cfjbaijad','bhgjfacgi','ehhieihhh','abijjabda']; var text="ag|c[de]|dc|fe|g[df]|aa[dj]|ab[cf]|b[aeh]{2}|bjb|cij|a{9}", reg=new RegExp(text); document.write('Длина выражения
С границами - '+reg.toString().length) document.write('
Только символы - '+text.length) document.write('
Тесты
Из белого '+W.filter(e=>reg.test(e)).length+'/'+W.length) document.write('
Из черного '+B.filter(e=>reg.test(e)).length+'/'+B.length) Решение по шагам Первым делом определим алфавит: в задаче используются символы abcdefghij Регулярное выражение будет определять принадлежность по уникальным для списка кускам (токен). Списка два - белый W и черный B, я выбрал белый список, просто он первый. Попробуйте с черным, возможно там есть более короткое решение?). Токены сначала нужно найти. Сгенерируем 2, 3, 4-символьные множества: function set(lev,str){ //set(2) - "aa,ab, ... ,jh,ji,jj", строкой пока компактнее lev--; str=str||""; return "abcdefghij".split('').map(function(e){ return lev>0 ? set(lev,str+e) : str+e; }).join(); } //"aa","ab",...,"jjji","jjjj" - массив sets=[].concat(set(2),set(3),set(4)).join().split(',') Для каждого токена определим, сколько раз он встречается в списках W и B. var freqs=sets.map(function(token){return { t:token, w:W.filter(function(e){return ~e.indexOf(token);}).length, b:B.filter(function(e){return ~e.indexOf(token);}).length };}); Определим список токенов, уникальных для W и не содержащих в себе меньших токенов uniq=freqs.filter(function(e){//берем уникальные для W return e.w>0 && e.b==0; }).filter(function(big,i,arr){//выкидываем избыточные - содержащие других return undefined==arr.find(function(small){//содержу ли другой, меньше и лаконичнее? return big.t.length>small.t.length && ~big.t.indexOf(small.t); }); }); //выводим списком с частотами - что одним выстрелом многих зайцев бьет? uniq.map(x=>x.t+'\t'+x.w).join('\n') //выводим через | для тестов в regex101.com и им подобных сервисах uniq.map(x=>x.t).join('|') Минимизация количества токенов. Берем http://regex101.com, белый и черный списки, полученную регулярку x|y|...|z и по одному пробуем убирать токены из регулярного выражения, следя за тем, чтобы в белом списке в каждом элементе был найден хотя бы один токен. Для особых случаев вписываем токен руками (пример - a{9}) Оптимизация символов. Имеем регулярное выражение, покрывающее белый список, но: Некоторые токены отличаются символами в одной позиции. XX: cd|сe можно превратить в c[de]. Польза = -1|-1c+2[] = 0 (полезна при похожести больше чем 2 токенов) XXX: aad|aaj превращаем в aa[dj]. Польза = -1|-2a+2[] = -1 символ Некоторые токены отличаются символами в двух позициях. XXX: bae|bea|bhh - b[aeh]{2}. Тут мы разширяем список токенов и это опасно. Но нам повезло и токены baa|bee|bah|beh|bha|bhe отсутствуют в черном списке, и эта расширяющая не сломает результат. Польза = -2|-2b-3eah+2[]+3{2} = -2 символа P.S: Теоретически, поиск похожих, формирование замен и проверку списка-антогониста можно бы автоматизировать с помощью алгоритма Левинштейна (как в Word - похожие слова) и кластеризации, но мне было влом и количество токенов не такое уж большое для ручного, творческого процесса

Комментариев нет:

Отправить комментарий