Найти - Пользователи
Полная версия: Упрощение строки для примерного сравнения
Начало » Python для новичков » Упрощение строки для примерного сравнения
1 2
alibek
Мне нужно будет сравнить и сопоставить несколько тысяч наименований, которые могут быть написаны с небольшими различиями. Гарантия распознавания не требуется, это нужно для первичной фильтрации, чтобы сократить сравниваемые коллекции, потом уже будут использоваться более сложные сравнения по нескольким критериям.
(если конкретнее, то мне нужно упорядочить медиатеку и речь идет о тэгах mp3-файлов)

Про дистанцию Левенштайна и различные модули fuzzy-match я знаю, но они для этой задачи не слишком удобны; они дают оценку похожести строк, но тогда придется где-то запоминать исходные строки и их похожесть.
Вообще в идеале мне нужно что-то вроде хэш-функции, которая бы игнорировала все несущественные части строки и возвращала хэш, который был бы одинаковым для идентичных (но различающихся в мелочах) строк.
В примитивном виде что-то вроде такого:
 def simplify(title: str) -> str:
    str = str.casefold()
    str = re.sub(r'[^\w]'gm, '', str)
    return title

Не подскажете идей или может быть готовых реализаций для подобного сравнения?
py.user.next
alibek
Мне нужно будет сравнить и сопоставить несколько тысяч наименований, которые могут быть написаны с небольшими различиями. Гарантия распознавания не требуется, это нужно для первичной фильтрации, чтобы сократить сравниваемые коллекции, потом уже будут использоваться более сложные сравнения по нескольким критериям.
(если конкретнее, то мне нужно упорядочить медиатеку и речь идет о тэгах mp3-файлов)
Ты составь сначала множество входных данных. Потом составь множество преобразований входных данных в выходные данные. Наполни это всё максимально. И когда оно у тебя будет всё, тогда и станет понятно, как их приводить к упрощённому единому виду.

И приведи конкретные примеры на форуме. Чтобы обсуждать не абстрактную хуйню непонятную, а конкретно на примерах рассмотреть все варианты преобразований.

А так ты спросил: мне нужно что-то сделать, я сам точно не знаю, что, а не подскажете как мне это сделать? Даже таким вещам простым тебя надо учить - как вопросы правильно задавать, чтобы тебе хотя бы отвечали.
alibek
Немного усложнил функцию:
 def simplify(str: str) -> str:
    str = str.casefold()
    str = unidecode(str)
    str = re.sub(r'[^\w\s]', '', str)
    str = re.sub(r'[\s ]+', ' ', str)
    str = str.strip()
    return str
Погонял на выборке в пару тысяч треков, вроде бы неплохо, ложных срабатываний по исполнителям не было. Коллизии по альбомам были, но там их никак не исключить.
В общем, для моих практических целей функции достаточно.
Но если кто-то посоветует, как ее улучшить, то буду благодарен.
py.user.next
alibek
В общем, для моих практических целей функции достаточно.
Хорошо хоть, что не говоришь, что это правильно.

1)
alibek
  
def simplify(str: str) -> str:
    str = str.casefold()
    str = unidecode(str)
    str = re.sub(r'[^\w\s]', '', str)
    str = re.sub(r'[\s ]+', ' ', str)
    str = str.strip()
    return str
Короче, ты видишь вот всё зелёненьким таким раскрасилось? или ты дальтоник?
А что же оно раскрасилось зелёненьким?

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

Вот смотри
  
>>> str.upper('abc')
'ABC'
>>> 
>>> 'abc'.upper()
'ABC'
>>>
Здесь идёт вызов метода у класса str и вызов метода у объекта класса str. Это разные вещи.

Где используется класс str?

Ну, например, вот здесь
  
>>> lst = [1, 2, 3, 4, 5]
>>> 
>>> out = list(map(str, lst))
>>> out
['1', '2', '3', '4', '5']
>>>
Были числа, стали строки.

Или вот здесь
  
>>> lst = ['abc', 'DeF', 'gHi']
>>> 
>>> out = list(map(str.upper, lst))
>>> out
['ABC', 'DEF', 'GHI']
>>>
Пропустили список строк через такой фильтр, увеличивающий регистр букв.

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

Так что придумай новое имя просто и всё. Имя text подойдёт и куча других асбтрактных имён, не привязанных ни к чему.

2)
  
str = ...
str = ...
str = ...
str = ...
Так делать не принято. Хотя бы пронумеруй их.
  
