2つの区切りファイルを比較し、違いを出力する

2つの区切りファイルを比較し、違いを出力する

過去にも何度かここで同様の質問をして大きな成果を得ましたが、現在私のニーズは少し変わっており、求めている正確な結果を得るのに苦労しています。

2つを比較したい似ている区切りファイルですが、行数が異なり、重複している部分もあります。ファイルのヘッダーは同じになります。

file1.txt

mem_id     date     time     building
aa1         bb1      cc1     dd1
aa2         bb2      cc2     dd2
aa3         bb3      ccx3    dd3
aa4         bb4      cc4     dd4
aa5         bb5      cc5     dd5

file2.txt

mem_id     date     time     building
aa1         bby1     cc1     ddy1
aa2         bb2      cc2     dd2  
aa3         bb3      cc3     dd3
aa4         bb4      cc4     dd4
aa4         bb4a     cc4a    dd4a

4 つの違いがあることがわかります。

1- ファイル2、mem_id aa1 には、「日付」と「建物」の両方の列に「y」があります。

2- File1、mem_id aa3 の「time」列に「x」がある

3- File1、mem_idはaa5

4- File2、mem_id aa4には2つのエントリがあります

2 つのファイルの違いだけを出力するスクリプトを実行したいと思います (同じ行はスキップします)。 試してみたものはすべて、重複行またはスキップされた行で停止し、ファイル全体の出力がおかしくなります。 すべての行が一致する場合、次のコードがうまく機能します。

current_code

awk -F ',' 'BEGIN {IGNORECASE = 1} NR==1 {for (i=1; i<=NF; i++)    header[i] = $i}NR==FNR {for (i=1; i<=NF; i++) {A[i,NR] = $i} next}{  for (i=1; i<=NF; i++) if (A[i,FNR] != $i) print header[1]"#-"$1": " header[i] "- " ARGV[1] " value= ", A[i,FNR]" / " ARGV[2] " value= "$i}'

desired_output.txt

Mem_id#-aa1 : date-  file1.txt value = bb1 / file2.txt value= bby1
Mem_id#-aa1 : building-  file1.txt value = dd1 / file2.txt value= ddy1
Mem_id#-aa3 : time-  file1.txt value = ccx3 / file2.txt value= dd3
Mem_id#-aa4 : date-  file1.txt value =        / file2.txt value= bb4a
Mem_id#-aa4 : time-  file1.txt value =        / file2.txt value= cc4a
Mem_id#-aa4 : building-  file1.txt value =        / file2.txt value= dd4a
Mem_id#-aa5 : date-  file1.txt value = bb5 / file2.txt value= 
Mem_id#-aa5 : time-  file1.txt value =  cc5 / file2.txt value= 
Mem_id#-aa5 : building-  file1.txt value =  dd5 / file2.txt value= 

答え1

次の Python プログラムは、必要なこと、またはそれに非常に近いことを実行するはずです。

  • 3行目はdesired_output.txt間違っているようです:

    Mem_id#-aa3 : time-  file1.txt value = ccx3 / file2.txt value= dd3
    

    cc3`dd3 should probably be

    それ以外では、プログラムからの出力は空白を除いて一致していますが、サンプル出力では少し不規則なようです。

  • 入力はキー(memid)によって順序付けられているとみなされます

  • プログラムは、同期を試みている間、デフォルトで 4 行 (max_diff + 1) をバッファリングします。そのバッファ内のキーのいずれも「現在の」キーと vv に一致しない場合は、両方とも一致しないものと見なされて出力され、次のペアが試されます。キーが見つかった場合は、他のバッファ内の一致しない項目または出力が最初に行われます。
  • サンプル入力では、最初の行と 2 番目の行に同じ memid が 2 回 (またはそれ以上) ある場合に予想される動作が少し制限されています。

    では、output()任意の行を一致させて、一致するすべての行をポップします (左と右の両方から)。したがって、同じメディア内の一致する行の順序は重要ではありません。左または右、あるいは両方が空の場合、その後の印刷は簡単です (特に両方が空の場合)。残りについては、残りの各行を左から右に一致させます。

  • 文字fmt列によってline_out()出力が決まり、自由に変更/並べ替えることができます。

#! /usr/bin/env python
# coding: utf-8
# http://unix.stackexchange.com/q/161913/33055

from __future__ import print_function
from collections import OrderedDict
from logging import debug

import sys


class RowBuffer:
    def __init__(self, file_name, delim=None, max_diff=3):
        """delim is the character that is used for splitting input.
        None->whitespace
        """
        self._verbose = 0
        self._file_name = file_name
        self._fp = open(self._file_name)
        self._delim = delim
        self._max_diff = max_diff
        self._head = self._fp.readline().split(delim)
        # the buffer consists of a maximum of max_diff entries
        # the keys are the first items of a row, the value a list
        # of all other items on that row
        self._buffer = OrderedDict()
        self.fill_buffer()

    def compare(self, rb):
        """check if self._buffer"""
        if self._head != rb._head:
            print('headings differ:\n  {}\n  {}'.format(
                self._head, rb._head))
        while self._buffer:
            l = self.get()
            try:
                r = rb.get()
            except KeyError:
                debug('only left %s', l[0])
                self.output(l, None, rb)
                break
            if l[0] == r[0]:
                debug('compare vals %s', l[0])
                self.output(l, r, rb)
                continue
            if l[0] in rb:
                # left key in right, but not at top
                # output right until top keys are same
                while l[0] != r[0]:
                    debug('only right %s', r[0])
                    self.output(None, r, rb)
                    r = rb.get()
                self.output(l, r, rb)
                continue
            if r[0] in self:
                # right key in left, but not at top
                # output left until top keys are same
                while l[0] != r[0]:
                    debug('only left %s', l[0])
                    self.output(l, None, rb)
                    l = self.get()
                self.output(l, r, rb)
                continue
            # neither found: output both
            debug('neither left in right nor vv %s %s', l[0], r[0])
            self.output(l, None, rb)
            self.output(None, r, rb)
        while rb._buffer:  # remaining in right file
            r = rb.get()
            debug('only right %s', r[0])
            self.output(None, r, rb)

    def output(self, l, r, right):
        fmt1 = '{col0_header}#-{col0_value} : {col_header}-  ' \
            '{left_file_name} value = {left_value} / ' \
            '{right_file_name} value= {right_value}'
        d = dict(
            col0_header=self._head[0],
            left_file_name=self._file_name,
            right_file_name=right._file_name,
        )
        if l is not None and r is not None:
            # one or more values on both sides, compare all lines on the
            # left with all on the right remove any matching pairs
            match = {}  # left index to right index
            for lidx, lv in enumerate(l[1]):
                for ridx, rv in enumerate(r[1]):
                    if lv == rv:
                        if lidx not in match:
                            match[lidx] = ridx
            # pop from back of list, not invalidate index
            for lidx in sorted(match, reverse=True):
                l[1].pop(lidx)
            for ridx in sorted(match.values(), reverse=True):
                r[1].pop(lidx)
        if r is None or not r[1]:
            for lv in l[1]:
                for idx, k in enumerate(self._head[1:]):
                    self.line_out(d, col0_value=l[0], col_header=k,
                                  left_value=lv[idx], right_value='    ')
            return
        if l is None or not l[1]:
            for rv in r[1]:
                for idx, k in enumerate(self._head[1:]):
                    self.line_out(d, col0_value=l[0], col_header=k,
                                  left_value='    ', right_value=rv[idx])
            return
        # print non matching
        for lv in l[1]:
            for rv in r[1]:
                for idx, k in enumerate(self._head[1:]):
                    if lv[idx] == rv[idx]:
                        continue  # same value
                    self.line_out(d, col0_value=l[0], col_header=k,
                                  left_value=lv[idx], right_value=rv[idx])

    def line_out(self, d, **kw):
        # manipulate and print output
        # the fields of the format string can be arbitrarily arranged
        # as long as the field names (between {} match)
        fmt = '{col0_header}#-{col0_value} : {col_header}-  ' \
            '{left_file_name} value = {left_value} / ' \
            '{right_file_name} value= {right_value}'
        d1 = d.copy()
        d1.update(kw)
        s = fmt.format(**d1)
        # s = s.rstrip()
        s = s[0].upper() + s[1:]  # sample output doesn't match input
        print(s)

    def get(self):
        item = self._buffer.popitem(last=False)
        self.fill_buffer()
        return item

    def fill_buffer(self):
        if self._fp is None:
            return
        while len(self._buffer) < self._max_diff:
            row = self._fp.readline().split(self._delim)
            if not row:
                self._fp.close()
                self._fp = None
                return
            entry = self._buffer.setdefault(row[0], [])
            entry.append(row[1:])

    def __contains__(self, key):
        self.fill_buffer()
        return key in self._buffer

rb1 = RowBuffer(sys.argv[1])
rb2 = RowBuffer(sys.argv[2])
rb1.compare(rb2)

答え2

これは、問題に対する (エレガントとは程遠い) 部分的な解決策です。最初の列を ID 列として使用し (最初の列である必要はありませんが、必ず 1 つは必要です)、同じキーsuffixの複数の出現を格納するために 3 番目の次元を導入します。最終的には、ファイル 1 で見つからなかったファイル 2 のキーを見つけようとします。

BEGIN {
    IGNORECASE = 1
} 

NR==1 {
    for (i = 1; i <= NF; i++)    
        header[i] = $i
    suffix = 0
    previous_key=""
}

NR==FNR {
    if ($1 == previous_key) {
        suffix = suffix + 1
        max_suffix[$1] = suffix
    } else
        suffix = 0
    for (i = 1; i <= NF; i++) {
        A[$1,suffix,i] = $i
    } 
    key_count[$1] = key_count[$1] + 1
    previous_key = $1
    next
}

{  
    if ($1 == previous_key)
        suffix = suffix + 1
    else
        suffix = 0
    previous_key = $1
    if (A[$1,suffix,1] != "") {
        for (i = 2; i <= NF; i++) 
            if (A[$1,suffix,i] != $i) {
                print header[1]"#-"$1": " header[i] "- " ARGV[1] " value= ", A[$1,suffix,i]" / " ARGV[2] " value= "$i
            }
        key_count[$1] = key_count[$1] - 1
    }
    else
        for (i = 2; i <= NF; i++) 
            print header[1]"#-"$1": " header[i] "- " ARGV[1] " value= ", " / " ARGV[2] " value= "$i
}

END {
    for (missing_key in key_count) 
        if (key_count[missing_key] > 0) {            
            for (suffix = max_suffix[missing_key] - key_count[missing_key] + 1; suffix <= C[missing_key]; suffix++) 
                for (i = 2; i <= NF; i++) 
                    print header[1]"#-"missing_key": " header[i] "- " ARGV[1] " value= ", A[missing_key,suffix,i] " / " ARGV[2] " value= "
        }           
}

注意点があります: ファイル 2 内の一致しないエントリは常に最後に印刷され、ファイル内の位置に従ってソートされません。さらに、これらの行のソートは任意です。結果をコマンドにパイプすることで、これを解決できると思いますsort

関連情報