Який найшвидший спосіб підрахувати кількість кожного символу у файлі?


121

Я хочу порахувати символи N та "-" "A" T's C у файлі, або кожну букву, якщо потрібно, чи є швидка команда Unix для цього?


56
Підрахунок основ у нитках ДНК?
Індрек

12
Мені подобається це питання, тому для вирішення однієї проблеми використовується багато різних підходів та інструментів.
Подорожник Geek

10
Хе, це прикордонний код-гольф
Earlz

13
якщо Somone цікавиться версією windows powerhell:[System.IO.File]::ReadAllText("C:\yourfile.txt").ToCharArray() | Group-Object $_ | Sort Count -Descending
Guillaume86

4
Гаразд я думаю, що я знайшов чистий ПС спосіб:Get-Content "C:\eula.3082.txt" | % { $_.ToCharArray() } | Group-Object | Sort Count -Descending
Guillaume86

Відповіді:


136

Якщо ви хочете реальної швидкості:

echo 'int cache[256],x,y;char buf[4096],letters[]="tacgn-"; int main(){while((x=read(0,buf,sizeof buf))>0)for(y=0;y<x;y++)cache[(unsigned char)buf[y]]++;for(x=0;x<sizeof letters-1;x++)printf("%c: %d\n",letters[x],cache[letters[x]]);}' | gcc -w -xc -; ./a.out < file; rm a.out;

Це неймовірно швидкий псевдооднолінійний.

Простий тест показує, що на моєму процесорі Core i7 870 при 2,93 ГГц він нараховує трохи більше 600 МБ / с:

$ du -h bigdna 
1.1G    bigdna

time ./a.out < bigdna 
t: 178977308
a: 178958411
c: 178958823
g: 178947772
n: 178959673
-: 178939837

real    0m1.718s
user    0m1.539s
sys     0m0.171s

На відміну від рішень, що включають сортування, це працює в постійній (4K) пам'яті, що дуже корисно, якщо ваш файл набагато більший, ніж ваш баран.

І, звичайно, трохи змастивши ліктьовий жир, ми можемо збрити 0,7 секунди:

echo 'int cache[256],x,buf[4096],*bp,*ep;char letters[]="tacgn-"; int main(){while((ep=buf+(read(0,buf,sizeof buf)/sizeof(int)))>buf)for(bp=buf;bp<ep;bp++){cache[(*bp)&0xff]++;cache[(*bp>>8)&0xff]++;cache[(*bp>>16)&0xff]++;cache[(*bp>>24)&0xff]++;}for(x=0;x<sizeof letters-1;x++)printf("%c: %d\n",letters[x],cache[letters[x]]);}' | gcc -O2 -xc -; ./a.out < file; rm a.out;

Мережі трохи більше 1,1 Гб / с закінчуються:

real    0m0.943s
user    0m0.798s
sys     0m0.134s

Для порівняння, я перевірив деякі інші рішення на цій сторінці, які, здавалося, мали якусь обіцянку швидкості.

sed/ awkРозчин зробив героїчні зусилля, але помер через 30 секунд. З таким простим регулярним виразом я думаю, що це буде помилка в sed (версія GNU sed 4.2.1):

$ time sed 's/./&\n/g' bigdna | awk '!/^$/{a[$0]++}END{for (i in a)print i,a[i];}' 
sed: couldn't re-allocate memory

real    0m31.326s
user    0m21.696s
sys     0m2.111s

Метод perl здався також багатообіцяючим, але я відмовився після його запуску протягом 7 хвилин

time perl -e 'while (<>) {$c{$&}++ while /./g} print "$c{$_} $_\n" for keys %c' < bigdna 
^C

real    7m44.161s
user    4m53.941s
sys     2m35.593s

1
+1 Для розумного рішення, коли багато даних, а не лише кілька байтів. Однак файли знаходяться в кеш-диску, чи не так?
Даніель Бек

2
Акуратним є те, що він має складність O (N) в обробці та O (1) в пам'яті. Труби зазвичай мають O (N log N) в обробці (або навіть O (N ^ 2)) і O (N) в пам'яті.
Мартін Удінг

73
Ви дещо розтягуєте визначення "командного рядка".
Герріт

11
Епічне згинання вимог запитання - я схвалюю; с. superuser.com/a/486037/10165 <- хто - то побігли тести, і це є найшвидшим варіантом.
Подорожник Geek

2
+1 Я ціную мене в хорошому використанні C в потрібних місцях.
Джефф Ферланд

119

grep -o foo.text -e A -e T -e C -e G -e N -e -|sort|uniq -c

Зробить трюк як один лайнер. Хоча потрібно трохи пояснень.

grep -o foo.text -e A -e T -e C -e G -e N -e -обробляє файл foo.text для літер a і g та символу -для кожного символу, який потрібно шукати. Він також друкує на ньому один символ рядка.

sortсортує його по порядку. Це встановлює основу для наступного інструменту

uniq -cпідраховує повторювані повторювані події будь-якого рядка. У цьому випадку, оскільки у нас є відсортований список символів, ми отримуємо акуратний підрахунок того, коли персонажі, які ми видобули на першому кроці

Якщо рядок foo.txt містив рядок, GATTACA-це я отримав із цього набору команд

[geek@atremis ~]$ grep -o foo.text -e A -e T -e C -e G -e N -e -|sort|uniq -c
      1 -
      3 A
      1 C
      1 G
      2 T

8
Кривава манія Unix! : D
Пітто

27
якщо у ваших файлах є лише символи CTAG, сам regexp стає безглуздим, правда? греп -о. | сортувати | uniq -c працював би однаково добре, afaik.
sylvainulg

7
+1 Я використовую grep протягом 25 років і не знав про це -o.
LarsH

9
@JourneymanGeek: Проблема в цьому полягає в тому, що він генерує багато даних, які потім пересилаються на сортування. Дешевше було б дозволити програмі розбирати кожного символу. Див. Відповідь Дейва на відповідь O (1), а не на O (N).
Мартін Удінг

2
@Pitto Native Windows версії Coreutils широко доступні - просто запитайте Google або щось подібне
OrangeDog

46

Спробуйте це, натхнене відповіддю @ Journeyman.

grep -o -E 'A|T|C|G|N|-' foo.txt | sort | uniq -c

Ключ - це знати про -o варіант для grep . Це розбиває збіг вгору, так що кожен рядок виводу відповідає одному екземпляру шаблону, а не цілому рядку для будь-якого рядка, який відповідає. Враховуючи ці знання, все, що нам потрібно, - це шаблон, який потрібно використовувати, і спосіб підрахунку рядків. Використовуючи регулярний вираз, ми можемо створити диз'юнктивний візерунок, який буде відповідати будь-якому з символів, які ви згадуєте:

A|T|C|G|N|-

Це означає "відповідати A або T або C або G або N або -". Посібник описує різні синтаксиси регулярних виразів, які ви можете використовувати .

Тепер у нас є вихід, який виглядає приблизно так:

$ grep -o -E 'A|T|C|G|N|-' foo.txt 
A
T
C
G
N
-
-
A
A
N
N
N

Останній наш крок - об'єднати та підрахувати всі подібні рядки, які можна просто виконати за допомогою sort | uniq -c, як у відповіді @ Journeyman. Цей вид дає нам такий вихід:

$ grep -o -E 'A|T|C|G|N|-' foo.txt | sort
-
-
A
A
A
C
G
N
N
N
N
T

Що, провівши його uniq -c, нарешті нагадує те, що ми хочемо:

$ grep -o -E 'A|T|C|G|N|-' foo.txt | sort | uniq -c
      2 -
      3 A
      1 C
      1 G
      4 N
      1 T

Додаток: Якщо ви хочете підключити кількість символів A, C, G, N, T і - у файлі, ви можете передавати висновок grep через, wc -lа не через sort | uniq -c. Існує багато різних речей, на які можна порахувати лише незначні зміни цього підходу.


Мені дійсно потрібно заглибитися в кролики, які є стрижнями і виразками. Це дещо елегантніше, ніж моє; p
Подорожник Geek

2
@JourneymanGeek: Вивчення регулярного виразів варте труднощів, оскільки воно корисне для багатьох речей. Просто зрозумійте, що це обмеження, і не зловживайте повноваженнями, намагаючись робити речі, що не входять до сфери регексів, як, наприклад, намагаючись проаналізувати XHTML .
crazy2be

20
grep -o '[ATCGN-] може бути трохи читабельнішим тут.
sylvainulg

14

Один лайнер, що рахує всі літери за допомогою Python:

$ python -c "import collections, pprint; pprint.pprint(dict(collections.Counter(open('FILENAME_HERE', 'r').read())))"

... випускаючи дружній вихід YAML таким чином:

{'\n': 202,
 ' ': 2153,
 '!': 4,
 '"': 62,
 '#': 12,
 '%': 9,
 "'": 10,
 '(': 84,
 ')': 84,
 '*': 1,
 ',': 39,
 '-': 5,
 '.': 121,
 '/': 12,
 '0': 5,
 '1': 7,
 '2': 1,
 '3': 1,
 ':': 65,
 ';': 3,
 '<': 1,
 '=': 41,
 '>': 12,
 '@': 6,
 'A': 3,
 'B': 2,
 'C': 1,
 'D': 3,
 'E': 25}

Цікаво побачити, як більшість разів Python може легко перемогти навіть баш за чіткістю коду.



10

Використовуючи UNIX протягом декількох років, ви отримуєте дуже досвідчене поєднання кількох невеликих операцій для виконання різних завдань з фільтрації та підрахунку. У кожного свій стиль - деяким подобається, awkа sedкомусь подобається cutі tr. Ось як я це зробив:

Для обробки конкретного імені файлу:

 od -a FILENAME_HERE | cut -b 9- | tr " " \\n | egrep -v "^$" | sort | uniq -c

або як фільтр:

 od -a | cut -b 9- | tr " " \\n | egrep -v "^$" | sort | uniq -c

Це працює так:

  1. od -a розділяє файл на символи ASCII.
  2. cut -b 9-виключає префікс odставить.
  3. tr " " \\n перетворює пробіли між символами в нові рядки, щоб був один символ на рядок.
  4. egrep -v "^$" позбавляється від усіх зайвих порожніх рядків, які це створює.
  5. sort збирає екземпляри кожного символу разом.
  6. uniq -c підраховує кількість повторів кожного рядка.

Я годував це "Привіт, світ!" після чого вийшов новий рядок і отримав таке:

  1 ,
  1 !
  1 d
  1 e
  1 H
  3 l
  1 nl
  2 o
  1 r
  1 sp
  1 w

9

sedЧастина базуючись на відповідь @ Гуру , ось ще один підхід з використанням uniq, аналогічно рішенню Девіда Шварца.

$ cat foo
aix
linux
bsd
foo
$ sed 's/\(.\)/\1\n/g' foo | sort | uniq -c
4 
1 a
1 b
1 d
1 f
2 i
1 l
1 n
2 o
1 s
1 u
2 x

1
Використовуйте [[:alpha:]]замість .в sedтільки герой матчу і не новий рядок.
Клавдій

1
[[:alpha:]]не вдасться, якщо ви також спробуєте співставити подібні речі -, про які згадувалось у питанні
Ізката

Правильно. Це може бути краще , щоб додати другий вираз СЕД на перший фільтрувати все інше , а потім явно збігаються з необхідними символами: sed -e 's/[^ATCGN-]//g' -e 's/\([ATCGN-]\)/\1\n/g' foo | sort | uniq -c. Однак я не знаю, як позбутися там нових рядків: \
Клавдій

7

Можна комбінувати grepі wcробити це:

grep -o 'character' file.txt | wc -w

grepздійснює пошук у заданих текстах (файлах) за вказаним текстом, і -oпараметр пропонує йому друкувати лише фактичні збіги (тобто символи, яких ви шукали), а не за замовчуванням, який повинен друкувати кожен рядок, у якому знаходився текст пошуку. знайдено на.

wcдрукує кількість байтів, слів і рядків для кожного файлу, або в цьому випадку, вихід grepкоманди. -wОпція каже його підрахунок кількості слів, причому кожне слово є входження пошукового характеру. Звичайно, -lпараметр (який рахує рядки) також буде працювати, оскільки grepдрукує кожне виникнення вашого пошукового персонажа на окремому рядку.

Щоб зробити це для декількох символів одночасно, покладіть символів у масив та переведіть на нього цикл:

chars=(A T C G N -)
for c in "${chars[@]}"; do echo -n $c ' ' && grep -o $c file.txt | wc -w; done

Приклад: для файлу, що містить рядок TGC-GTCCNATGCGNNTCACANN-, результатом буде:

A  3
T  4
C  6
G  4
N  5
-  2

Для отримання додаткової інформації див man grepі man wc.


Мінус цього підходу, як зазначає користувач Journeyman Geek нижче в коментарі, полягає в тому, що grepйого потрібно виконувати один раз для кожного персонажа. Залежно від того, наскільки великі ваші файли, це може спричинити помітний показник продуктивності. З іншого боку, коли це зроблено, трохи простіше швидко побачити, які символи шукають, і додати / видалити їх, оскільки вони знаходяться в окремому рядку від решти коду.


3
їм потрібно було б повторити це, якщо вони хочуть ... Я б додав. Я можу поклястись, що є більш елегантне рішення, але йому потрібно більше ткнути; p
Journeyman Geek

@JourneymanGeek Добрий момент. Один із підходів, який спадає на думку, - це розміщення персонажів у масиві та перегляд його. Я оновив свою публікацію.
Індрек

занадто складний ІМО. Просто використовуйте grep -ea -et тощо. Якщо ви помістите його в масив і пропустіть через нього, чи не доведеться вам один раз запускати цикл grep за кожним символом?
Подорожник Geek

@JourneymanGeek Ви, мабуть, праві. uniq -cтакож здається кращим способом отримання добре відформатованого виводу. Я не * nix guru, вищезгадане - це те, що мені вдалося зібрати з моїх обмежених знань та деяких сторінок чоловіка :)
Індрек

Так само і я; p, і в одному з моїх останніх термінів виконання завдань було сортування приблизно 5000 записів адресної книги, а uniq полегшив це багато.
Подорожник Geek

7

Використовуючи рядки послідовностей з 22hgp10a.txt різницю часу між grep та awk у моїй системі, зробіть, використовуючи awk шлях, щоб піти ...

[Редагувати]: Побачивши скомпільоване рішення Дейва, також забудьте awk, оскільки його завершено за ~ 0,1 секунди у цьому файлі для повного підрахунку регістру.

# A nice large sample file.
wget http://gutenberg.readingroo.ms/etext02/22hgp10a.txt

# Omit the regular text up to the start `>chr22` indicator.
sed -ie '1,/^>chr22/d' 22hgp10a.txt

sudo test # Just get sudo setup to not ask for password...

# ghostdog74 answered a question <linked below> about character frequency which
# gave me all case sensitive [ACGNTacgnt] counts in ~10 seconds.
sudo chrt -f 99 /usr/bin/time -f "%E elapsed, %c context switches" \
awk -vFS="" '{for(i=1;i<=NF;i++)w[$i]++}END{for(i in w) print i,w[i]}' 22hgp10a.txt

# The grep version given by Journeyman Geek took a whopping 3:41.47 minutes
# and yielded the case sensitive [ACGNT] counts.
sudo chrt -f 99 /usr/bin/time -f "%E elapsed, %c context switches" \
grep -o foo.text -e A -e T -e C -e G -e N -e -|sort|uniq -c

Справа нечутливої ​​версії привида завершена за ~ 14 секунд.

Сед пояснюється у прийнятій відповіді на це питання .
Бенчмаркінг - як у прийнятій відповіді на це питання .
На це питання була прийнята відповідь ghostdog74 .


1
Ви можете s/cache[letters[x]]/cache[letters[x]]+cache[toupper(letters[x])]шахти, щоб зробити випадок нечутливим, не впливаючи на його швидкість.
Дейв

6

Я думаю, що будь-яка гідна реалізація уникає подібного роду. Але оскільки це також погана ідея прочитати все 4 рази, я думаю, що можна якось генерувати потік, який проходить через 4 фільтри, по одному для кожного символу, який відфільтрований і де довжина потоку також якось обчислюється.

time cat /dev/random | tr -d -C 'AGCTN\-' | head -c16M >dna.txt
real    0m5.797s
user    0m6.816s
sys     0m1.371s

$ time tr -d -C 'AGCTN\-' <dna.txt | tee >(wc -c >tmp0.txt) | tr -d 'A' | 
tee >(wc -c >tmp1.txt) | tr -d 'G' | tee >(wc -c >tmp2.txt) | tr -d 'C' | 
tee >(wc -c >tmp3.txt) | tr -d 'T' | tee >(wc -c >tmp4.txt) | tr -d 'N' | 
tee >(wc -c >tmp5.txt) | tr -d '\-' | wc -c >tmp6.txt && cat tmp[0-6].txt

real    0m0.742s
user    0m0.883s
sys     0m0.866s

16777216
13983005
11184107
8387205
5591177
2795114
0

Кумулятивні суми знаходяться тоді в tmp [0-6] .txt .., тому робота ще триває

У цьому підході всього 13 труб, що перетворює на менше 1 Мб пам'яті.
Звичайно, моє улюблене рішення:

time cat >f.c && gcc -O6 f.c && ./a.out
# then type your favourite c-program
real    0m42.130s

Це дуже приємне використання tr.
adavid

4

Я uniqні про що grep -o, ні про це не знав , але оскільки мої коментарі до @JourneymanGeek та @ crazy2be мали таку підтримку, можливо, я повинен перетворити це на власного прихильника:

Якщо ви знаєте, що у вашому файлі є лише "хороші" символи (ті, які ви хочете порахувати), можете продовжити

grep . -o YourFile | sort | uniq -c

Якщо потрібно враховувати лише деякі символи, а інші не (тобто роздільники)

grep '[ACTGN-]' YourFile | sort | uniq -c

Перший використовує підстановку регулярних виразів ., які відповідають будь-якому одному символу. У другому використовується "набір прийнятих символів", без конкретного порядку, крім того, який -повинен бути останнім ( A-Cтрактується як "будь-який символ між Aі C). У цьому випадку необхідні котирування, щоб оболонка не намагалася розширити цю програму, щоб перевірити односимвольні файли, якщо такі є (і створити помилку "без збігу", якщо такої немає).

Зауважте, що "сортувати" також має -uпрапор нікеля, так що він повідомляє про речі лише один раз, але жоден супровідний прапор для підрахунку дублікатів, тому uniqдійсно є обов'язковим.


-не повинно бути останньою, якщо уникнути її з нахилом: '[A\-CTGN]'має працювати чудово.
Індрек

2

Дурний:

tr -cd ATCGN- | iconv -f ascii -t ucs2 | tr '\0' '\n' | sort | uniq -c
  • trвидалити ( -d) всі символи, але ( -c) ATCGN-
  • iconv перетворити на ucs2 (UTF16 обмежено 2 байтами), щоб додати 0 байт після кожного байта,
  • інший trдля перекладу цих символів NUL в NL. Тепер кожен персонаж знаходиться по своїй лінії
  • sort | uniq -cрахувати кожен рядок uniq

Це альтернатива нестандартному -oваріанту грепу (GNU) .


Не могли б ви дати коротке пояснення команд та логіки тут?
Ендрю Ламберт

2
time $( { tr -cd ACGTD- < dna.txt | dd | tr -d A | dd | tr -d C | dd | tr -d G |
dd | tr -d T | dd | tr -d D | dd | tr -d - | dd >/dev/null; } 2>tmp ) &&
grep byte < tmp | sort -r -g | awk '{ if ((s-$0)>=0) { print s-$0} s=$0 }'

Формат виходу не найкращий ...

real    0m0.176s
user    0m0.200s
sys     0m0.160s
2069046
2070218
2061086
2057418
2070062
2052266

Теорія роботи:

  • $ ({command | command} 2> tmp) перенаправляє stderr потоку у тимчасовий файл.
  • dd виводить stdin у stdout та виводить кількість байтів, переданих stderr
  • tr -d фільтрує по одному символу за раз
  • grep і сортування фільтрує вихід DD у порядку зменшення
  • awk обчислює різницю
  • сортування використовується лише на етапі після обробки, щоб вирішити невизначеність порядку виходу примірників dd

Здається, швидкість становить 60 Мбіт / с


Поліпшення: позбутися від tmp? використовувати "вставити", щоб надрукувати відповідний лист?
Акі Суйконен

1

Зразок файлу:

$ cat file
aix
unix
linux

Команда:

$ sed 's/./&\n/g' file | awk '!/^$/{a[$0]++}END{for (i in a)print i,a[i];}'
u 2
i 3
x 3
l 1
n 2
a 1

-1 для недостатньої ясності, а також для розміщення однолінійки без пояснень. АФАІК, це могла бути бомба з вилами
PPC

1

Поєднання кількох інших

chars='abcdefghijklmnopqrstuvwxyz-'
grep -o -i "[$chars]" foo|sort | uniq -c

Додати, | sort -nrщоб побачити результати в порядку частоти.


1

Коротка відповідь:

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

А, але заплутані деталі:

Це всі персонажі Ассії. Один байт per. Файли, звичайно, мають додаткові метадані для різних матеріалів, що використовуються ОС та додатком, який їх створив. У більшості випадків я б очікував, що вони займають однаковий простір незалежно від метаданих, але я б намагався підтримувати однакові обставини, коли ви вперше перевіряєте підхід, а потім перевіряєте, чи є у вас постійне зміщення, перш ніж не турбуватися про це. Інша суть полягає в тому, що розриви рядків зазвичай включають два символи пробілу ascii, а будь-які вкладки чи пробіли будуть по одній. Якщо ви можете бути впевнені, що вони будуть присутні, і немає жодного способу дізнатися, скільки заздалегідь, я б перестав читати зараз.

Це може здатися великим обмеженням, але якщо ви зможете їх легко встановити, це вважає мене найпростішим / найкращим підходом, якщо у вас є тонна кількість таких (що здається, ймовірно, якщо це ДНК). Перевірка тони файлів на довжину та віднімання константи було би швидше, ніж запуск grep (або подібного) на кожному.

Якщо:

  • Це прості неперервані рядки в чистих текстових файлах
  • Вони знаходяться в однакових типах файлів, створених тим же ванільним неформатуючим текстовим редактором, як Scite (вставлення нормально, якщо ви перевіряєте пробіли / повернення) або якоїсь базової програми, яку хтось написав

І дві речі, які можуть не мати значення, але я спробував би спершу

  • Імена файлів мають однакову довжину
  • Файли знаходяться в одному каталозі

Спробуйте знайти зсув, виконавши наступне:

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


1

Хескелл спосіб:

import Data.Ord
import Data.List
import Control.Arrow

main :: IO ()
main = interact $
  show . sortBy (comparing fst) . map (length &&& head) . group . sort

він працює так:

112123123412345
=> sort
111112222333445
=> group
11111 2222 333 44 5
=> map (length &&& head)
(5 '1') (4 '2') (3 '3') (2 '4') (1,'5')
=> sortBy (comparing fst)
(1 '5') (2 '4') (3 '3') (4 '2') (5 '1')
=> one can add some pretty-printing here
...

складання та використання:

$ ghc -O2 q.hs
[1 of 1] Compiling Main             ( q.hs, q.o )
Linking q ...
$ echo 112123123412345 | ./q
[(1,'\n'),(1,'5'),(2,'4'),(3,'3'),(4,'2'),(5,'1')]%       
$ cat path/to/file | ./q
...

не годиться для величезних файлів, можливо.


1

Швидкий хакер Perl:

perl -nle 'while(/[ATCGN]/g){$a{$&}+=1};END{for(keys(%a)){print "$_:$a{$_}"}}'
  • -n: Повторюйте вхідні рядки, але не друкуйте для них нічого
  • -l: Смугайте або додайте розриви рядків автоматично
  • while: повторіть усі випадки запитуваних символів у поточному рядку
  • END: Наприкінці надрукуйте результати
  • %a: Хеш, де зберігаються значення

Символи, які взагалі не зустрічаються, не включаються до результату.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.