string0 = string
string1 = ... string0 ...
string2 = ... string1 ...
string3 = ... string2 ...
string4 = ... string3 ...
out = string4
В чём разница: в твоём случае каждое имя теряется после его обработки. В том числе потеряно исходное имя, с которого обработка началась. И когда тебе его нужно в конце функции где-то там сообщить (в каком-нибудь report'е там и так далее), его нет к этому моменту, потому что ты его перекрыл. И всё. И когда тебе надо на экран вывести всю информацию о работе, тебе нечего выводить, потому что у тебя только последний результат остался и всё.

А когда оно разделено (через нумерацию хотя бы, но лучше имена придумать, чтобы строки можно было ещё местами менять), то можно сделать вот такой report, потому что все имена сохранились и доступны в любой момент (хоть в конце кода, хоть в середине)
  
report = {'ok': {string1, string2, string3, string4, 'fail': {string9, string10}}
print(report)

3)
alibek
  
str = re.sub(r'[\s ]+', ' ', str)
Это делается вот так, потому что пробел и так уже находится внутри \s (класс пробельных символов)
  
string = re.sub(r'\s+', ' ')
Вообще, в таких заменах надо помнить две вещи только: плюс бывает длинный и короткий; плюс не может проходить перевод строки (в sub нужно флаг передавать для перехода плюса и звёздочки через перевод строки - re.S). Оно касается точки, но проблемы всегда возникают вот именно на кванторах на этих. Поэтому кажется, что это плюс или звёздочка не может преодолеть перевод строки, хотя на самом деле это точка не может совпасть с переводом строки, если не указан этот флаг.

Тут видно разницу
  
>>> import re
>>> 
>>> re.sub(r'.+', 'x', 'abc\ndef\nghi')
'x\nx\nx'
>>> 
>>> re.sub(r'.+', 'x', 'abc\ndef\nghi', flags=re.S)
'x'
>>>

4)
По алгоритму: научись разделять задание и реализацию задания на две абсолютно отдельные друг от друга вещи. Не надо задание формулировать в виде кода и тем более не надо его другим объяснять на коде. Если ты не смог сформулировать задание от и до только на одних словах, то и в коде получится какая-то параша в итоге, тоже приблизительная.

Здесь писал, как учиться и как делать программы.

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

Поэтому ты сначала формулируешь всё словесно, все детали, без единой строчки кода. Может быть, ты и сам её уже решишь и полностью поймёшь, как её написать, когда доформулируешь всё по всем правилам. Потом ты её передаёшь людям, спрашивая, а как это решить. И в конце этой полной словесной формулировки задания ты где-то там добавляешь этот свой говнокод, в котором питонячьи классы перекрыты и много всего другого понаписано, со словами "я вот эту задачу пытался сделать так; возможно, это поможет вам понять ход моих мыслей и мою задумку по решению этой задачи, а может быть и нет и я вообще не шарю, а нужно её вообще по-другому решать; тогда подскажите, как её решать, если вы знаете, конечно“. Вот. И тогда тебе помогут. Кто-нибудь скажет тебе ”о! я видел, как она решается! там-то там-то посмотри, её делали в таком-то там году, я тогда там сидел и читал инфу по другому вопросу!" и всё. И ты приходишь туда по этой ссылке, и там 100500 решений этой задачи со всех сторон.
xam1816
alibek
Мне нужно будет сравнить и сопоставить несколько тысяч наименований, которые могут быть написаны с небольшими различиями.
Приведи примеры какие данные у тебя на входе, и что ждешь на выходе
alibek
Например.

Abba, ABBA -> abba

Bomfunk MC s, Bomfunk MC's, Bomfunk MC-s, Bomfunk MCs -> bomfunk mcs

Chimène Badi, Chimene Badi -> сhimene badi

E-Type, E Type -> e type

Mylène Farmer, Mylene Farmer -> mylene farmer

L.E.J., LEJ, L E J, L_E_J - > l e j

The Offspring, Offspring -> offspring

А'Студио, А-студио, А Студио -> a studio

Ёлка, Елка -> elka

Это упрощение по имени исполнителя, тут чуть проще. Хотя и тут есть нюансы (например несколько исполнителей, или исполнители типа “Nick Cave and The Bad Seeds”, где вторая часть может отсутствовать, писаться через &, запятую, тире, без артикля).

Еще будет упрощение по названию альбома и названию трека.
Тут ожидаются свои сложности, особенно с названиями альбомов.

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

Всего же у меня где-то порядка 150 ГБ музыки и порядка 30 тысяч треков.
Конечно там будут дубли или некачественные треки, поэтому после обработки коллекция сократится где-то на четверть или на треть.
Но все равно слишком много для ручной обработки.
alibek
На домашнем ПК добавил в adblock-фильтр строку:
python.su#?#div.blockpost:-abp-has(div.postleft strong:-abp-contains(py.user.next))
Поэтому отвечу с задержкой.

