【AI Programmerは失敗】2個のDataViewの中身が同じかを判定する関数

# はじめに

JavaScriptで2個の`DataView`の中身が同じかを判定したくなりました。
ここで、「中身が同じ」とは「データの内容が同じ」を意味し、
参照している`ArrayBuffer`やその中の範囲は違っても「同じ」になりうることとします。

# うまく動かないコード

[AI Programmer](https://aiprogrammer.hashlab.jp/) に、JavaScriptの

> 2個のDataViewの中身が同じかを判定する関数

を頼んだところ、以下のコードが出力されました。

```javascript
function compareDataViews(view1, view2) {
  if (view1.byteLength !== view2.byteLength) {
    return false;
  }
  const view1Array = new Uint8Array(view1.buffer);
  const view2Array = new Uint8Array(view2.buffer);
  for (let i = 0; i < view1Array.length; i++) {
    if (view1Array[i] !== view2Array[i]) {
      return false;
    }
  }
  return true;
}
```

残念ながら、このコードは正しくありません。
例えば、以下の入力を与えてみます。

```javascript
const data1 = new Uint8Array([0x12, 0x34, 0x56, 0x78]).buffer;
const data2 = new Uint8Array([0x56, 0x78]).buffer;

const view1 = new DataView(data1, 0, 2);
const view2 = new DataView(data1, 2, 2);
const view3 = new DataView(data2);

console.log("view1 : " + view1.getUint16(0).toString(16));
console.log("view2 : " + view2.getUint16(0).toString(16));
console.log("view3 : " + view3.getUint16(0).toString(16));
console.log("view1 == view2 : " + compareDataViews(view1, view2));
console.log("view2 == view3 : " + compareDataViews(view2, view3));
console.log("view1 == view3 : " + compareDataViews(view1, view3));
```

[iUMUUW - Online JavaScript Interpreter & Debugging Tool - Ideone.com](https://ideone.com/iUMUUW)

以下の実行結果が得られました。

```
view1 : 1234
view2 : 5678
view3 : 5678
view1 == view2 : true
view2 == view3 : false
view1 == view3 : false
```

`view1`と`view2`は中身が異なるにもかかわらず、中身が同じだと判定されています。
また、`view2`と`view3`は中身が同じであるにもかかわらず、中身が異なると判定されています。

# 何がいけなかったのか

[DataView - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/DataView)

`DataView`は、以下のプロパティを持ちます。

* `buffer` : 参照する`ArrayBuffer`
* `byteOffset` : `ArrayBuffer`の中で参照を開始する位置
* `byteLength` : `ArrayBuffer`の中で参照する長さ

今回の例では、各`DataView`は以下のように`ArrayBuffer`を参照しています。

![dataview-20221004.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/95736561-c66c-7600-ecf5-f559650ce89c.png)

一方、生成された `compareDataViews` 関数は `byteOffset` を無視して `buffer` を参照しています。
すると、`view2` も `view1` と同様に `data1` の先頭から参照していると間違えてしまいます。

![dataview-wrong-20221004.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/05b9b658-3642-aec1-c39f-9a69146fb295.png)

そのため、間違った判定結果が得られてしまいます。

# どうすればよかったか

`Uint8Array` に変換する際に、`buffer` だけでなく `byteOffset` や `byteLength` も参照します。
ついでに、関数名も改善しておきます。

```javascript
function isDataViewsEquals(view1, view2) {
  if (view1.byteLength !== view2.byteLength) {
    return false;
  }
  const view1Array = new Uint8Array(view1.buffer, view1.byteOffset, view1.byteLength);
  const view2Array = new Uint8Array(view2.buffer, view2.byteOffset, view2.byteLength);
  for (let i = 0; i < view1Array.length; i++) {
    if (view1Array[i] !== view2Array[i]) {
      return false;
    }
  }
  return true;
}

const data1 = new Uint8Array([0x12, 0x34, 0x56, 0x78]).buffer;
const data2 = new Uint8Array([0x56, 0x78]).buffer;

const view1 = new DataView(data1, 0, 2);
const view2 = new DataView(data1, 2, 2);
const view3 = new DataView(data2);

console.log("view1 : " + view1.getUint16(0).toString(16));
console.log("view2 : " + view2.getUint16(0).toString(16));
console.log("view3 : " + view3.getUint16(0).toString(16));
console.log("view1 == view2 : " + isDataViewsEquals(view1, view2));
console.log("view2 == view3 : " + isDataViewsEquals(view2, view3));
console.log("view1 == view3 : " + isDataViewsEquals(view1, view3));
```

[AECB0S - Online JavaScript Interpreter & Debugging Tool - Ideone.com](https://ideone.com/AECB0S)

以下の実行結果が得られました。

```
view1 : 1234
view2 : 5678
view3 : 5678
view1 == view2 : false
view2 == view3 : true
view1 == view3 : false
```

正しく判定できていそうであることがわかります。