Как убрать n оставив r n python
Перейти к содержимому

Как убрать n оставив r n python

  • автор:

Как убрать символ новой строки (\n) при чтении строк из файла

При использовании file.readlines() получаем что-то вроде этого:

>>> file.readlines() ['12\n', '10\n', '9\n', '15\n', '10\n', '120'] 

Как убрать \n ?

Отслеживать

8,602 4 4 золотых знака 30 30 серебряных знаков 53 53 бронзовых знака

задан 30 ноя 2016 в 19:06

user216622 user216622

3 ответа 3

Сортировка: Сброс на вариант по умолчанию

>>> l = ['12\n', '10\n', '9\n', '15\n', '10\n', '120'] >>> l = [line.rstrip() for line in l] >>> l ['12', '10', '9', '15', '10', '120'] 

Отслеживать

ответ дан 30 ноя 2016 в 19:38

1,981 12 12 серебряных знаков 23 23 бронзовых знака

стоит заметить, что .rstrip() также удаляет любой пробел в конце строки, а не только символы новой строки как .rstrip(‘\n’) (это может и быть желаемым поведением).

13 инструментов для обработки текста в командной оболочке

Здесь представлен фрагмент будущей книги «Основные инструменты и практики для начинающего разработчика программного обеспечения» Бальтазара Рубероля и Этьена Броду. Книга должна помочь образованию подрастающего поколения разработчиков. Она охватит такие темы, как освоение консоли, настройка и эффективная работа в командной оболочке, управление версиями кода с помощью git , основы SQL, инструменты вроде Make , jq и регулярные выражения, основы сетевого взаимодействия, а также лучшие практики разработки программного обеспечения и совместной работы. В настоящее время авторы упорно работают над этим проектом и приглашают всех поучаствовать в списке рассылки.

Обработка текста в командной оболочке

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

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

Заставьте каждую программу хорошо выполнять одну функцию — «Основы философии Unix»

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

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

Файл CSV с примерами доступен в онлайне. Можете скачать его для проверки материала.

Команда cat используется для составления списка из одного или нескольких файлов и отображения их содержимого на экране.

$ cat Documents/readme Thanks again for reading this book! I hope you're following so far! $ cat Documents/computers Computers are not intelligent They're just fast at making dumb things. $ cat Documents/readme Documents/computers Thanks again for reading this book! I hope you are following so far! Computers are not intelligent They're just fast at making dumb things.

head

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

$ head -n 2 metadata.csv metric_name,metric_type,interval,unit_name,per_unit_name,description,orientation,integration,short_name mysql.galera.wsrep_cluster_size,gauge,,node,,The current number of nodes in the Galera cluster.,0,mysql,galera cluster size

Если -n не указано, head выводит первые десять строк указанного файла или входящего потока.

tail

tail — аналог head , только он выводит последние n строк в файле.

$ tail -n 1 metadata.csv mysql.performance.queries,gauge,,query,second,The rate of queries.,0,mysql,queries

Если хотите вывести все строки, расположенном после n-й строки (включая её), можете использовать аргумент -n +n .

$ tail -n +42 metadata.csv mysql.replication.slaves_connected,gauge. Number of slaves connected to a replication master.,0,mysql,slaves connected mysql.performance.queries,gauge,,query,second,The rate of queries.,0,mysql,queries

В нашем файле 43 строки, поэтому tail -n +42 выводит только 42-ю и 43-ю строки из него.

Если параметр -n не указан, tail выведет последние десять строк в указанном файле или входном потоке.

tail -f или tail —follow отображают последние строки в файле и каждую новую строку по мере записи в файл. Это очень полезно для просмотра активности в реальном времени, например, что записывается в логи веб-сервера и т. д.

wc (word count) выводит количество символов ( -c ), слов ( -w ) или строк ( -l ) в указанном файле или потоке.

$ wc -l metadata.csv 43 metadata.csv $ wc -w metadata.csv 405 metadata.csv $ wc -c metadata.csv 5094 metadata.csv

По умолчанию отображается всё вышеперечисленное.

$ wc metadata.csv 43 405 5094 metadata.csv

Если текстовые данные передаются по конвейеру или перенаправлены в stdin , то отображается только счётчик.

$ cat metadata.csv | wc 43 405 5094 $ cat metadata.csv | wc -l 43 $ wc -w < metadata.csv 405

grep

grep — это швейцарский нож фильтрации строк по заданному шаблону.

Например, можем найти все вхождения слова mutex в файле.

$ grep mutex metadata.csv mysql.innodb.mutex_os_waits,gauge,,event,second,The rate of mutex OS waits.,0,mysql,mutex os waits mysql.innodb.mutex_spin_rounds,gauge,,event,second,The rate of mutex spin rounds.,0,mysql,mutex spin rounds mysql.innodb.mutex_spin_waits,gauge,,event,second,The rate of mutex spin waits.,0,mysql,mutex spin waits

grep может обрабатывать либо файлы, указанные в качестве аргументов, либо поток текста, переданный на его stdin . Таким образом, мы можем сцеплять несколько команд grep для дальнейшей фильтрации текста. В следующем примере мы фильтруем строки в нашем файле metadata.csv , чтобы найти строки, содержащие и mutex, и OS.

$ grep mutex metadata.csv | grep OS mysql.innodb.mutex_os_waits,gauge,,event,second,The rate of mutex OS waits.,0,mysql,mutex os waits

Рассмотрим некоторые опции grep и их поведение.

grep -v выполняет инвертное сопоставление: фильтрует строки, которые не соответствуют шаблону аргументов.

$ grep -v gauge metadata.csv metric_name,metric_type,interval,unit_name,per_unit_name,description,orientation,integration,short_name

grep -i выполняет сопоставление без учёта регистра. В следующем примере grep -i os находит как OS, так и os.

$ grep -i os metadata.csv mysql.innodb.mutex_os_waits,gauge,,event,second,The rate of mutex OS waits.,0,mysql,mutex os waits mysql.innodb.os_log_fsyncs,gauge,,write,second,The rate of fsync writes to the log file.,0,mysql,log fsyncs

grep -l выводит список файлов, содержащих совпадение.