py.user.next
Ну, например, вот здесь
Строго говоря нет.
Во-первых, там используется не класс, а одноименная функция.
Во-вторых str это не класс, а тип.
Кроме того, из моего примера очевидно, что я знаю что такое классы и экземпляры классов, и что я использую в цепочке присвоений именно метод экземпляра класса. То есть то, что форум “раскрасил зелененьким” это не класс str, а одноименная переменная str, потому что при разрешении имен у нее будет приоритет и Python никогда не перепутает, нужно вызвать метод класса str, или метод строкового объекта str.

py.user.next
Поэтому его нельзя перекрывать
Нельзя — это когда syntax error. Все остальное можно.
Впрочем, имя переменной никакого значения не имеет, а небольшая вероятность путаницы все же есть, поэтому совет обоснован и имя переменной я исправлю.

py.user.next
в твоём случае каждое имя теряется после его обработки
Я как бы это знаю.
А зачем мне хранить промежуточные значения?
Если вдруг функция будет работать не так, как ожидается, я ее прослежу в режиме отладки и просмотрю промежуточные значения уже непосредственно, на каждом шаге.
А еще лучше — добавить отладочный вывод значения после каждой строки. Это будет правильнее, чем делать вывод report общим списком — если при выполнении функции будет что-то не то, то код до вывода report может и не дойти, а отладочный вывод максимально точно укажет место сбоя.
Не говоря уж о том, что это лишний расход ресурсов.

py.user.next
пробел и так уже находится внутри \s
Не существенно. Уверен даже, что внутренний оптимизатор регулярных выражений умеет сам убирать подобные дубли.
Могу убрать, если так глаза мозолит.

py.user.next
плюс бывает длинный и короткий
Плюс бывает жадный и нежадный.
А то, что вы описали — это называется флаг ‘single line’ (или ‘multi line’), и влияет на поведение токена “.”, а не на квантификаторы.

py.user.next
Не надо задание формулировать в виде кода и тем более не надо его другим объяснять на коде.
Нужная задача описана в первых двух абзацах и кода там нет.
Код приведен в качестве пояснения (примера), уже после самой задачи.

py.user.next
И ты приходишь туда по этой ссылке, и там 100500 решений этой задачи со всех сторон.
Да? А как же 13 принцип дзена Питона?
Впрочем, я с ним категорически не согласен и считаю правильным основной девиз Perl.
alibek
py.user.next
потому что пробел и так уже находится внутри \s
Хотя, нет.
Это не пробел.
Это символ U+00A0 и он не находится внутри \s.
py.user.next
alibek
Хотя, нет.
Это не пробел.
Это символ U+00A0 и он не находится внутри \s.
Ну у тебя пробелы везде записаны там
  
>>> r"str = re.sub(r'[\s ]+', ' ', str)"
"str = re.sub(r'[\\s ]+', ' ', str)"
>>> list(map(ord, r"str = re.sub(r'[\s ]+', ' ', str)"))
[115, 116, 114, 32, 61, 32, 114, 101, 46, 115, 117, 98, 40, 114, 39, 91, 92, 115, 32, 93, 43, 39, 44, 32, 39, 32, 39, 44, 32, 115, 116, 114, 41]
>>>
Даже если символ какой-то необычный есть, то так и надо его записывать конкретным кодом, чтобы не путать с другими символами, выглядящими так же.

alibek
Во-вторых str это не класс, а тип.
  
>>> str
<class 'str'>
>>>
А ты вообще знаешь-то хоть, чем тип и класс вообще похожи и различаются?
  
>>> type(type)
<class 'type'>
>>> 
>>> type(str)
<class 'type'>
>>> 
>>> type('')
<class 'str'>
>>> 
>>> type(lambda: 1)
<class 'function'>
>>> 
>>> issubclass(type(lambda: 1), type)
False
>>> issubclass(type(str), type)
True
>>>
>>> issubclass('', type)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: issubclass() arg 1 must be a class
>>> 
>>> issubclass(str, type)
False
>>>

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

alibek
То есть то, что форум “раскрасил зелененьким” это не класс str, а одноименная переменная str
Ну форум раскрасил потому, что никто не будет это использовать в качестве имени переменной или функции, так как все знают, что встроенные имена перекрывать нельзя. Они могут понадобится потом, а они перекрыты. Ну тупень какой-нибудь, конечно же, будет делать del, чтобы использовать в своём коде перекрытое им же самим встроенное имя. А умный человек всё-таки выучит все имена (благо они указаны все на одной страничке) и не будет позориться так уж, как новичок уровня школоты.

alibek
Плюс бывает жадный и нежадный.
Там вообще больше понятий всяких: жадный, ленивый, ревнивый. И это всё разные вещи. Поэтому лучше их не использовать, а прямо так и разделить на два вида: длинный и короткий. Если ты ревнивый жадным назовёшь, то ты ошибёшься. Если же я ревнивый назову длинным или жадный назову длинным, то я не ошибусь в обоих случаях.

