2016-12-28 9 views
0

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

100A 2000 
100B 150 
100C 800 
100A 1000 
100B 100 
100C 300 

Я хочу, чтобы вычесть значения в столбце 2 для каждого Uniq значения в колонке 1 поэтому из пут должен выглядеть

100A 1000 
100B 50 
100C 500 

Я попытался

awk '{if(!a[$1])a[$1]=$2; else a[$1]=$2-a[$1]}END{ for(i in a)print i" " a[i]}' file 

но из положить является:

100A 0 
100B 0 
100C 0 

пожалуйста посоветуйте

+0

Что произойдет, если значение в первом столбце повторяется более одного раза? – ghoti

+0

В этом случае я хочу взять первое и последнее вхождения – Vicky

+0

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

ответ

2

Так много (небольшие) вариации на та же тема.

awk ' 
    !($1 in a) {a[$1]=$2; next} 
    {a[$1]-=$2} 
    END {for (i in a) printf "%s %d\n",i,a[i]} 
' input.txt 

Уложите его как однострочный вкладыш, если хотите.

Помните, что структура awk состоит из нескольких пар condition { statement }, поэтому вы можете иногда выражать свои требования более элегантно, чем использовать if..else. (Не говоря, что это имеет место здесь - это достаточно простой awk-скрипт, который, вероятно, не имеет значения, если вы не пурист.:])

Кроме того, остерегайтесь тестирования значений так, вы сделали в состоянии в вашем if в вопросе. Обратите внимание, что в a[$1]оба параметра проверяют, является ли значение в индексе этого массива отличным от нуля. и заставляет индекс существовать с нулевым значением, если он ранее не существовал. Если вы хотите проверить наличие индекса, используйте $1 in a.


Обновление на основе комментариев на ваш вопрос ...

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

awk ' 
    !($1 in a){a[$1]=$2;next} 
    {b[$1]=$2} 
    END {for(i in b)if(i in a)print i,a[i]-b[i]} 
' input.txt 

Обратите внимание, что, как упоминает Эд, это производит выход в случайном порядке. Если вы хотите, чтобы результат был заказан, вам понадобится дополнительный массив для отслеживания заказа. Например, это будет использовать порядок, предметы первой видели:

awk ' 
    !($1 in a) { 
    a[$1]=$2; 
    o[++n]=$1; 
    next 
    } 
    { 
    b[$1]=$2 
    } 
    END { 
    for (n=1;n<=length(o);n++) 
     print o[n],a[o[n]]-b[o[n]] 
    } 
' i 

Обратите внимание, что функция length() которая используется для определения количества элементов в массиве не является универсальным среди диалектов AWK, но он работает в обоих gawk и one-true-awk (используется во FreeBSD и др.).

+0

Я согласен, но я присвоил значение a [$ 1] = $ 2, когда индекс не существовал, а что с моим решением? – Vicky

+0

Ваш тест был 'if (! A [$ 1])', который не проверяет несуществование, он заставляет индекс существовать (если он этого еще не существует) и проверяет значение нуля. Я не уверен, почему вы получите результаты, о которых вы говорили в своем вопросе, но у вас есть вычитание назад. Вы вычитаете второе значение из первого, поэтому, когда я запускаю свой пример кода, я вижу отрицательные числа (1000 - 2000, 100 - 150, 300 - 800). – ghoti

+0

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

1

Это AWK один лайнер делает работу:

awk '{if($1 in a)a[$1]=a[$1]-$2;else a[$1]=$2} 
     END{for(x in a) print x, a[x]}' file 
+0

Есть ли преимущество использования if/else, а не неявной структуры, которую awk предоставляет с помощью 'condition {statement}' constructs? – ghoti

+0

@ghoti Я чувствую, что если/else ясен – Kent

0

Вы можете использовать этот awk:

awk 'a[$1]{a[$1]=a[$1]-$2; next} {a[$1]=$2} END{for(v in a){print v, a[v]}}' file 
1

В awk.С помощью условного оператора для размещения значения/вычитание, чтобы держать его крепко:

$ awk '{ a[$1]+=($1 in a?-$2:$2) } END{ for(i in a)print i, a[i] }' file 
100A 1000 
100B 50 
100C 500 

Разъяснение:

{ 
    a[$1]+=($1 in a?-$2:$2) # if $1 in a already, subtract from it 
           # otherwise add value to it 
} 
END { 
    for(i in a)    # go thru all a 
     print i, a[i]   # and print keys and values 
} 
1

Учитывая входной сэмпл при условии, все, что вам нужно:

$ awk '$1 in a{print $1, a[$1]-$2} {a[$1]=$2}' file 
100A 1000 
100B 50 
100C 500 

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

+0

Спасибо Ed, который работает. Является ли это способом реализации if -else в блоке условий awk, где {print $ 1, a [$ 1] - $ 2} выполняется, когда $ 1 в оценке равно true и {a [$ 1] = $ 2} действует как еще и выполняется, когда $ 1 оценивается как false? – Vicky

+0

Нет, для того, чтобы быть другим, вы должны вставить '; next' перед первым'} '. Сейчас назначение выполняется для каждой строки, это просто функционально не имеет значения, что это происходит. –

Смежные вопросы