$ grep -l mysql metadata.csv metadata.csv

Команда grep -c подсчитывает, сколько раз найден образец.

$ grep -c select metadata.csv 3

grep -r рекурсивно ищет файлы в текущем рабочем каталоге и всех его подкаталогах.

$ grep -r are ~/Documents /home/br/Documents/computers:Computers are not intelligent /home/br/Documents/readme:I hope you are following so far!

grep -w показывает только совпадающие целиком слова.

$ grep follow ~/Documents/readme I hope you are following so far! $ grep -w follow ~/Documents/readme $

cut извлекает часть файла (или, как обычно, входного потока). Команда определяет разделитель полей (который разделяет столбцы) с помощью опции -d , а порядковые номера столбцов для извлечения с помощью опции -f .

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

$ tail -n 5 metadata.csv | cut -d , -f 1 mysql.performance.user_time mysql.replication.seconds_behind_master mysql.replication.slave_running mysql.replication.slaves_connected mysql.performance.queries

Поскольку мы имеем дело с CSV, то столбцы разделяются запятой, а за извлечение первого столбца отвечает опция -f 1 .

Можно выбрать и первый, и второй столбцы, используя опцию -f 1,2 .

$ tail -n 5 metadata.csv | cut -d , -f 1,2 mysql.performance.user_time,gauge mysql.replication.seconds_behind_master,gauge mysql.replication.slave_running,gauge mysql.replication.slaves_connected,gauge mysql.performance.queries,gauge

paste

paste объединяет вместе два разных файла в один многоколоночный файл.

$ cat ingredients eggs milk butter tomatoes $ cat prices 1$ 1.99$ 1.50$ 2$/kg $ paste ingredients prices eggs 1$ milk 1.99$ butter 1.50$ tomatoes 2$/kg

По умолчанию paste использует разделитель табуляции, но его можно изменить с помощью параметра -d .

$ paste ingredients prices -d: eggs:1$ milk:1.99$ butter:1.50$ tomatoes:2$/kg

Ещё один распространённый способ использования paste — объединение всех строк в потоке или файле с помощью заданного разделителя, используя комбинацию аргументов -s и -d .

$ paste -s -d, ingredients eggs,milk,butter,tomatoes

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

$ cat ingredients | paste -s -d, - eggs,milk,butter,tomatoes

sort

Команда sort , собственно, сортирует данные (в указанном файле или входном потоке).

$ cat ingredients eggs milk butter tomatoes salt $ sort ingredients butter eggs milk salt tomatoes

sort -r выполняет обратную сортировку.

$ sort -r ingredients tomatoes salt milk eggs butter

sort -n сортирует поля по их арифметическому значению.

$ cat numbers 0 2 1 10 3 $ sort numbers 0 1 10 2 3 $ sort -n numbers 0 1 2 3 10

uniq

uniq обнаруживает и отфильтровывает соседние одинаковые строки в указанном файле или входном потоке.

$ cat duplicates and one and one and two and one and two and one, two, three $ uniq duplicates and one and two and one and two and one, two, three

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

$ sort duplicates | uniq and one and one, two, three and two

uniq -c в начале каждой строки вставляет количество её вхождений.

$ sort duplicates | uniq -c 3 and one 1 and one, two, three 2 and two

uniq -u отображает только уникальные строки.

$ sort duplicates | uniq -u and one, two, three

Примечание. uniq особенно полезен в сочетании с сортировкой, поскольку конвейер | sort | uniq позволяет удалить все дублирующиеся строки в файле или потоке.

awk — это чуть больше, чем просто инструмент обработки текста: на самом деле у него целый язык программирования. В чём awk действительно хорош — так это в разбиении файлов на столбцы, и делает это с особенным блеском, когда в файлах перемешаны пробелы и табы.

$ cat -t multi-columns John Smith Doctor^ITardis Sarah-James Smith^I Companion^ILondon Rose Tyler Companion^ILondon

Примечание. cat -t отображает табы как ^I .

Как видим, столбцы разделены либо пробелами, либо табуляциями, и не всегда одинаковым количеством пробелов. cut здесь бесполезен, потому что работает только с одним символом-разделителем. Но awk легко разберётся с таким файлом.

awk '< print $n >' выводит n-й столбец в тексте.

$ cat multi-columns | awk '< print $1 >' John Sarah-James Rose $ cat multi-columns | awk '< print $3 >' Doctor Companion Companion $ cat multi-columns | awk '< print $1,$2 >' John Smith Sarah-James Smith Rose Tyler

Хотя awk способен на гораздо большее, выдача колонок составляет, наверное, 99% вариантов использования в моём личном случае.

Примечание. < print $NF >выводит последний столбец в строке.

tr расшифровывается как translate. Эта команда заменяет одни символы на другие. Она работает либо с символами, либо с классами символов, такими как строчные, печатные, пробелы, буквенно-цифровые и т. д.

На стандартных входных данных tr заменяет все вхождения на .

$ echo "Computers are fast" | tr a A computers Are fAst

tr может переводить классы символов с помощью нотации [:class:] . Полный список доступных классов описан на справочной странице tr , но некоторые продемонстрируем здесь.

[:space:] представляет все типы пробелов, от простого пробела до табуляции или символа новой строки.

$ echo "computers are fast" | tr '[:space:]' ',' computers,are,fast,%

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

[:lower:] представляет все строчные символы, а [:upper:] — все прописные. Таким образом, преобразование между ними становится тривиальным.

$ echo "computers are fast" | tr '[:lower:]' '[:upper:]' COMPUTERS ARE FAST $ echo "COMPUTERS ARE FAST" | tr '[:upper:]' '[:lower:]' computers are fast

tr -c SET1 SET2 преобразует любой символ, не входящий в набор SET1, в символы набора SET2. В следующем примере все символы, кроме указанных гласных, заменяются пробелами.

$ echo "computers are fast" | tr -c '[aeiouy]' ' ' o u e a e a

tr -d удаляет указанные символы, а не заменяет их. Это эквивалент tr '' .

$ echo "Computers Are Fast" | tr -d '[:lower:]' C A F

tr также может заменить диапазоны символов, например, все буквы между a и e или все числа между 1 и 8, используя нотацию s-e , где s — начальный символ, а e — конечный.

$ echo "computers are fast" | tr 'a-e' 'x' xomputxrs xrx fxst $ echo "5uch l337 5p34k" | tr '1-4' 'x' 5uch lxx7 5pxxk

Команда tr -s string1 сжимает все множественные вхождения символов в string1 в одно-единственное. Одним из наиболее полезных применений tr -s является замена нескольких последовательных пробелов одним.

$ echo "Computers are fast" | tr -s ' ' Computers are fast

fold

Команда fold сворачивает все входные строки до заданной ширины. Например, может быть полезно убедиться, что текст помещается на дисплеях небольшого размера. Так, fold -w n укладывает строки по ширине n символов.

$ cat ~/Documents/readme | fold -w 16 Thanks again for reading this bo ok! I hope you're fo llowing so far!

Команда fold -s будет разбивать строки только на символах пробела. Её можно объединить с предыдущей, чтобы ограничить строким заданным количеством символом.

Thanks again for reading this book! I hope you're following so far!

sed — это неинтерактивный потоковый редактор, который используется для преобразования текста во входном потоке строка за строкой. В качестве входных данных используется или файл, или stdin , а на выходе тоже или файл, или stdout .

Команды редактора могут включать один или несколько адресов, функцию и параметры. Таким образом, команды выглядят следующим образом:

[address[,address]]function[arguments]

Хотя sed выполняет множество функций, мы рассмотрим только замену текста как один из самых распространённых вариантов использования.

Замена текста

Команда замены sed выглядит следующим образом:

s/PATTERN/REPLACEMENT/[options]

Пример: замена первого экземпляра слова в каждой строке в файле:

$ cat hello hello hello hello world! hi $ cat hello | sed 's/hello/Hey I just met you/' Hey I just met you hello Hey I just met you world hi

Мы видим, что в первой строчке заменяется только первый экземпляр hello . Чтобы заменить все вхождения hello во всех строках, можно использовать опцию g (означает global).

$ cat hello | sed 's/hello/Hey I just met you/g' Hey I just met you Hey I just met you Hey I just met you world hi

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

$ cat hello | sed 's@hello@Hey I just met you@g' Hey I just met you Hey I just met you Hey I just met you world hi

Адрес говорит редактору, в какой строке или диапазоне строк выполнять подстановку.

$ cat hello | sed '1s/hello/Hey I just met you/g' Hey I just met you hello hello world hi $ cat hello | sed '2s/hello/Hey I just met you/g' hello hello Hey I just met you world hi

Адрес 1 указывает заменять hello на Hey I just met you в первой строке. Можем указать диапазон адресов в нотации , , где может быть либо номером строки, либо $ , то есть последней строкой в файле.

$ cat hello | sed '1,2s/hello/Hey I just met you/g' Hey I just met you Hey I just met you Hey I just met you world hi $ cat hello | sed '2,3s/hello/Hey I just met you/g' hello hello Hey I just met you world hi $ cat hello | sed '2,$s/hello/Hey I just met you/g' hello hello Hey I just met you world hi

По умолчанию sed выдаёт результат в свой stdout , но может отредактировать и оригинальный файл с опцией -i .

$ sed -i '' 's/hello/Bonjour/' sed-data $ cat sed-data Bonjour hello Bonjour world hi

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

Реальные примеры

Фильтрация CSV с помощью grep и awk

$ grep -w gauge metadata.csv | awk -F, ' < if ($4 == "query") < print $1, "per", $5 >>' mysql.performance.com_delete per second mysql.performance.com_delete_multi per second mysql.performance.com_insert per second mysql.performance.com_insert_select per second mysql.performance.com_replace_select per second mysql.performance.com_select per second mysql.performance.com_update per second mysql.performance.com_update_multi per second mysql.performance.questions per second mysql.performance.slow_queries per second mysql.performance.queries per second

В этом примере grep в файле metadata.csv сначала фильтрует строки, содержащие слово gauge , затем те, у которых query в четвёртой колонке, и выводит название метрики (1-я колонка) с соответствующим значением per_unit_name (5-я колонка).

Вывод адреса IPv4, связанного с сетевым интерфейсом

$ ifconfig en0 | grep inet | grep -v inet6 | awk '< print $2 >' 192.168.0.38

Команда ifconfig выводит сведения по указанному сетевому интерфейсу. Например:

en0: flags=8863 mtu 1500 ether 19:64:92:de:20:ba inet6 fe80::8a3:a1cb:56ae:7c7c%en0 prefixlen 64 secured scopeid 0x7 inet 192.168.0.38 netmask 0xffffff00 broadcast 192.168.0.255 nd6 options=201 media: autoselect status: active

Затем запускаем grep для inet , что выдаст две строки соответствия.

$ ifconfig en0 | grep inet inet6 fe80::8a3:a1cb:56ae:7c7c%en0 prefixlen 64 secured scopeid 0x7 inet 192.168.0.38 netmask 0xffffff00 broadcast 192.168.0.255

Затем с помощью grep -v исключаем строку с ipv6 .

$ ifconfig en0 | grep inet | grep -v inet6 inet 192.168.0.38 netmask 0xffffff00 broadcast 192.168.0.255

Наконец, с помощью awk запрашиваем второй столбец в этой строке: это IPv4-адрес, связанный с нашим сетевым интерфейсом en0 .

$ ifconfig en0 | grep inet | grep -v inet6 | awk '< print $2 >' 192.168.0.38

Примечание. Мне предложили заменить grep inet | grep -v inet6 такой надёжной командой awk :

$ ifconfig en0 | awk ' $1 == "inet" < print $2 >' 192.168.0.38

Она короче и конкретно нацелена на IPv4 с условием $1 == "inet" .

Извлечение значения из файла конфигурации

$ grep 'editor =' ~/.gitconfig | cut -d = -f2 | sed 's/ //g' /usr/bin/vim

В файле конфигурации git текущего пользователя ищем значение editor = , обрезаем знак = , извлекаем второй столбец и удаляем все пробелы вокруг.

$ grep 'editor =' ~/.gitconfig editor = /usr/bin/vim $ grep 'editor =' ~/.gitconfig | cut -d'=' -f2 /usr/bin/vim $ grep 'editor =' ~/.gitconfig | cut -d'=' -f2 | sed 's/ //' /usr/bin/vim

Извлечение IP-адресов из файла журнала

Следующий реальный код ищет в журнале БД сообщение Too many connections from (за ним следует IP-адрес) и отображает десять главных нарушителей.

$ grep 'Too many connections from' db.log | \ awk '< print $12 >' | \ sed 's@/@@' | \ sort | \ uniq -c | \ sort -rn | \ head -n 10 | \ awk '< print $2 >' 10.11.112.108 10.11.111.70 10.11.97.57 10.11.109.72 10.11.116.156 10.11.100.221 10.11.96.242 10.11.81.68 10.11.99.112 10.11.107.120

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

$ grep "Too many connections from" db.log | head -n 1 2020-01-01 08:02:37,617 [myid:1] - WARN [NIOServerCxn.Factory:1.2.3.4/1.2.3.4:2181:NIOServerCnxnFactory@193] - Too many connections from /10.11.112.108 - max is 60

Затем awk '< print $12 >' извлекает из строки IP-адрес.

$ grep "Too many connections from" db.log | awk '< print $12 >' /10.11.112.108 . 

Команда sed 's@/@@' удаляет начальный слэш.

$ grep "Too many connections from" db.log | awk '< print $12 >' | sed 's@/@@' 10.11.112.108 . 

Примечание. Как мы уже видели ранее, в sed можно использовать любой разделитель. Хотя обычно в качестве разделителя используется / , здесь мы заменяем именно этот символ, что слегка ухудшит читаемость выражения подстановки.

sed 's/\///'

sort | uniq -c сортирует IP-адреса в лексикографическом порядке, а затем удаляет дубликаты, добавляя перед IP-адресами количество вхождений каждого.

$ grep 'Too many connections from' db.log | \ awk '< print $12 >' | \ sed 's@/@@' | \ sort | \ uniq -c 1379 10.11.100.221 1213 10.11.103.168 1138 10.11.105.177 946 10.11.106.213 1211 10.11.106.4 1326 10.11.107.120 . 

sort -rn | head -n 10 сортирует строки по количеству вхождений, численно и в обратном порядке, чтобы главные нарушители выводились в первую очередь, из которых отображаются 10 строк. Последняя команда awk < print $2 >извлекает сами IP-адреса.

$ grep 'Too many connections from' db.log | \ awk '< print $12 >' | \ sed 's@/@@' | \ sort | \ uniq -c | \ sort -rn | \ head -n 10 | \ awk '< print $2 >' 10.11.112.108 10.11.111.70 10.11.97.57 10.11.109.72 10.11.116.156 10.11.100.221 10.11.96.242 10.11.81.68 10.11.99.112 10.11.107.120

Переименование функции в исходном файле

Представим, что мы работаем над проектом и хотели бы переименовать недачно названную функцию (или класс, переменную и т. д.) в исходном файле. Можно сделать это с помощью команды sed -i , которая выполняет замену прямо в оригинальном файле.

$ cat izk/utils.py def bool_from_str(s): if s.isdigit(): return int(s) == 1 return s.lower() in ['yes', 'true', 'y']
$ sed -i 's/def bool_from_str/def is_affirmative/' izk/utils.py $ cat izk/utils.py def is_affirmative(s): if s.isdigit(): return int(s) == 1 return s.lower() in ['yes', 'true', 'y']

Примечание. На macOS вместо sed -i используйте sed -i '' .

Однако мы переименовали функцию только в оригинальном файле. Это сломает импорт bool_from_str в любом другом файле, поскольку эта функция больше не определена. Нужно найти способ переименовать bool_from_str повсюду в нашем проекте. Такого можно добиться с помощью команд grep , sed , а также циклов for или с помощью xargs .

Углубляемся: циклы for и xargs

Чтобы заменить в нашем проекте все вхождения bool_from_str , сначала нужно рекурсивно найти их с помощью grep -r .

$ grep -r bool_from_str . ./tests/test_utils.py:from izk.utils import bool_from_str ./tests/test_utils.py:def test_bool_from_str(s, expected): ./tests/test_utils.py: assert bool_from_str(s) == expected ./izk/utils.py:def bool_from_str(s): ./izk/prompt.py:from .utils import bool_from_str ./izk/prompt.py: default = bool_from_str(os.environ[envvar])

Поскольку нас интересуют только файлы c совпадениями, также необходимо использовать опцию -l/--files-with-matches :

-l, --files-with-matches Only the names of files containing selected lines are written to standard out- put. grep will only search a file until a match has been found, making searches potentially less expensive. Pathnames are listed once per file searched. If the standard input is searched, the string ``(standard input)'' is written.
$ grep -r --files-with-matches bool_from_str . ./tests/test_utils.py ./izk/utils.py ./izk/prompt.py

Затем можем использовать команду xargs для осуществления действий с каждой строки выходных данных (то есть всех файлов, содержащих строку bool_from_str ).

$ grep -r --files-with-matches bool_from_str . | \ xargs -n 1 sed -i 's/bool_from_str/is_affirmative/'

Опция -n 1 указывает, что каждая строка в выходных данных должна выполнить отдельную команду sed .

Затем выполняются следующие команды:

$ sed -i 's/bool_from_str/is_affirmative/' ./tests/test_utils.py $ sed -i 's/bool_from_str/is_affirmative/' ./izk/utils.py $ sed -i 's/bool_from_str/is_affirmative/' ./izk/prompt.py

Если команда, которую вы вызываете с помощью xargs (в нашем случае sed ), поддерживает несколько аргументов, то следует отбросить аргумент -n 1 для производительности.

grep -r --files-with-matches bool_from_str . | xargs sed -i 's/bool_from_str/is_affirmative/'

Эта команда затем исполнит

$ sed -i 's/bool_from_str/is_affirmative/' ./tests/test_utils.py ./izk/utils.py ./izk/prompt.py

Примечание. Из синопсиса sed на ман-странице видно, что команда может принять несколько аргументов.

SYNOPSIS sed [-Ealn] command [file . ] sed [-Ealn] [-e command] [-f command_file] [-i extension] [file . ]

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

Мы видим, что произведены замены для всех вхождений bool_from_str .

$ grep -r is_affirmative . ./tests/test_utils.py:from izk.utils import is_affirmative ./tests/test_utils.py:def test_is_affirmative(s, expected): ./tests/test_utils.py: assert is_affirmative(s) == expected ./izk/utils.py:def is_affirmative(s): ./izk/prompt.py:from .utils import is_affirmative ./izk/prompt.py: default = is_affirmative(os.environ[envvar])

Как это часто бывает, существует несколько способов достижения одного и того же результата. Вместо xargs мы могли бы использовать циклы for , чтобы перебирать строки по списку и выполнять действие над каждым элементом. У этих циклов такой синтаксис:

for item in list; do command $item done

Если обернуть нашу команду grep в $() , то оболочка выполнит её в подоболочке, результат чего затем будет повторён в цикле for .

$ for file in $(grep -r --files-with-matches bool_from_str .); do sed -i 's/bool_from_str/is_affirmative/' $file done

Эта команда выполнит

$ sed -i 's/bool_from_str/is_affirmative/' ./tests/test_utils.py $ sed -i 's/bool_from_str/is_affirmative/' ./izk/utils.py $ sed -i 's/bool_from_str/is_affirmative/' ./izk/prompt.py

Синтаксис циклов for кажется мне более чётким, чем у xargs , однако последняя может выполнять команды параллельно, используя параметры -P n , где n — максимальное количество параллельных команд, выполняемых одновременно, что может дать выигрыш в производительности.

Резюме

Все эти инструменты открывают целый мир возможностей, так как позволяют извлекать и преобразовывать данные, создавая целые конвейеры из команд, которые, возможно, никогда не предназначались для совместной работы. Каждая из них выполняет относительно небольшую функцию (сортировка sort , объединение cat , фильтры grep , редактирование sed , вырезание cut и т. д.).

Любую задачу, включающую текст, можно свести к конвейеру более мелких задач, каждая из которых выполняет простое действие и передаёт свои выходные данные в следующую задачу.

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

  • grep строк, которые соответствуют шаблону строк с IP-адресами
  • найти столбец с IP-адресом, извлечь его с помощью awk
  • отсортировать список IP-адресов с помощью sort
  • устранить смежные дубликаты с помощью uniq
  • подсчитать количество строк (то есть уникальных IP-адресов) с помощью wc -l

Примеры в этой статье были надуманными, но я предлагаю вам прочитать удивительную статью «Инструменты командной строки могут быть в 235 раз быстрее, чем ваш кластер Hadoop», чтобы получить представление о том, насколько полезны и мощны эти команды на самом деле и какие реальные проблемы они могут решить.

Что дальше

  1. Подсчитайте количество файлов и каталогов, расположенных в вашем домашнем каталоге.
  2. Отобразите содержимое файла только прописными буквами.
  3. Подсчитайте, сколько раз встречалось каждое слово в файле.
  4. Подсчитайте количество гласных в файле. Отсортируйте результат от наиболее распространённой до наименее распространённой буквы.

Если интересно поучаствовать в проекте, подписывайтесь на список рассылки!

Исправляем опечатки в поисковых запросах

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

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

Может показаться, что мы отказали пользователю в его мечте о вертикальной реальности, но на самом деле буква К просто стоит на клавиатуре рядом с буквой У.

В этой статье мы разберём один из классических подходов к исправлению опечаток, от построения модели до написания кода на Python и Go. И в качестве бонуса — видео с моего доклада «”Очки верткальной реальности”: исправляем опечатки в поисковых запросах» на Highload++.

Постановка задачи

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

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

Неясных моментов здесь два — где взять словарь и как посчитать . Первый вопрос считается простым. В 1990 году [1] словарь собирали из базы утилиты spell и доступных в электронном виде словарей; в 2009 году в Google [4] поступили проще и просто взяли топ самых популярных слов в Интернете (вместе с популярными ошибочными написаниями). Этот подход взял и я для построения своего опечаточника.

Второй вопрос сложнее. Хотя бы потому, что его решение обычно начинается с применения формулы Байеса!

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

Как оценить ? Очевидно, что пользователь с большей вероятностью путает А с О, чем Ъ с Ы. А если мы исправляем текст, распознанный с отсканированного документа, то велика вероятность путаницы между rn и m. Так или иначе, нам нужна какая-то модель, описывающая ошибки и их вероятности.

Такая модель называется noisy channel model (модель зашумлённого канала; в нашем случае зашумлённый канал начинается где-то в центре Брока пользователя и заканчивается по другую сторону его клавиатуры) или более коротко error model — модель ошибок. Эта модель, которой ниже посвящен отдельный раздел, будет ответственна за учёт как орфографических ошибок, так и, собственно, опечаток.

Оценить вероятность использования слова — — можно по-разному. Самый простой вариант — взять за неё частоту, с которой слово встречается в некотором большом корпусе текстов. Для нашего опечаточника, учитывающего контекст фразы, потребуется, конечно, что-то более сложное — ещё одна модель. Эта модель называется language model, модель языка.

Модель ошибок

Первые модели ошибок считали , подсчитывая вероятности элементарных замен в обучающей выборке: сколько раз вместо Е писали И, сколько раз вместо ТЬ писали Т, вместо Т — ТЬ и так далее [1]. Получалась модель с небольшим числом параметров, способная выучить какие-то локальные эффекты (например, что люди часто путают Е и И).