alibek
А то, что вы описали — это называется флаг ‘single line’ (или ‘multi line’), и влияет на поведение токена “.”
Это не токен, а метасимвол. Метасимвол - символ, описывающий какой-то символ.
Вообще, там понятия используются из теории грамматик. А в грамматиках есть символы и метасимволы. Символы называют терминальными символами, а метасимволы называют нетерминальными символами.
multiline - это другой флаг. Смотри, не называй флаг для совпадения точки с переводом строки multiline, потому что флаг multiline подразумевает вообще другие возможности.

alibek
Уверен даже, что внутренний оптимизатор регулярных выражений умеет сам убирать подобные дубли.
Убирает - не убирает, это неважно. Сам код должен быть чистый, а не замусоренный. Почитай нормальные коды каких-нибудь программ. И почитай замусоренные коды каких-нибудь программ. Сразу поймёшь, в чём разница. Пример - этот adblock, к которому даже прикасаться не хочется, настолько там всё наговнокожено. Жалко время тратить на него, чтобы разобраться сначала, что там ценного, а что там просто так. Другое дело какие-нибудь исходники Git'а, которые вычищены до блеска японцем. Так там и читать приятно, и сделать что-нибудь хочется, подредактировать что-то в программе.

alibek
Я как бы это знаю.
А зачем мне хранить промежуточные значения?
Ну, например, ты удаляешь какое-то из этих преобразований, потому что оно оказалось ненужным, и тут раз и ты пропускаешь момент, что оно вообще-то привязано к предыдущему преобразованию, а следующее преобразование привязано к нему. И потом наступает момент (после нескольких таких удалений и добавления новых преобразований), что ты смотришь в имя string в какой-то строке кода и просто не знаешь, что в нём находится. В то же время фиксированные разные имена в любой момент сообщают о том, кто они и что в них хранится.

alibek
Если вдруг функция будет работать не так, как ожидается, я ее прослежу в режиме отладки и просмотрю промежуточные значения уже непосредственно, на каждом шаге.
Не посмотришь ты ничего. Этот код будет на хостинге стоять где-нибудь и пользоваться им будут сто бухгалтерш тупых одновременно. Попробуй тормозни его, они разорутся там все. Они тебе просто скажут “у нас такая-то ошибка выскакивает” и ты прямо на сервере должен будешь смотреть свою программу. Какой дебаггер? На это нет времени, да и дебаггер сам может не поставиться туда.

alibek
А еще лучше — добавить отладочный вывод значения после каждой строки. Это будет правильнее, чем делать вывод report общим списком — если при выполнении функции будет что-то не то, то код до вывода report может и не дойти, а отладочный вывод максимально точно укажет место сбоя.
Да вообще-то нет. Элемент report делается в исключении. А исключение если выходит, то после выхода из функции распространяется дальше по программе. Его никак не пропустишь, если, конечно, не будешь тупить и перехватывать все исключения и гасить их все по умолчанию. Так что оно может быть и в середине, и в конце. И вот что оно будет выводить? Какое-то пятидесятое преобразование из ста преобразований, потому что предыдущие сорок стали неизвестными? Ты сэкономил имена, якобы какие-то ресурсы, но в итоге ты не сэкономил, а превратил всё в два притопа три прихлопа, с которыми потом ничего не сделаешь, пока не превратишь их в нормальные имена, где все имена разделены.

alibek
Не говоря уж о том, что это лишний расход ресурсов.
Имена в питоне ничего не расходуют.

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

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

alibek
Да? А как же 13 принцип дзена Питона?
Впрочем, я с ним категорически не согласен и считаю правильным основной девиз Perl.
Дзен питона соблюдается, принципы UNIX соблюдаются, принципы SOLID соблюдаются и многие другие принципы соблюдаются. А про 100500 решений я имел в виду то, что бывает кривое решение, содержащее один единственный полезный элемент, который можно взять на заметку. Потом из другого решения можно взять другой элемент полезный. Потом из третьего решения можно взять третий элемент полезный. Потом эти все элементы взятые или там идеи объединяются и делается своё оптимальное решение.
alibek
py.user.next
Ну у тебя пробелы везде записаны там
Да, при отправке в форум символ заменился на обычный пробел.
Исходно это был символ неразрывного пробела.

py.user.next
Имена в питоне ничего не расходуют.
Как минимум они занимают место в каком-нибудь внутреннем указателе или словаре для области видимости функции, уж не знаю, как в питоне это реализовано.
Но я имел ввиду, что место в памяти занимают значения промежуточных переменных.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB