У меня есть файл с идентификатором и значением:
ABC123 111111
ABC123 111111
ABCDEF 333333
ABCDEF 111111
CCCCCC 333333
ABC123 222222
DEF123 444444
DEF123 444444
Оба столбца содержат повторяющиеся значения, но мне нужно посчитать строки с одинаковым идентификатором (первый столбец) и уникальным значением (второй столбец). Это приведет к выводу из приведенного выше ввода:
ABCDEF 2
ABC123 2
DEF123 1
CCCCCC 1
...где первый столбец — это идентификатор, а второй столбец — количество уникальных значений во втором столбце. Другими словами, мне нужно узнать, сколько уникальных значений существует для данного идентификатора.
Ближе всего я подошел к этому, но все, что он делает, это подсчитывает уникальные значения первого столбца:
cut -d " " -f1 "file.txt" | uniq -cd | sort -nr | head
Как бы мне сделать что-то подобное в Bash?
сколько уникальных значений существует для данного идентификатора, также будет напечатано 2 для «ABC123». Вы действительно хотите считать идентификаторы только там, где дубликаты вообще не встречаются?
@AndreWildberg Хорошая уловка, вы правы, что ABC123 из примеров также должен выдавать 2.
Поскольку мы очищаем тестовые данные, DEF123
не имеет двух разных значений, оно встречается с 444444
дважды.
Почему DEF123 2
сейчас не выводится?
Благодаря вам обоим оказалось, что писать хорошие исчерпывающие примеры сложно, или я просто очень устал.
Может ли серийный противник моих вопросов объяснить, почему?
Это awk
должно сработать для вас:
awk '{
++c1[$1] # frequency of 1st column
uq[$0] # counts of full record
}
END {
for (i in uq) { # store frequency of uniques in fq
sub(/ .*/, "", i)
++fq[i]
}
for (i in fq) # print output from fq
if (c1[i] > 1)
print i, fq[i]
}' file
ABCDEF 2
DEF123 1
ABC123 2
Это достаточно близко?
$ sort -u file.txt | cut -d' ' -f1 | uniq -c
2 ABC123
2 ABCDEF
1 CCCCCC
1 DEF123
Вы можете дополнительно отфильтровать его с помощью | grep -vw '1'
, чтобы имитировать семантику HAVING COUNT(DISTINCT value) > 1
и исключить последние две строки из вывода в этом примере (при условии, что 1
не является допустимым значением идентификатора!).
И вы, конечно, можете изменить порядок столбцов несколькими способами. Например.
$ sort -u file.txt | # sort and eliminate multiple occurrences of the same '<identifier> <value>' pair
cut -d' ' -f1 | # keep only the identifier
uniq -c | # collapse and count occurrences of the same identifier
grep -vw '1' | # eliminate rows containing the word '1', assuming this can only be a count value, never an identifier!
awk '{print $2 " " $1}' # reverse column order to show '<identifier> <count>'
ABC123 2
ABCDEF 2
Только что протестировал и отлично работает, спасибо, особенно полезно иметь возможность связывать grep -v
, когда я хочу просмотреть только строки с более чем 1 или даже 2 дубликатами, хотя стоит отметить, что мне также нужен sort -rn
для эмуляции сортировки.
С GNU awk (для многомерных массивов):
awk '
!seen[$1][$2]++ {++uniqs[$1]}
END {for(id in uniqs) print id, uniqs[id]}
' file.txt
DEF123 1
ABC123 2
ABCDEF 2
CCCCCC 1
С помощью стандартного awk вы можете использовать seen[$1,$2]
для эмуляции двумерного массива.
Ты прав; теперь, когда вы об этом упомянули, можно использовать даже seen[$0]
Предположения:
Еще один awk
подход:
awk '
{ lines[$0] } # capture unique lines
END { for (line in lines) { # loop through list of unique lines
split(line,a) # split line on white space
counts[a[1]]++ # count number of times we see the first field (aka "id")
}
for (id in counts) # loop through list of id
print id, counts[id] # print id and count
}
' file.txt
Это генерирует:
ABC123 2
DEF123 1
ABCDEF 2
CCCCCC 1
Если выходные данные необходимо упорядочить, передайте результаты соответствующей команде sort
, например:
$ awk '<see script from above>' file.txt | sort -k2,2nr -k1,1r
ABCDEF 2
ABC123 2
CCCCCC 1
DEF123 1
Вот Ruby для этого:
ruby -lane 'BEGIN{ cnt=Hash.new{|h,k| h[k]=[]} }
cnt[$F[0]]<<$F[1]
END{
cnt.select{|k,v| v.length>1 }.
each{|k,v| puts "#{k} #{v.uniq.length}"}
}
' file.txt
Распечатки:
ABC123 2
ABCDEF 2
DEF123 1
Неясно, должен ли CCCCCC 1
присутствовать в выводе. Если да, то фильтровать не нужно:
ruby -lane 'BEGIN{ cnt=Hash.new{|h,k| h[k]=[]} }
cnt[$F[0]]<<$F[1]
END{ cnt.each{|k,v| puts "#{k} #{v.uniq.length}"} }
' file.txt
Распечатки:
ABC123 2
ABCDEF 2
CCCCCC 1
DEF123 1
Вы также можете сделать этот канал POSIX:
uniq file.txt | awk '{cnt[$1]++} END{for (e in cnt) print e, cnt[e]}'
Распечатки:
CCCCCC 1
ABCDEF 2
DEF123 1
ABC123 2
Начнем с сортировки:
sort PHA-DC.txt | cut -d " " -f1 | uniq …