В наших изысканиях мы остановились на более развитой модели ошибок, предложенной в 2000 году Бриллом и Муром [2] и многократно использованной впоследствии (например, специалистами Google [4]). Представим, что пользователи мыслят не отдельными символами (спутать Е и И, нажать К вместо У, пропустить мягкий знак), а могут изменять произвольные куски слова на любые другие — например, заменять ТСЯ на ТЬСЯ, У на К, ЩА на ЩЯ, СС на С и так далее. Вероятность того, что пользователь опечатался и вместо ТСЯ написал ТЬСЯ, обозначим — это параметр нашей модели. Если для всех возможных фрагментов мы можем посчитать , то искомую вероятность набора слова s при попытке набрать слово w в модели Брилла и Мура можно получить следующим образом: разобьем всеми возможными способами слова w и s на более короткие фрагменты так, чтобы фрагментов в двух словах было одинаковое количество. Для каждого разбиения посчитаем произведение вероятностей всех фрагментов w превратиться в соответствующие фрагменты s. Максимум по всем таким разбиениям и примем за значение величины :

Давайте посмотрим на пример разбиения, возникающего при вычислении вероятности напечатать «аксесуар» вместо «аксессуар»:

Как вы наверняка заметили, это пример не очень удачного разбиения: видно, что части слов легли друг под другом не так удачно, как могли бы. Если величины и ещё не так плохи, то и , скорее всего, сделают итоговый «счёт» этого разбиения совсем печальным. Более удачное разбиение выглядит как-то так:

Здесь всё сразу стало на свои места, и видно, что итоговая вероятность будет определяться преимущественно величиной .

Как вычислить

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

Мы заведём прямоугольную таблицу, строки которой будут соответствовать буквам правильного слова, а столбцы — опечатанного. В ячейке на пересечении строки i и столбца j к концу алгоритма будет лежать в точности вероятность получить s[:j] при попытке напечатать w[:i] . Для того, чтобы её вычислить, достаточно вычислить значения всех ячеек в предыдущих строках и столбцах и пробежаться по ним, домножая на соответствующие . Например, если у нас заполнена таблица

, то для заполнения ячейки в четвёртой строке и третьем столбце (серая) нужно взять максимум из величин и . При этом мы пробежались по всем ячейкам, подсвеченным на картинке зелёным. Если также рассматривать модификации вида и , то придётся пробежаться и по ячейкам, подсвеченным жёлтым.

Сложность этого алгоритма, как я уже упомянул выше, составляет : мы заполняем таблицу , и для заполнения ячейки (i, j) нужно операций. Впрочем, если мы ограничим рассмотрение фрагментами не больше какой-то ограниченной длины (например, не больше двух букв, как в [4]), сложность уменьшится до . Для русского языка в своих экспериментах я брал .

Как максимизировать

Мы научились находить за полиномиальное время — это хорошо. Но нам нужно научиться быстро находить наилучшие слова во всём словаре. Причём наилучшие не по , а по ! На деле нам достаточно получить какой-то разумный топ (например, лучшие 20) слов по , который мы потом отправим в модель языка для выбора наиболее подходящих исправлений (об этом ниже).

Чтобы научиться быстро проходить по всему словарю, заметим, что таблица, представленная выше, будет иметь много общего для двух слов с общими префиксами. Действительно, если мы, исправляя слово «аксесуар», попробуем заполнить её для двух словарных слов «аксессуар» и «аксессуары», мы заметим, что первые девять строк в них вообще не отличаются! Если мы сможем организовать проход по словарю так, что у двух последующих слов будут достаточно длинные общие префиксы, мы сможем круто сэкономить вычисления.

И мы сможем. Давайте возьмём словарные слова и составим из них trie. Идя по нему поиском в глубину, мы получим желаемое свойство: большинство шагов — это шаги от узла к его потомку, когда у таблицы достаточно дозаполнить несколько последних строк.

Этот алгоритм, при некоторых дополнительных оптимизациях, позволяет нам перебирать словарь типичного европейского языка в 50-100 тысяч слов в пределах сотни миллисекунд [2]. А кэширование результатов сделает процесс ещё быстрее.

Как получить

Вычисление для всех рассматриваемых фрагментов — самая интересная и нетривиальная часть построения модели ошибок. Именно от этих величин будет зависеть её качество.

Подход, использованный в [2, 4], сравнительно прост. Давайте найдём много пар , где — правильное слово из словаря, а — его опечатанный вариант. (Как именно их находить — чуть ниже.) Теперь нужно извлечь из этих пар вероятности конкретных опечаток (замен одних фрагментов на другие).

Для каждой пары возьмём составляющие её и и построим соответствие между их буквами, минимизирующее расстояние Левенштейна:

Теперь мы сразу видим замены: а → а, е → и, с → с, с → пустая строка и так далее. Также мы видим замены двух и более символов: ак → ак, се → си, ес → ис, сс → с, сес → сис, есс → ис и прочая, и прочая. Все эти замены необходимо посчитать, причём каждую столько раз, сколько слово s встречается в корпусе (если мы брали слова из корпуса, что очень вероятно).

После прохода по всем парам за вероятность принимается количество замен α → β, встретившихся в наших парах (с учётом встречаемости соответствующих слов), делённое на количество повторений фрагмента α.

Как найти пары ? В [4] предлагается такой подход. Возьмём большой корпус сгенерированного пользователями контента (UGC). В случае Google это были просто тексты сотен миллионов веб-страниц; в нашем — миллионы пользовательских поисковых запросов и отзывов. Предполагается, что обычно правильное слово встречается в корпусе чаще, чем любой из ошибочных вариантов. Так вот, давайте для каждого слова находить близкие к нему по Левенштейну слова из корпуса, которые значительно менее популярны (например, в десять раз). Популярное возьмём за , менее популярное — за . Так мы получим пусть и шумный, но зато достаточно большой набор пар, на котором можно будет провести обучение.

Этот алгоритм подбора пар оставляет большое пространство для улучшений. В [4] предлагается только фильтр по встречаемости ( в десять раз популярнее, чем ), но авторы этой статьи пытаются делать опечаточник, не используя какие-либо априорные знания о языке. Если мы рассматриваем только русский язык, мы можем, например, взять набор словарей русских словоформ и оставлять только пары со словом , найденном в словаре (не лучшая идея, потому что в словаре, скорее всего, не будет специфичной для сервиса лексики) или, наоборот, отбрасывать пары со словом s, найденном в словаре (то есть почти гарантированно не являющимся опечатанным).

Чтобы повысить качество получаемых пар, я написал несложную функцию, определяющую, используют ли пользователи два слова как синонимы. Логика простая: если слова w и s часто встречаются в окружении одних и тех же слов, то они, вероятно, синонимы — что в свете их близости по Левенштейну значит, что менее популярное слово с большой вероятностью является ошибочной версией более популярного. Для этих расчётов я использовал построенную для модели языка ниже статистику встречаемости триграмм (фраз из трёх слов).

Модель языка

Итак, теперь для заданного словарного слова w нам нужно вычислить — вероятность его использования пользователем. Простейшее решение — взять встречаемость слова в каком-то большом корпусе. Вообще, наверное, любая модель языка начинается с собирания большого корпуса текстов и подсчёта встречаемости слов в нём. Но ограничиваться этим не стоит: на самом деле при вычислении P(w) мы можем учесть также и фразу, слово в которой мы пытаемся исправить, и любой другой внешний контекст. Задача превращается в задачу вычисления , где одно из — слово, в котором мы исправили опечатку и для которого мы теперь рассчитываем , а остальные — слова, окружающие исправляемое слово в пользовательском запросе.

Чтобы научиться учитывать их, стоит пройтись по корпусу ещё раз и составить статистику n-грамм, последовательностей слов. Обычно берут последовательности ограниченной длины; я ограничился триграммами, чтобы не раздувать индекс, но тут всё зависит от вашей силы духа (и размера корпуса — на маленьком корпусе даже статистика по триграммам будет слишком шумной).

Традиционная модель языка на основе n-грамм выглядит так. Для фразы её вероятность вычисляется по формуле

где — непосредственно частота слова, а — вероятность слова при условии, что перед ним идут — не что иное, как отношение частоты триграммы к частоте биграммы . (Заметьте, что эта формула — просто результат многократного применения формулы Байеса.)

Иными словами, если мы захотим вычислить , обозначив частоту произвольной n-граммы за , мы получим формулу

Логично? Логично. Однако трудности начинаются, когда фразы становятся длиннее. Что, если пользователь ввёл впечатляющий своей подробностью поисковый запрос в десять слов? Мы не хотим держать статистику по всем 10-граммам — это дорого, а данные, скорее всего, будут шумными и не показательными. Мы хотим обойтись n-граммами какой-то ограниченной длины — например, уже предложенной выше длины 3.

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

Положив , для более длинной фразы получим формулу

Обратите внимание: фраза состоит из пяти слов, но в формуле фигурируют n-граммы не длиннее трёх. Это как раз то, чего мы добивались.

Остался один тонкий момент. Что, если пользователь ввёл совсем странную фразу и соответствующих n-грамм у нас в статистике и нет вовсе? Было бы легко для незнакомых n-грамм положить , если бы на эту величину не надо было делить. Здесь на помощь приходит сглаживание (smoothing), которое можно делать разными способами; однако подробное обсуждение серьёзных подходов к сглаживанию вроде Kneser-Ney smoothing выходит далеко за рамки этой статьи.

Как исправлять фразы

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

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

Можно рассматривать слова по отдельности и применять некий классификатор, чтобы понимать, опечатано данное слово или нет, как это предложено в [4]. Классификатор обучается на вероятностях, которые мы уже умеем считать, и ряде других фичей. Если классификатор говорит, что нужно исправлять — исправляем, учитывая имеющийся контекст. Опять-таки, если несколько слов написаны с ошибкой, принимать решение насчёт первого из них придётся, опираясь на контекст с ошибками, что может приводить к проблемам с качеством.

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

Здесь — слова, введённые пользователем, — подобранные для них исправления (которые мы сейчас перебираем), а — коэффициент, определяемый сравнительным качеством модели ошибок и модели языка (большой коэффициент — больше доверяем модели языка, маленький коэффициент — больше доверяем модели ошибок), предложенный в [4]. Итого для каждой фразы мы перемножаем вероятности отдельных слов исправиться в соответствующие словарные варианты и ещё домножаем это на вероятность всей фразы в нашем языке. Результат работы алгоритма — фраза из словарных слов, максимизирующая эту величину.

Так, стоп, что? Перебор фраз?

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

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

Реализация

Собираем корпус и считаем n-граммы

Сразу оговорюсь: данных в моём распоряжении было не так много, чтобы заводить какой-то сложный MapReduce. Так что я просто собрал все тексты отзывов, комментариев и поисковых запросов на русском языке (описания товаров, увы, приходят на английском, а использование результатов автоперевода скорее ухудшило, чем улучшило результаты) с нашего сервиса в один текстовый файл и поставил сервер на ночь считать триграммы простым скриптом на Python.

В качестве словарных я брал топ слов по частотности так, чтобы получалось порядка ста тысяч слов. Исключались слишком длинные слова (больше 20 символов) и слишком короткие (меньше трёх символов, кроме захардкоженных известных русских слов). Отдельно пощадил слова по регулярке r"^[a-z0-9]$" — чтобы уцелели версии айфонов и другие интересные идентификаторы длины 2.

При подсчёте биграмм и триграмм во фразе может встретиться несловарное слово. В этом случае это слово я выбрасывал и бил всю фразу на две части (до и после этого слова), с которыми работал отдельно. Так, для фразы «А вы знаете, что такое «абырвалг»? Это… ГЛАВРЫБА, коллега» учтутся триграммы “а вы знаете”, “вы знаете что”, “знаете что такое” и “это главрыба коллега” (если, конечно, слово “главрыба” попадёт в словарь. ).

Обучаем модель ошибок

Дальше всю обработку данных я проводил в Jupyter. Статистика по n-граммам грузится из JSON, производится постобработка, чтобы быстро находить близкие друг к другу по Левенштейну слова, и для пар в цикле вызывается (довольно громоздкая) функция, выстраивающая слова и извлекающая короткие правки вида сс → с (под спойлером).

Код на Python

def generate_modifications(intended_word, misspelled_word, max_l=2): # Выстраиваем буквы слов оптимальным по Левенштейну образом и # извлекаем модификации ограниченной длины. Чтобы после подсчёта # расстояния восстановить оптимальное расположение букв, будем # хранить в таблице помимо расстояний указатели на предыдущие # ячейки: memo будет хранить соответствие # i -> j -> (distance, prev i, prev j). # Дальше немного непривычно страшного Python кода - вот что # бывает, когда язык используется не по назначению! m, n = len(intended_word), len(misspelled_word) memo = [[None] * (n+1) for _ in range(m+1)] memo[0] = [(j, (0 if j > 0 else -1), j-1) for j in range(n+1)] for i in range(m + 1): memo[i][0] = i, i-1, (0 if i > 0 else -1) for j in range(1, n + 1): for i in range(1, m + 1): if intended_word[i-1] == misspelled_word[j-1]: memo[i][j] = memo[i-1][j-1][0], i-1, j-1 else: best = min( (memo[i-1][j][0], i-1, j), (memo[i][j-1][0], i, j-1), (memo[i-1][j-1][0], i-1, j-1), ) # Отдельная обработка для перепутанных местами # соседних букв (распространённая ошибка при # печати). if (i > 1 and j > 1 and intended_word[i-1] == misspelled_word[j-2] and intended_word[i-2] == misspelled_word[j-1] ): best = min(best, (memo[i-2][j-2][0], i-2, j-2)) memo[i][j] = 1 + best[0], best[1], best[2] # К концу цикла расстояние по Левенштейну между исходными словами # хранится в memo[m][n][0]. # Теперь восстанавливаем оптимальное расположение букв. s, t = [], [] i, j = m, n while i >= 1 or j >= 1: _, pi, pj = memo[i][j] di, dj = i - pi, j - pj if di == dj == 1: s.append(intended_word[i-1]) t.append(misspelled_word[j-1]) if di == dj == 2: s.append(intended_word[i-1]) s.append(intended_word[i-2]) t.append(misspelled_word[j-1]) t.append(misspelled_word[j-2]) if 1 == di > dj == 0: s.append(intended_word[i-1]) t.append("") if 1 == dj > di == 0: s.append("") t.append(misspelled_word[j-1]) i, j = pi, pj s.reverse() t.reverse() # Генерируем модификации длины не выше заданной. for i, _ in enumerate(s): ss = ts = "" while len(ss) < max_l and i < len(s): ss += s[i] ts += t[i] yield ss, ts i += 1

Сам подсчёт правок выглядит элементарно, хотя длиться может долго.

Применяем модель ошибок

Эта часть реализована в виде микросервиса на Go, связанного с основным бэкендом через gRPC. Реализован алгоритм, описанный самими Бриллом и Муром [2], с небольшими оптимизациями. Работает он у меня в итоге примерно вдвое медленнее, чем заявляли авторы; не берусь судить, дело в Go или во мне. Но по ходу профилировки я узнал о Go немного нового.

    Не используйте math.Max , чтобы считать максимум. Это примерно в три раза медленнее, чем if a > b < b = a >! Только взгляните на реализацию этой функции:

// Max returns the larger of x or y. // // Special cases are: // Max(x, +Inf) = Max(+Inf, x) = +Inf // Max(x, NaN) = Max(NaN, x) = NaN // Max(+0, ±0) = Max(±0, +0) = +0 // Max(-0, -0) = -0 func Max(x, y float64) float64 func max(x, y float64) float64 < // special cases switch < case IsInf(x, 1) || IsInf(y, 1): return Inf(1) case IsNaN(x) || IsNaN(y): return NaN() case x == 0 && x == y: if Signbit(x) < return y >return x > if x > y < return x >return y >

Применяем модель языка

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

Результаты

По итогам этой работы (занявшей примерно человекомесяц) мы провели A/B тест опечаточника на наших пользователях. Вместо 10% пустых выдач среди всех поисковых запросов, которые мы имели до внедрения опечаточника, их стало 5%; в основном оставшиеся запросы приходятся на товары, которых просто нет у нас на платформе. Также увеличилось количество сессий без второго поискового запроса (и ещё несколько метрик такого рода, связанных с UX). Метрики, связанные с деньгами, впрочем, значимо не изменились — это было неожиданно и сподвигло нас к тщательному анализу и перепроверке других метрик.

Заключение

Стивену Хокингу как-то сказали, что каждая включенная им в книгу формула вдвое уменьшит число читателей. Что ж, в этой статье их порядка полусотни — поздравляю тебя, один из примерно читателей, добравшихся до этого места!

Бонус

Ссылки

[1] M. D. Kernighan, K. W. Church, W. A. Gale. A Spelling Correction Program Based on a Noisy Channel Model. Proceedings of the 13th conference on Computational linguistics — Volume 2, 1990.

[2] E.Brill, R. C. Moore. An Improved Error Model for Noisy Channel Spelling Correction. Proceedings of the 38th Annual Meeting on Association for Computational Linguistics, 2000.

[3] T. Brants, A. C. Popat, P. Xu, F. J. Och, J. Dean. Large Language Models in Machine Translation. Proceedings of the 2007 Conference on Empirical Methods in Natural Language Processing.

[4] C. Whitelaw, B. Hutchinson, G. Y. Chung, G. Ellis. Using the Web for Language Independent Spellchecking and Autocorrection. Proceedings of the 2009 Conference on Empirical Methods in Natural Language Processing: Volume 2.

  • Блог компании Joom
  • Поисковые технологии
  • Программирование
  • Алгоритмы
  • Математика

Удаление завершающего символа новой строки в Python

Баннер Баннер

Часто во время работы с текстовыми данными в Python, разработчики сталкиваются с ситуацией, когда в конце строки появляется символ новой строки ( \n ). Этот символ обычно добавляется при чтении данных из текстового файла или в результате других операций с текстом.

Например, есть строка "Привет, мир!\n" . Задача — удалить символ новой строки из этой строки, чтобы получить "Привет, мир!" .

Python предоставляет несколько способов решения этой задачи.

Использование метода rstrip()

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

s = "Привет, мир!\n" s = s.rstrip('\n')

Теперь переменная s содержит строку "Привет, мир!" .

Использование срезов строк

Другой способ — использование срезов строк. С помощью срезов можно получить подстроку, исключив последний символ.

s = "Привет, мир!\n" s = s[:-1]

В этом случае, если последний символ строки является символом новой строки, он будет удален.

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *