2016-01-19 4 views
-1

Я смотрю вокруг, но я не могу сглазить все, что приближается к тому, что я ищу. Я думаю, что пример будет объяснить это лучше начать с: Вход:Как сохранить только содержимое, специфичное для столбцов?

------------------------------------| 
| List1 | List2 | List3 | 
|  1  |  2  |  3  | 
|  2  |  3  |  4  | 
|  3  |  4  |  5  | 
|  4  |  5  |  6  | 
|  5  |  6  |  7  | 
|  6  |  7  |  a  | 
|  7  |  8  |  b  | 
|  a  |  d  |  c  | 

Желаемая Выход:

------------------------------------| 
| List1 | List2 | List3 | 
|  1  |  8  |  b  | 
|   |  d  |  c  | 

Итак, как вы можете видеть, цель состоит в том, чтобы в каждом столбце только контента который не найден в другом месте таблицы. В идеале это должно работать с любым количеством столбцов.

Все, что угодно в awk, bash или даже в excel, будет в порядке. До сих пор я играл с awk, но безрезультатно.

Любая помощь будет оценена по достоинству.

Спасибо всем.

Редактировать, уточнить. Фактический ввод - это разные списки, которые я хочу сравнить. В идеале каждый из них находится в другом файле. Теперь я могу легко объединить их, чтобы они соответствовали вводу, приведенному в примере, так что код, приведенный здесь в ответах, применим.

+1

Это действительно ваш вклад или вы пытаетесь показать нам, что ваш реальный входной файл (например, CSV или аналогичный) будет выглядеть, если бы он был нарисован как таблица? –

+0

Да, это пример того, как он будет выглядеть, а не фактический ввод. – comaX

+0

Пожелайте, чтобы вы только что разместили свой реальный вход и желаемый результат. Я, и я уверен, другие, потратил много времени на разбор этого ввода и создание этого вывода, чтобы попытаться помочь вам. Теперь у вас есть куча ответов, которые работают с некоторыми форматами входных файлов, которых нет для создания некоторого формата вывода, которого вы действительно не хотите. Какая пустая трата времени ... –

ответ

1

Пожалуйста, попробуйте Python:

import re 

with open("./input_file", "rt") as file: 
    f = file.readlines() 
    cols = [re.findall(r"[\w']+", x) for x in f[3:]] 

col1 = set(x[0] for x in cols) 
col2 = set(x[1] for x in cols) 
col3 = set(x[2] for x in cols) 

print col1.difference(col2.union(col3)) 
print col2.difference(col1.union(col3)) 
print col3.difference(col1.union(col2)) 

Выход:

set(['1']) 
set(['8', 'd']) 
set(['c', 'b']) 

EDIT

Enhanced версию, чтобы соответствовать желаемого форматирования. Также должен работать с любым количеством столбцов/строк.

from __future__ import print_function 
import re 

with open("./input_file", "rt") as file: 
    f = file.readlines() 
    for x in f[:2]: 
     print(x,end='') 
    rows = [re.findall(r"(?<=\|)(.*?)(?:\|)", x) for x in f[2:]] 
    col_num, col_width = len(rows[0]), len(rows[0][0]) 
    cols = [ set(y[x] for y in rows) for x in range(len(rows[0]))] 

uniq_cols = [] 
for col in cols: 
    uniq_cols.append(list(col.difference(set().union(*[c for c in cols if c != col])))) 

for x in range(max(len(x) for x in uniq_cols)): 
    print('|', end='') 
    for col in uniq_cols: 
     try: 
      print(col[x], end='') 
      print('|', end='') 
     except IndexError: 
      print(' '*col_width, end='') 
      print('|', end='') 
    print('\n', end='') 

Выход:

------------------------------------| 
| List1 | List2 | List3 | 
|  1  |  8  |  c  | 
|   |  d  |  b  | 
0

awk на помощь!

$ awk 'function abc(x,y,z) 
     {for(k in x) if(!(k in y || k in z)) printf "%s", FS k; 
     print "" 
     } 

    NR==1{split($0,h); next} 
     {a[$1];b[$2];c[$3]} 
    END{printf "%s", h[1]; abc(a,b,c); 
     printf "%s", h[2]; abc(b,a,c); 
     printf "%s", h[3]; abc(c,a,b) }' file 

List1 1 
List2 8 d 
List3 b c 

Обычно операции столбца сложнее, так как логическая единица записи является строкой (строкой). Вы можете перенести это обратно в формат столбца, я не уверен, что это необходимо.

0

Вы можете взять 2 пропуск в этот файл, используя AWK:

awk 'FNR==NR{for(i=1; i<=NF; i++) dup[$i]++; next} 
    {for(i=1; i<=NF; i++) printf "%s%s", ((dup[$i]>1)? ".":$i), OFS; print ""}' file file | 
    column -t 

Выход:

List1 List2 List3 
1  .  . 
.  .  . 
.  .  . 
.  .  . 
.  .  . 
.  .  . 
.  8  b 
.  d  c 

Используется DOT просто показать пустые столбцы вывода.


Чтобы получить форматирование правильно использовать:

awk 'FNR==NR{ 
    for(i=1; i<=NF; i++) 
     dup[$i]++ 
    fld=NF 
    next 
} { 
    for(i=1; i<=NF; i++) 
    if (dup[$i]<=1) { 
     for(j=1; j<i; j++) 
      if (dup[$j]>1) 
       printf "\t" 
     p++ 
     printf "%s%s", $i, (p && p%fld == 0) ? ORS : OFS 
    } 
} 
END{ 
    print "" 
}' file file 

Выход:

List1 List2 List3 
1  8  b 
     d  c 
+0

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

+0

Я добавил оба выхода в ответ и, 2-й, тот же, что и в вопросе. Я обязательно проверю его, если для проверки будет предоставлен другой файл данных. – anubhava

0
awk -F '\|' 

{ l1[$2]++; l2[$3]++; l3[$4]++ } 
END{ 
    for i in l1 {if(l2[i] > 0 || l3[i] > 0) delete l1[i]} 
    for i in l2 {if(l1[i] > 0 || l3[i] > 0) delete l2[i]} 
    for i in l3 {if(l1[i] > 0 || l2[i] > 0) delete l3[i]} 

    # formatting report is left as an exercise 
} 
0

Будучи довольно строгим с форматом образца, вот сценарий оболочки, который генерирует ваш ожидаемый результат, кроме первой строки. В принципе, вы читаете данные в отдельные столбцы и записываете все данные в таблицу. Затем создайте столбцы данных без дублированных данных. Затем распечатайте дедуплицированные данные. Это сломается, если у вас есть данные, содержащие пробелы, но для исправления вам просто нужно настроить $ IFS для соответствия вашему разделителю.

Это также ksh93. Я не знаю, сколько будет ломаться в bash, но мне не нравится, что так много. Я думаю, что он должен быть достаточно читабельным, если вы хотите переопределить его на другом языке, но, вероятно, проще просто установить ksh93 в Linux или UNIX-систему где-нибудь (RHEL и Ubuntu отправляются вместе с пакетом, как и большинство обычных систем).

#!/usr/bin/ksh 
IFS="${IFS}|" # also split on pipe 

[[ -f "${1:-}" ]] || { print -- "'${1:-}' is not a file" >&2; exit 1; } 
exec 3<$1 

read firstline <&3 
read -A headings <&3 
# prune first element, due to weird line splitting 
headings=("${headings[@]:1}") 


# build data structure of all columns and aggregated values 
typeset -a data 
typeset -A counter 
typeset -i row=0 
while read -A line 
do 
    col=0 
    #for v in ${line[@]} 
    for v in "${line[@]:1}" # ignore false first element 
    do 
    data[$((col++))][$row]=$v 
    ((counter[$v] += 1)) 
    done 
    ((row++)) 
done <&3 

# remove duplicated data from columns 
row=0 
typeset -i maxrows=0 
typeset -a deduped 
for col in {1..${#data[@]}} 
do 
    c=$((col - 1)) 
    row=0 
    for v in ${data[$c][@]} 
    do 
    [[ ${counter[$v]} -eq 1 ]] && deduped[$c][$((row++))]=${v} 
    done 
    [[ $row -gt $maxrows ]] && maxrows=$row 
done 

# pad short columns with empty elements and calculate widths 
typeset -a widths 
for col in {1..${#deduped[@]}} 
do 
    c=$((col-1)) 
    widths[$c]=${#headings[$c]} # default to heading width 
    while [[ ${#deduped[$c][@]} -lt $maxrows ]] 
    do 
    deduped[$c][${#deduped[$c][@]}]='' 
    [[ ${widths[$c]} -lt ${#v} ]] && widths[$c]=${#v} 
    done 
    ((widths[$c] += 2)) # allow for a space on each side 
done 

# print the table out 
# header first 
format='|' 
for w in "${widths[@]}" 
do 
    format="${format}%=${w}s|" 
done 
format="${format}\n" 
printf "$format" "${headings[@]}" 

# then table 
row=0 
while [[ $row -lt $maxrows ]] 
do 
    typeset -a r 
    for col in {1..${#deduped[@]}} 
    do 
    c=$((col-1)) 
    r[$c]="${deduped[$c][$row]}" 
    done 
    printf "$format" "${r[@]}" 
    ((row++)) 
done 

работает на тестовом файле:

$ ./test.ksh testfile 
| List1 | List2 | List3 | 
| 1 | 8 | b | 
|  | d | c | 
+0

Если ваш входной формат на самом деле не начинается с канала, закомментируйте часть «обрезать первый столбец» после чтения заголовков и используйте альтернативный оператор 'for' (тот, который не содержит': 1' в конце). Я не совсем уверен, почему он работает именно так - возможно, что-то с порядком символов в '$ IFS' или что-то еще). – dannysauer

+1

Плохое планирование, в основном. :) – dannysauer

1
$ cat tst.awk 
BEGIN { FS="[[:space:]]*[|][[:space:]]*" } 
NR<3 { print; next } 
{ 
    for (i=2;i<NF;i++) { 
     cnt[$i]++ 
     inCells[NR,i] = $i 
    } 
} 
END { 
    for (inRowNr=3; inRowNr<=FNR; inRowNr++) { 
     for (colNr=2; colNr<NF; colNr++) { 
      val = inCells[inRowNr,colNr] 
      if (cnt[val] == 1) { 
       outRowNr = ++colOutRowNr[colNr] 
       outCells[outRowNr,colNr] = val 
       numOutRows = (outRowNr > numOutRows ? outRowNr : numOutRows) 
      } 
     } 
    } 

    for (outRowNr=1; outRowNr<=numOutRows; outRowNr++) { 
     printf "|" 
     for (colNr=2; colNr<NF; colNr++) { 
      printf "  %s  |", ((outRowNr,colNr) in outCells ? outCells[outRowNr,colNr] : " ") 
     } 
     print "" 
    } 

} 

.

$ awk -f tst.awk file 
------------------------------------| 
| List1 | List2 | List3 | 
|  1  |  8  |  b  | 
|   |  d  |  c  | 
0

Этот ответ фокусируется на общем случае, в котором имеется п столбцов, для любого п> = 1. Это делает программу немного дольше, чем было бы в противном случае.

Поскольку OP относится к Excel, этот ответ также предполагает, что данные доступны в виде файла с разделителями-разделителями (например, TSV или разделенные каналами). Для удобства чтения, я буду считать, несложный CSV, например, так:

List1,List2,List3 
1,2,3 
2,3,4 
3,4,5 
4,5,6 
5,6,7 
6,7,a 
7,8,b 
a,d,c 

Этот ответ также будет предполагать, что в выходных данных, порядок значений в каждом столбце не имеет значения. (Альтернативный случай оставлен в качестве упражнения для читателя :-)

Окончательное предположение состоит в том, что мы можем использовать разделитель полей ввода как разделитель выходного поля (OFS = FS).

Вот тогда программа AWK, которая не требует больше стандартного AWK:

awk -F, ' 
    # print an m by n matrix, a: 
    function printm(a,m,n, i,j, row) { 
    for(i=1;i<=m;i++) { 
     row=a[i,1]; 
     for(j=2;j<=n;j++) {row = row OFS a[i,j]} 
     print row; 
    } 
    } 

    function maxfields(i) { if (MAXFIELDS =="" || MAXFIELDS<i) {MAXFIELDS=i; } } 
    function maxcounter(i) { if (MAXCOUNTER=="" || MAXCOUNTER<i) {MAXCOUNTER=i;} } 
    # process one line of input: 
    function assemble( i) { 
    for(i=1;i<=NF;i++) { 
     if ($i in seen) { delete col[$i] } 
     else { seen[$i]; col[$i]=i; } 
    } 
    } 
    function finish( i,x) { 
    for(i=1; i<=MAXFIELDS; i++) { counter[i]=1 } 
    for (x in col) { a[ counter[col[x]], col[x] ] = x; 
        maxcounter(counter[col[x]]++); } 
    printm(a, MAXCOUNTER, MAXFIELDS) 
    } 
    BEGIN {OFS=FS} 
    NR==1 {print; next;} 
    {maxfields(NF); assemble();} 
    END { finish(); } ' 

Выход:

List1,List2,List3 
1,d,b 
,8,c 
Смежные вопросы