Как найти неповторяющуюся букву в заданной строке

Используйтеawk:

awk -v na="./." '
BEGIN{OFS=FS}
NR==FNR && NR>1 {
  for(i=1;i<=NF;i++){if($i!=na){s[i]=1}}
}
NR!=FNR {
  for(l in s){true} 
  for(i in s){if (i!=l){printf "%s"OFS,$i} else {printf "%s\n",$i}}
}
' file file

Возможно, вам придется изменить BEGIN{OFS=FS}на BEGIN{OFS=FS="\t"}, если ваш файл разделен табуляцией.

Пояснение:

  • Пройтись по файлу дважды(awk... file file)
  • Первый раз(NR==FNRи NR>1для исключения заголовка )вы проверяете, что в ваших столбцах(i)есть хотя бы одно значение, отличное от na="./.", сохраните номер столбца в переменной s.
  • Второй раз(NR!=FNR)для каждого столбца, сохраненного в s, распечатать значение столбца. (Первый цикл позволяет узнать последний столбец, который будет напечатан (, сохраненный в переменной l, поэтому вы можете выбрать между печатью OFSили \n.)

Выход:

Sample1 Sample2 Sample3 Sample5
./../../../.
./../../. A/G
./../../../.
A/A A/A A/G./.

Если ваш файл был разделен табуляцией -, вывод будет немного лучше, если нет, вы можете сделать его лучше, добавив |column -t. Тогда это будет выглядеть так:

Sample1  Sample2  Sample3  Sample5
./.     ./.     ./.     ./.
./.     ./.     ./.      A/G
./.     ./.     ./.     ./.
A/A      A/A      A/G     ./.
5
09.11.2020, 06:46
9 ответов

Потребовалось так много времени, чтобы реализовать этот подход, но описанный ниже подход сработал для меня, как и ожидалось, и я, наконец, закончил !!!

Проверено на приведенной ниже строке

Строка 1:aaabefhhhhhthkkd -> Вывод -> beftd

Строка 2:AAAbefhhhhhThkkD -> Вывод -> befTD

Строка 3:AAAbefhMThkkD -> Вывод -> befMTD

#!/bin/bash

# String passed as an input 
str='AAAbefhMThkkD'

# Taking character count of provided string 
count=$(echo "$str" | tr -cd 'a-z|A-Z' | wc -c)

# Dynamic array created 
dynamic_array=()

# Looping through the for loop 
for (( i=1 ; i<=$((count)) ; i++ ))
{
    c=$(echo "$str" |  cut -c "$i")
    character_count=$(echo "$str" | tr -cd "$c" | wc -c) 
    echo "Character : $c  : $character_count" 

   if [ "$character_count" -eq 1 ]
   then  
        dynamic_array+=("$c")   
   fi           
}

str_array_value="${dynamic_array[*]}" ; echo "Output : ${str_array_value// /}" 

# Input :  aaabefhhhhhthkkd : Output : beftd 
# Input :  AAAbefhhhhhThkkD : Output : befTD
# Input :  AAAbefhMThkkD    : Output : befMTD  

Код исправлен наhttps://www.shellcheck.net

-2
18.03.2021, 22:51
awk '
{
  n=split($0, a, "")
  for(i=1; i<=n; i++){
    if(gsub(a[i], "") == 1){ printf("%s", a[i]) }
  }
  print ""
}'
  • n=split($0, a, ""):a[1]становится 1-м символом строки, a[2]— 2-м и т. д. n— общее количество символов.
  • for(i=1; i<=n; i++):Пройдемся по всему массиву a.
  • if(gsub(a[i], "") == 1):Удалить все символы a[i]из строки. Если в строке был удален только один символ,
    • printf("%s", a[i])напечатать этот символ.
  • print ""печатает символ новой строки после обработки всей строки. Это необязательно, если у вас есть одна строка ввода.

Пример с одним уплотненным -вкладышем:

$ awk '{n=split($0,a,"");for(i=1;i<=n;i++)if(gsub(a[i],"")==1)printf("%s",a[i])}' <<< AAAbefhMThkkD
befMTD

Примечание. :Разделение нулевой строки не определено в POSIX. Однакоgawk(GNU Awk ), mawkиoriginal-awkвсе реализуют операцию по желанию.

3
18.03.2021, 22:51

uniq работает только с соседними дубликатами -, поэтому, если вы хотите использовать это, вам нужно сначала отсортировать ввод, например:

fold -w1 | sort | uniq -u | paste -sd ''
  • fold -w1делает то же самое, что и ваш sed 's/./&\n/g', но без дополнительной ложной новой строки
  • sortчтобы сделать повторяющиеся символы смежными
  • uniq -u-uважно печатать только синглтоны
  • paste -sd ''объединяет результат обратно в одну строку

Из-за сортировки вы не сможете получить желаемый порядок вывода во всех случаях, например.

$ echo 'AAAbefhMThkkD' | fold -w1 | sort | uniq -u | paste -sd ''
  DMTbef

Если вы не хотите внедрять собственное решение, вы всегда можете использовать PerlMoreUtils:

$ echo 'AAAbefhMThkkD' |
    perl -MList::MoreUtils=singleton -ne 'print singleton split //'
befMTD
5
18.03.2021, 22:51

С помощью sedвы можете сделать что-то вроде:

sed '
  :1
  /\(.*\(.\).*\)\2/ { # while there is a duplicated char
    s//\2\1/; # move it to the front
    :2
      # remove characters that are the same as the first in a loop:
      s/^\(\(.\).*\)\2/\1/
    t2
    s/^.//
    b1
  }'

С реализацией GNU sedвы можете сократить его до:

sed -E ':1;s/(.*(.).*)\2/\2\1/;T;:2;s/^((.).*)\2/\1/;t2;s/^.//;t1'

Если вы хотите выполнить проверку на наличие дубликатов без учета регистра (для того, чтобы áÁbBcδΔстало c, например ), вы можете добавить флаг iк первым 2 sкомандам в Код GNU sedвыше. Однако обратите внимание, что это не будет работать для таких вещей, как German ßvs SS.

И это по-прежнему не будет обрабатывать эквивалентность Unicode и работать на уровне символа (, а не кластера графемы ), поэтому, например, если у вас есть aéá, где эти буквы с акцентом выражаются в их в разложенной форме, не только U+00E9 éне будет считаться таким же, как U+0065 U+0301 é, но aéá, выраженное как U+0061 U+0065 U+0301 U+0061 U+0301, станетe(U+0065 ), единственный не -повторяющийся символ там, даже если эти 5 символов фактически образуют 3 различных кластера графем. Мое имя в его разложенной форме стало быSt́phan(с комбинированным острым ударением, приземляющимся на t, когда оба eудалены ).

Использование:

perl -Mopen=locale -lpe 's/\b{g}\Q$1\E\b{g}//gi while m/(\X)\X*\1/i'

здесь расширение ответа @sitaram(с использованием -Mopen=localeдля обработки ввода как символов вместо байтов, \Xвместо .для сопоставления кластера графемы вместо символа и \b{g}для графемы граница кластера )решит некоторые из этих проблем (не разбивая кластеры графем в середине, ßпротив SS), но не эквивалентность юникода:

$ echo $'groß KUSS. Ste\u0301phane, \ue9' |  perl -Mopen=locale -lpe 's/\b{g}\Q$1\E\b{g}//gi while m/(\X)\X*\1/i'
groKU.Stéphane,é

(ßзамечен как дубликат SS, eв e\u0301не связан с автономным e, но два варианта éне распознаны как одно и то же ).

Также обратите внимание, что ß/SSбудет преобразовано в /, поскольку ßобрабатывается первым, а SS/ßбудет преобразовано в , поскольку Sобрабатывается первым.

Это также превратит ßA/SASв /, поскольку удаление дубликатов Aоткроет SS, версию ßв верхнем регистре. Чтобы избежать этого, вы можете изменить его на:

perl -Mopen=locale -lpe 's/\b{g}\Q$1\E\b{g}/\n/gi while m/((?!\n)\X)\X*\1/i; s/\n//g'

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

3
18.03.2021, 22:51

С GNU awk, присваивающим FS пустую строку.

Из руководства GNU awk:

FS == ""

Each individual character in the record becomes a separate field. (This is a common extension; it is not specified by the POSIX standard.)

echo 'aaabefhhhhhthkkd' | awk -v FS= -v ORS='' '
{for (i=1; i<=NF;i++) if ( gsub($i,"&") == 1 ) print $i;print "\n"}'
beftd

1
18.03.2021, 22:51

Вариация на тему

echo 'aaabefhhhhhthkkd' | 
 awk '{while (length()>0) {t=substr($0,1,1); printf (gsub( t,"")==1)?t:""} print}'

beftd

Используйте $0, заменив первый символ на ""до тех пор, пока он не станет пустым, и напечатайте, когда произойдет только одна замена.

1
18.03.2021, 22:51

Без awk:

string="aaabefhhhhhthkkd" && discard=$(echo $string|fold -w1|sort|uniq -d|tr -d '\n') && echo $string|sed "s/[$discard]//g"

Пояснение:

string="aaabefhhhhhthkkd"

устанавливает вашу строку как переменную 'string'

&&

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

discard=$(echo $string|fold -w1|sort|uniq -d|tr -d '\n')

найдет все неуникальные символы в $string и сохранит их в переменной $discard. (развернуть на символы, отсортировать, определить не -уникальные символы, откатиться назад, сохранить в переменную)

echo $string|sed "s/[$discard]//g"

удалит символы, не являющиеся -уникальными, из строки

.
0
18.03.2021, 22:51

Вот очень простой Perl one -лайнер (с небольшой помощьюecho):

echo aaabefhhhhhthkkd | perl -ple 'my %z; for my $c (split q//){$z{$c}++} for my $k (keys %z){$_ =~ s/\Q$k\E//g if $z{$k} > 1}'

Пояснение:

  1. -p:зациклить заданную программу, каждая строка ввода находится в переменной по умолчанию $_и распечатать каждую строку ввода после обработки
  2. -l:обрабатывать новые строки, чтобы вам не приходилось
  3. -e:выполните следующее как perl-код
  4. my %z:инициализируйте хеш, чтобы сохранить количество символов в (для каждой строки)
  5. for my $c (split q//):разделить переменную по умолчанию, $_, на $c, перебирая каждый символ
  6. $z{$c}++:увеличить количество символов$c
  7. for my $k (keys %z):для каждой клавиши в %z, количество просмотренных символов
  8. $_ =~ s/$k//g if $z{$k} > 1:удалить все экземпляры символа $k, если его количество больше 1
0
18.03.2021, 22:51

Кажется, это работает нормально:

$ echo 'aaabefhhhhhthkkd' | perl -pe 's/$1//g while m/(.).*\1/'
beftd

Довольно легко понять, даже если вы не знаете perl; это просто регулярное выражение, как оно используется в sed и любых других подобных инструментах. Даже то, как работает while, такое же, как, скажем, bash/sh, так что это тоже должно быть достаточно ясно.

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

Кроме того, если вы хотите, чтобы сравнение было нечувствительным к регистру -, добавьте флаг iк m//иs///g:

$ echo 'aaabefhhHhhthkkd' | perl -pe 's/$1//gi while m/(.).*\1/i'
beftd
1
18.03.2021, 22:51

Теги

Похожие вопросы