Разделите слово, используя биграмму, триграмму

У меня есть этот текстовый файл:

worked
working
works
tested
tests
find
found

Он содержит миллион слов без пробелов. Он может содержать символы Юникода.

Самое длинное слово — «работает»:

awk '{print length, $0}' test.txt | sort -nr | head -1
7 working

Мне нужно создать биграмму, триграмму (максимум 7 столбцов)

w,wo,wor,work,worke,worked,
w,wo,wor,work,worki,workin,working
w,wo,wor,work,works,,
t,te,tes,test,teste,tested,
t,te,tes,test,tests,,
f,fi,fin,find,,,,
f,fo,fou,foun,found,,

желательно в awk (потому что это быстро)

Это не биграммы и триграммы. Вы имеете в виду, что хотите, чтобы все префиксы имели фиксированную длину, как предполагает ваш вывод; или это пример того, чего вы не хотите?

tripleee 29.08.2024 11:48

Если ваш реальный ввод может содержать символы Юникода, включите символы Юникода в образец ввода/вывода. Вам необходимо предоставить минимальный воспроизводимый пример, на котором мы можем протестировать, чтобы знать, работает ли данное решение или нет, и правильная обработка юникода может быть отличительной чертой.

Ed Morton 29.08.2024 13:16
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
55
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Прямой подход будет следующим:

awk -vn=7 -vOFS=, \
  '{s=$0; for (i=1;i<=n;i++) $i=i<=length(s)? substr(s,1,i): ""}1'
# or
# '{s=$0; for (i=1;i<=n;i++) $i=substr(s, i<=length(s)?1:n, i)}1'
w,wo,wor,work,worke,worked,
w,wo,wor,work,worki,workin,working
w,wo,wor,work,works,,
t,te,tes,test,teste,tested,
t,te,tes,test,tests,,
f,fi,fin,find,,,
f,fo,fou,foun,found,,

Кстати, вы могли бы немного ускорить это, не вызывая length(s) на каждой итерации цикла и вместо этого устанавливая/используя переменную, и вы могли бы сделать его более переносимым, добавив пробел после каждого -v (без него некоторые awks не сработают, это может быть только gawk, с этим все в порядке, я не знаю).

Ed Morton 29.08.2024 14:09

Вы также можете ускорить его еще больше, зацикливая только количество символов в строке, а затем один раз используя $n=$n для заполнения всех конечных пустых полей, т. е. awk -v n=7 -v OFS=, '{s=$0; len=length(s); for (i=1;i<=len;i++) $i=substr(s,1,i); $n=$n}1'. В тесте я выполнил 100 000 копий ввода OP, что было примерно на 15% быстрее, чем исходный сценарий, так что это не так уж и важно — разница, вероятно, больше при более различной длине ввода.

Ed Morton 29.08.2024 14:16

Если вы заранее не знаете максимальную длину строки, вы можете использовать:

awk '
 BEGIN {max_length=1}; { a[NR]=$1; m=length($1); if (m > max_length) {max_length=m} }
 END { for(i=1;i<=NR;i++) {for (j=1; j<=max_length;j++) {if (j<=length(a[i])){printf "%s", substr(a[i],1,j)}; if (j<max_length){ printf "%s", "," } }; printf "\n"} }
' file
w,wo,wor,work,worke,worked,
w,wo,wor,work,worki,workin,working
w,wo,wor,work,works,,
t,te,tes,test,teste,tested,
t,te,tes,test,tests,,
f,fi,fin,find,,,
f,fo,fou,foun,found,,

Придирка - print "" лучше, чем printf "\n", поскольку он использует значение, которое имеет ORS, а не жестко запрограммирует значение "\n", которое, как вы думаете/надеетесь, будет иметь ORS, и поэтому оно будет работать, например, даже на платформах, которые используют "\r\n" для ORS.

Ed Morton 29.08.2024 12:48

Awk не очень хорошо справляется с последовательностями компоновки Unicode. Возможно, стоит обратиться к Perl за удобной заменой.

Вот краткий тест со случайной строкой на вьетнамском языке, в которой используется множество акцентов.

bash$ printf '%s\n' "người" "đặt" | xxd
00000000: 6e67 c6b0 e1bb 9d69 0ac4 91e1 bab7 740a  ng.....i......t.

Вот демонстрация того, как nawk ведет себя с этим вводом:

bash$ printf '%s\n' "người" "đặt" |
> awk -vn=7 -vOFS=, '{s=$0; for (i=1;i<=n;i++) $i=i<=length(s)? substr(s,1,i): ""}1'
n,ng,ng?,ngư,ngư?,ngư?,ngườ
?,đ,đ?,đ?,đặ,đặt,

Вот быстрая и грязная реализация Perl:

bash$ printf '%s\n' "người" "đặt" |
> perl -CSD -ne 'BEGIN { $n = 7 }
> chomp; $sep = ""; for my $i (1..$n) {
>   print ($_ =~ "\\X{$i}" ? "$sep$&" : "$sep"); $sep = ","; }
> print "\n";'
n,ng,ngư,ngườ,người,,
đ,đặ,đặt,,,,

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

bash$ printf '%s\n' $'re\u0301sume\u0301' |
> perl -CSD -ne 'BEGIN { $n = 7 }
> chomp; $sep = ""; for my $i (1..$n) {
>   print ($_ =~ "\\X{$i}" ? "$sep$&" : "$sep"); $sep = ","; }
> print "\n";'
r,ré,rés,résu,résum,résumé,

Если это не очевидно, Perl прекрасно знает, что U+0301 является комбинирующим символом, который должен быть графически соединен с предыдущим базовым символом, и поэтому рассматривает полученный кластер как один символ (или, точнее, графему), насколько касается регулярного выражения \X. Поскольку у Awk нет этих знаний, он не может этого сделать. (Возможно, см. также Эквивалентность Юникода в Википедии.)

Если вам действительно нужны биграммы и триграммы, а не префиксы определенной длины, это тоже легко.

bash$ printf '%s\n' "người" "đặt" |
> perl -CSD -ne 'chomp; $sep = "";
> for my $i (0..length($_)) {
>     break unless ($_ =~ "\\X{$i}\\K\\X{2}");
>     print "$sep$&";
>     $sep = ",";
>     print ($_ =~ "\\X{$i}\\K\\X{3}" ? "$sep$&" : "");
> } print "\n";'
ng,ngư,gư,gườ,ườ,ười,ời
đặ,đặt,ặt

Использование любого awk (по крайней мере, для ввода символов английского языка), если вы уже знаете максимальную длину входной строки:

$ cat tst.awk
BEGIN { FS=OFS = ","; maxLen=7 }
{
    curLen = length($0)
    out = substr($0,1,1)
    for ( i=2; i<=curLen; i++ ) {
        out = out OFS substr($0,1,i)
    }
    $0 = out
    $maxLen = $maxLen
    print
}

$ awk -f tst.awk file
w,wo,wor,work,worke,worked,
w,wo,wor,work,worki,workin,working
w,wo,wor,work,works,,
t,te,tes,test,teste,tested,
t,te,tes,test,tests,,
f,fi,fin,find,,,
f,fo,fou,foun,found,,

в противном случае с помощью двухпроходного подхода сначала вычислите максимальную длину:

$ cat tst.awk
BEGIN { FS=OFS = "," }
{ curLen = length($0) }
NR == FNR {
    if ( curLen > maxLen ) {
        maxLen = curLen
    }
    next
}
{
    out = substr($0,1,1)
    for ( i=2; i<=curLen; i++ ) {
        out = out OFS substr($0,1,i)
    }
    $0 = out
    $maxLen = $maxLen
    print
}

$ awk -f tst.awk file file
w,wo,wor,work,worke,worked,
w,wo,wor,work,worki,workin,working
w,wo,wor,work,works,,
t,te,tes,test,teste,tested,
t,te,tes,test,tests,,
f,fi,fin,find,,,
f,fo,fou,foun,found,,

Учитывая вышесказанное, я

  1. Вызывайте length() только один раз перед началом цикла, чтобы awk не вызывал его на каждой итерации цикла.
  2. Заполняйте $0 только вне цикла, чтобы избежать повторного разделения и/или повторной конструкции awk $0 на каждой итерации цикла.
  3. Цикл только до длины текущей строки, а не максимальной длины всех строк,
  4. Создавайте пустые поля только до максимального количества полей 1 раз в строке ввода, используя $maxLen = $maxLen после цикла и
  5. Храните в памяти только одну строку за раз.

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

@tripleee отмечает в комментарии, что они получили неправильный вывод на MacOS из приведенного выше сценария при использовании символов Юникода. awk по умолчанию в MacOS, как известно, содержит ошибки, и у меня нет Mac для тестирования, поэтому я не собираюсь это исследовать, но, кстати, вот что я вижу из второго сценария выше, учитывая ввод @tripleee и использование gawk 5.3.0 на Cygwin с LC_ALL='en_US.UTF-8':

$ printf '%s\n' "người" "đặt" > file

$ awk -f tst.awk file file
n,ng,ngư,ngườ,người
đ,đặ,đặt,,

$ awk -b -f tst.awk file file
n,ng,ng▒,ngư,ngư▒,ngư▒,ngườ,người
▒,đ,đ▒,đ▒,đặ,đặt,,

$ printf '%s\n' 'résumé' 'zoölogy' > file

$ awk -f tst.awk file file
r,ré,rés,résu,résum,résumé,
z,zo,zoö,zoöl,zoölo,zoölog,zoölogy

$ awk -b -f tst.awk file file
r,r▒,ré,rés,résu,résum,résum▒,résumé
z,zo,zo▒,zoö,zoöl,zoölo,zoölog,zoölogy

$ printf '%s\n' $'re\u0301sume\u0301' > file

$ awk -f tst.awk file file
r,re,ré,rés,résu,résum,résume,résumé

$ awk -b -f tst.awk file file
r,re,re▒,ré,rés,résu,résum,résume,résume▒,résumé

Вот что делает -b (из руководства):

-б --символы как байты

Заставьте gawk обрабатывать все входные данные как однобайтовые символы. Кроме того, весь вывод, записанный с помощью print или printf, рассматривается как однобайтовые символы.

Обычно gawk следует стандарту POSIX и пытается обработать входные данные в соответствии с текущей локалью (см. «Где вы находитесь»). Имеет значение). Часто это может включать преобразование многобайтовых символы в широкие символы (внутренне), что может привести к проблемам. или путаница, если входные данные не содержат действительных многобайтовых данных персонажи. Этот вариант — простой способ сказать наблюдателю: «Руки прочь от меня». данные!"

Второй скрипт ничего не печатает на MacOS при вводе со стандартного ввода. Первый предсказуемо ведет себя плохо с такими данными, как резюме или зоология.

tripleee 29.08.2024 12:52

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

Ed Morton 29.08.2024 12:54

Ой, извините, я пропустил там двухпроходную конструкцию. Моя локаль — en_US.UTF-8, и мой Perl отлично работает с этими строками Unicode.

tripleee 29.08.2024 12:56

Если ОП скажет, что у них проблемы с обработкой строк Юникода, я посмотрю, но у меня нет Mac для тестирования. FWIW, используя gawk --posix на cygwin с той же локалью, что и у вас, я получаю тот же результат, что и ваш Perl-скрипт. Стандартный awk в MacOS, как известно, содержит ошибки, так что, возможно, это всего лишь одна из таких ошибок.

Ed Morton 29.08.2024 13:02

К сожалению, я получаю такой же искаженный результат с LC_ALL=C

tripleee 29.08.2024 13:03

С помощью brew install gawk я получаю правильный вывод для них, если входные данные нормализованы до одной кодовой точки для каждого глифа. С iconv -t utf-8-mac file | gawk '...' я получаю r,re,ré,rés,résu,résum,résume,résumé, т. е. он (как и ожидалось) рассматривает сочетающийся акцент как отдельный символ.

tripleee 29.08.2024 13:09

В ОП упоминается: «Он может содержать символы Юникода», хотя, возможно, они означают только простые составленные символы Latin-1.

tripleee 29.08.2024 13:11

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

Ed Morton 29.08.2024 13:12

Абсолютно согласен!

tripleee 29.08.2024 13:13

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

tripleee 29.08.2024 13:14

Я обновил свой ответ, упомянув ваш комментарий и показав gawk, работающий на предоставленном вами образце, спасибо.

Ed Morton 29.08.2024 13:15

Попробуйте также printf '%s\n' $'re\u0301sume\u0301'

tripleee 29.08.2024 13:19

@tripleee закончил, и я добавил вывод с опцией -b и без нее в конец моего ответа. Что-то из этого похоже на то, что вы ожидали?

Ed Morton 29.08.2024 13:21

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

tripleee 29.08.2024 13:23

Предполагая, что Perl это делает, можете ли вы добавить этот случай в свой ответ, чтобы я мог видеть, каким должен быть ожидаемый результат? Не знаю, что касается awk, но я, конечно, ничего не знаю о «кластерах графем Юникода», поэтому объяснение этой проблемы тоже было бы полезно, если вы не возражаете.

Ed Morton 29.08.2024 13:26

Спасибо за подсказку, я добавил к своему ответу простой пример.

tripleee 29.08.2024 13:31

А, теперь понял, спасибо.

Ed Morton 29.08.2024 13:33

Похоже, достойное обсуждение графем и т. д. на stackoverflow.com/q/27331819/1745001.

Ed Morton 29.08.2024 13:36

Другие вопросы по теме