週末に、「与えられた2つのバイト配列を比較し、一致した要素と、一致しなかった要素を取得する」プログラムを(何も考えずに)書いていたら、、はまった(あー、ほんとダメだ)。
次の2つのバイト配列が比較できなかったのだ。。
byte[] a = { 0x00, 0x11, 0x22, 0x22 }; byte[] b = { 0x00, 0x22, 0x22, 0x33 };
これらを比較した結果、「1番目と3番目の要素が一致、2番目と4番目の要素が不一致」とすることもできるし、「バイト配列aの2番目の要素が余計で、バイト配列bの4番目の要素が余計。0x00、0x22、0x22の3つの要素が一致」とすることもできる。
比較する配列の特性や、取得したい結果によって、比較方法を変えなければならないような気がした。
そこで、1バイトずつ比較するのか、4バイト先まで比較する(一致した要素があった場合には、そこまでの要素を、余計な要素とする)のか、比較する範囲を指定できるようにした(せざるを得なかった)。
先ほど、ビール片手に、次のBytesDiffクラスを作成した。
public class BytesDiff { private byte[] base; private byte[] target; private final ArrayList<Byte> matchedBytes = new ArrayList<Byte>(); private final Map<Integer, Byte> baseOnlyBytes = new HashMap<Integer, Byte>(); private final Map<Integer, Byte> targetOnlyBytes = new HashMap<Integer, Byte>(); /** * @param base 比較元となるバイト配列 * @param target 比較先となるバイト配列 */ public BytesDiff(byte[] base, byte[] target) { this.base = base; this.target = target; } public void diff(int diffRange) { if (diffRange < 0) throw new IllegalArgumentException( String.format("比較する範囲として、負の数 %s が指定されました。", diffRange)); clearResult(); int bi = 0; // 比較元バイト配列のインデックス int ti = 0; // 比較先バイト配列のインデックス while (bi < base.length && ti < target.length) { boolean matched = false; for (int offset = 0; offset < diffRange && bi + offset < base.length && ti + offset < target.length; offset++) { if (base[bi] == target[ti + offset]) { matched = true; matchedBytes.add(base[bi]); for (int i = 0; i < offset; i++) targetOnlyBytes.put(ti + i, target[ti + i]); ti = ti + offset; break; } if (target[ti] == base[bi + offset]) { matched = true; matchedBytes.add(target[ti]); for (int i = 0; i < offset; i++) baseOnlyBytes.put(bi + i, base[bi + i]); bi = bi + offset; break; } } if (!matched) { baseOnlyBytes.put(bi, base[bi]); targetOnlyBytes.put(ti, target[ti]); } bi++; ti++; } for (; bi < base.length; bi++) baseOnlyBytes.put(bi, base[bi]); for (; ti < target.length; ti++) targetOnlyBytes.put(ti, target[ti]); } public void clearResult() { matchedBytes.clear(); baseOnlyBytes.clear(); targetOnlyBytes.clear(); } // -------- 結果を返すメソッド。参照をそのまま返す(イミュータブルではない) public ArrayList<Byte> getMatchedByteList() { return matchedBytes; } /** * @return 比較元のバイト配列のみに存在する値(比較元のインデックスと値のマップ) */ public Map<Integer, Byte> getBaseOnlyByteMap() { return baseOnlyBytes; } /** * @return 比較先のバイト配列のみに存在する値(比較先のインデックスと値のマップ) */ public Map<Integer, Byte> getTargetOnlyByteMap() { return targetOnlyBytes; } }
このクラスは、次のように使う(次はJUnit 4を用いたテストコードの一部)。
@Test public void testDiff() { byte[] base = { 0x00, 0x00, 0x00, 0x11, 0x33, 0x44 }; byte[] target = { 0x00, 0x11, 0x22, 0x33, 0x33 }; BytesDiff bytesDiff = new BytesDiff(base, target); bytesDiff.diff(4); assertEquals(3, bytesDiff.getMatchedByteList().size()); assertEquals(3, bytesDiff.getBaseOnlyByteMap().size()); assertEquals(2, bytesDiff.getTargetOnlyByteMap().size()); assertEquals(new Byte((byte) 0x00), bytesDiff.getMatchedByteList().get(0)); assertEquals(new Byte((byte) 0x11), bytesDiff.getMatchedByteList().get(1)); assertEquals(new Byte((byte) 0x33), bytesDiff.getMatchedByteList().get(2)); assertEquals(new Byte((byte) 0x00), bytesDiff.getBaseOnlyByteMap().get(1)); assertEquals(new Byte((byte) 0x00), bytesDiff.getBaseOnlyByteMap().get(2)); assertEquals(new Byte((byte) 0x44), bytesDiff.getBaseOnlyByteMap().get(5)); assertEquals(new Byte((byte) 0x22), bytesDiff.getTargetOnlyByteMap().get(2)); assertEquals(new Byte((byte) 0x33), bytesDiff.getTargetOnlyByteMap().get(4)); }
十分にテストしたわけではないが、動作はしそう。
通常は、どんな風に実装するのだろうか。知りたい。。