多数決による間違い探しアルゴリズムを考える

# 問題提起

ネットの情報を鵜呑みにしてはいけない。そもそも情報源自体が誤っている可能性があるので，できるだけ複数のソースから情報を集めて比較検証しなくてはならない。

一例として，表１に日本将棋連盟所属（引退・退会者も含む）の棋士一覧（の一部抜粋）を示すが，データサイトによって漢字表記の揺らぎがある。さて，正しいのはどれか？公式サイトである日本将棋連盟のデータを100%信用できないというのが悲しいところ。おそらく最も早期から棋士名簿を管理していたため，当時の法令や技術的な制限を強く受けたものと考えられる。

<table>
<caption>表１　各データサイトの将棋棋士一覧（一部抜粋）<br>※本記事執筆時点2023年10月2日でのデータに基づく。
</caption>
<thead>
<tr><th>日本将棋連盟</th><th>棋士成績DB</th><th>棋士別成績一覧</th><th>Wikipedia</th></tr>
</thead>
<tbody>
<tr><td>木村義雄</td><td>木村義雄</td><td>木村義雄</td><td>木村義雄</td></tr>
<tr><td>升田幸三</td><td>升田幸三</td><td>升田幸三</td><td>升田幸三</td></tr>
<tr><td>大山康晴</td><td>大山康晴</td><td>大山康晴</td><td>大山康晴</td></tr>
<tr><td><font color=red>広津久雄</font></td><td>廣津久雄</td><td>廣津久雄</td><td>廣津久雄</td></tr>
<tr><td>加藤一二三</td><td>加藤一二三</td><td>加藤一二三</td><td>加藤一二三</td></tr>
<tr><td>米長邦雄</td><td>米長邦雄</td><td>米長邦雄</td><td>米長邦雄</td></tr>
<tr><td>中原誠</td><td>中原誠</td><td>中原誠</td><td>中原誠</td></tr>
<tr><td>谷川浩司</td><td>谷川浩司</td><td>谷川浩司</td><td>谷川浩司</td></tr>
<tr><td>羽生善治</td><td>羽生善治</td><td>羽生善治</td><td>羽生善治</td></tr>
<tr><td><font color=red>高野秀行</font></td><td>髙野秀行</td><td>髙野秀行</td><td>髙野秀行</td></tr>
<tr><td>渡辺明</td><td>渡辺明</td><td>渡辺明</td><td>渡辺明</td></tr>
<tr><td><font color=red>高崎一生</font></td><td>髙﨑一生</td><td>髙﨑一生</td><td>髙﨑一生</td></tr>
<tr><td><font color=red>高見泰地</font></td><td>髙見泰地</td><td>髙見泰地</td><td><font color=red>高見泰地</font></td></tr>
<tr><td>藤森哲也</td><td>藤森哲也</td><td>藤森哲也</td><td>藤森哲也</td></tr>
<tr><td>斎藤慎太郎</td><td>斎藤慎太郎</td><td>斎藤慎太郎</td><td>斎藤慎太郎</td></tr>
<tr><td><font color=red>高野智史</font></td><td>髙野智史</td><td>髙野智史</td><td>髙野智史</td></tr>
<tr><td>藤井聡太</td><td>藤井聡太</td><td>藤井聡太</td><td>藤井聡太</td></tr>
<tr><td>斎藤明日斗</td><td>斎藤明日斗</td><td>斎藤明日斗</td><td>斎藤明日斗</td></tr>
<tr><td>高田明浩</td><td>高田明浩</td><td>高田明浩</td><td>高田明浩</td></tr>
<tr><td>齊藤裕也</td><td>齊藤裕也</td><td>齊藤裕也</td><td>齊藤裕也</td></tr>
<tr><td>土居市太郎</td><td>土居市太郎</td><td>土居市太郎</td><td></td></tr>
<tr><td>伊藤博文</td><td>伊藤博文</td><td>伊藤博文</td><td></td></tr>
<tr><td>宮嶋健太</td><td></td><td>宮嶋健太</td><td>宮嶋健太</td></tr>
<tr><td>上野裕寿</td><td></td><td>上野裕寿</td><td>上野裕寿</td></tr>
<tr><td></td><td>永作芳也</td><td>永作芳也</td><td>永作芳也</td></tr>
<tr><td></td><td>橋本崇載</td><td>橋本崇載</td><td>橋本崇載</td></tr>
</tbody>
</table>

念のため，小さな文字が読みづらい人のために，主として

```plantuml
@startuml
<style>
root {
  margin: 0
  padding: 0
}
title {
  fontstyle: normal
  fontsize: 64
  fontcolor: red
}
</style>
title 高・髙，崎・﨑
@enduml
```
の違いが目立つ。巷でよく見かける以下の誤用・混用は起こっていないようだ。

```plantuml
@startuml
<style>
root {
  margin: 0
  padding: 0
}
title {
  horizontalalignment: left
  fontstyle: normal
  fontsize: 64
  fontcolor: blue
}
</style>
title
辺・邊・邉
島・嶋
斉・斎・齊・齋
end title
@enduml
```

やりたいことを整理すると

- ４つのサイトの表記を相互比較して多数決による間違い探しを行う。
- １対３に分かれた場合は少数派を間違いとするが，２対２のようにタイの場合はどちらでも良い。
- ４つのサイトですべてデータが揃っている訳ではなく，一部欠損しているものもある。
  ※この場合は残りのサイトのデータのみで比較する。

こうして少数派（間違い？）を注意喚起のため色付けすることにする。

# まず JavaScript で考える
各サイトのデータ（文字列）が配列 `str[]` に格納されているとする。いちいち文字列を比較するのは面倒なので連想配列 `count[]` を用いる。`count[]` には同じ文字列のサイト数が格納される。連想配列をそのままソートできないので，いったん配列 `sorted[]` に内容を移してしてソートする。連想配列 `order[]` にはデータ（文字列）の使用順位が格納される。こうして使用順位２位以下の文字列に色付けすれば良い。

うん！JavaScript はデータ形式が柔軟なうえ，ソート機能もあるので非常に簡単に組める。

```javascript:JavaScriptによるサンプルコード
var	count = [];
for(var i = 0; i < str.length; i++) {
	if(str[i] == "") continue;
	if(count[str[i]] == null)
		count[str[i]] = 1;
	else
		count[str[i]]++;
}
var	sorted = [];
for(var k in table)
	sorted.push({str:k, count:table[k]});
sorted.sort(function(a,b) {return b.count - a.count;});
var order = [];
for(var i = 0; i < sorted.length; i++)
    order[sorted[i].str] = i;
```

# Excel VBA の場合どうする？
ここで Excel VBA を使うのは，各サイトのデータが Excel のワークブックに入っているからである。Excel でも JavaSscript が使えるようになったよ！とか，Google Sheets を使え！という助言は VBA で挫折した後で聞く。

VBA の場合，動的配列はあるが可変長配列（リスト構造）はなく，ソート機能も無い・・・いきなり詰んだ・・・が，コレで諦めない。

VBA には `Collection` オブジェクトという文字列をキーとして任意のオブジェクトを格納できる連想配列っぽいものがあることにはあるが，キー文字列の存在チェックを行えるメソッドが無く，また一度登録したオブジェクトの更新ができないため非常に使いにくい。ただ，連想配列は Windows Scripting Host の `Scripting.Dictionary` オブジェクトを使えばいいし，ソートについても .NET Framework の `System.Collections.ArrayList` オブジェクトを使えばいいので十分代替がきく。ここで，ちょっとしたテクニックを披露すると，連想配列のキーと値を連結させた一つの文字列としてソートさせている。こうすることで連想配列の値を用いて比較が行え，かつキーも含めた全体をソートさせることができる。こうして連想配列 `oOrder()` にはデータ（文字列）の使用順位が格納される。

```vb:VBAによるサンプルコード
Dim oCount: Set oCount = CreateObject("Scripting.Dictionary")
Dim oOrder: Set oOrder = CreateObject("Scripting.Dictionary")
Dim oSorted: Set oSorted = CreateObject("System.Collections.ArrayList")
Dim i, a, k, s
Count.RemoveAll                 ' 実際には繰り返し処理を行うので内容を毎回クリアする
For i = 0 To UBound(aStr)
    s = aStr(i)
    If s <> "" Then
        If oCount.Exists(s) Then
            oCount(s) = oCount(s) + 1
        Else
            oCount.Add s, 1
        End If
    End If
Next
oSorted.Clear                   ' 実際には繰り返し処理を行うので内容を毎回クリアする
i = 0
For Each k In oCount.Keys
    oSorted.Add oCount(k) & vbTab & i & vbTab & k
    i = i + 1
Next
oSorted.Sort                    ' いったん Sort してから Reverse しないといけないらしい
oSorter.Reverse
oOrder.RemoveAll                ' 実際には繰り返し処理を行うので内容を毎回クリアする
For i = 0 To oSorted.Count - 1
    a = Split(oSorted(i), vbTab)
    oOrder.Add a(2), i
Next
```
:::note
ちなみに，キーと値以外に連想配列の登録順 `i` も連結させてソートさせている。これは安全ソートにするための配慮のつもりであったが，ソート結果を反転しているため当初の意図とは真逆になってしまった。ただ，この結果，最も信頼性が高いと思われるウィキペディアのデータが同率タイのときでも優先され易くなったので良しとしている。
:::

:::note warn
本ケースはデータ数が１桁であることが明らかなため
`oSorted.Add oCount(k) & vbTab & i & vbTab & k`
としたが，データ数が３桁に及ぶ場合は
`oSorted.Add Format(oCount(k), "000") & vbTab & Format(i, "000") & vbTab & k`
とする必要がある。
:::

# 実際の VBA コード

Excel ファイルの内容を以下に示す。ワークシート「棋士名比較」に４つのサイトの棋士名（漢字表記，よみがな）が順に格納されている。１行目はヘッダで２行目以降にデータが格納されている。奇数列は漢字，偶数列はよみがなである。日本将棋連盟のよみがなはローマ字表記なのであくまで参考用である。

<table>
<thead>
<tr><th colspan=9>Excel ファイルの内容（一部抜粋）</th></tr>
<thead>
<tbody>
<tr><td></td>
    <td style="text-align:center;">A</td><td style="text-align:center;">B</td>
    <td style="text-align:center;">C</td><td style="text-align:center;">D</td>
    <td style="text-align:center;">E</td><td style="text-align:center;">F</td>
    <td style="text-align:center;">G</td><td style="text-align:center;">H</td></tr>
<tr></tr>
<tr><td>1</td>
    <th colspan=2 style="text-align:center;">$\textsf{日本将棋連盟}$</th>
    <th colspan=2 style="text-align:center;">$\textsf{棋士成績DB}$</th>
    <th colspan=2 style="text-align:center;">$\textsf{棋士別成績一覧}$</th>
    <th colspan=2 style="text-align:center;">$\textsf{ウィキペディア}$</th></tr>
<tr></tr>
<tr><td>2</td>
    <th>$\textsf{金易二郎}$</th><th>$\textsf{Yasujiro Kon}$</th>
    <th>$\textsf{金易二郎}$</th><th>$\textsf{こんやすじろう}$</th>
    <th>$\textsf{金易二郎}$</th><th>$\textsf{こんやすじろう}$</th>
    <th>$\textsf{金易二郎}$</th><th>$\textsf{こんやすじろう}$</th></tr>
<tr></tr>
<tr><td>3</td>
    <th>$\textsf{木村義雄}$</th>
    <th></th>
    <th>$\textsf{木村義雄}$</th><th>$\textsf{きむらよしお}$</th>
    <th>$\textsf{木村義雄}$</tH><th>$\textsf{きむらよしお}$</th>
    <th>$\textsf{木村義雄}$</th><th>$\textsf{きむらよしお}$</th></tr>
<tr></tr>
<tr><td>4</td>
    <th>$\textsf{金子金五郎}$</th><th>$\textsf{Kingorou Kaneko}$</th>
    <th>$\textsf{金子金五郎}$</th><th>$\textsf{かねこきんごろう}$</th>
    <th>$\textsf{金子金五郎}$</th><th>$\textsf{かねこきんごろう}$</th>
    <th>$\textsf{金子金五郎}$</th><th>$\textsf{かねこきんごろう}$</th></tr>
</tbody>
</table>

実際の VBA マクロを以下に示す。奇数列（漢字表記）と偶数列（よみがな）を分けて処理を行っている。同じような処理を繰り返すため一部をサブルーチン化して呼び出すようにした。こうして得られた順位 `oOrder()` に応じてセルの色付けを行う。

```vb:実際のVBAコード（メインルーチン）
Dim i           As Long
Dim j           As Long
Dim s           As String
Dim oCount      As Object:  Set oCount = CreateObject("Scripting.Dictionary")
Dim oOrder      As Object:  Set oOrder = CreateObject("Scripting.Dictionary")
Dim aColor(3)   As Long
aColor(0) = 1   ' BLACK
aColor(1) = 3   ' RED
aColor(2) = 5   ' BLUE
aColor(3) = 4   ' GREEN
With ThisWorkbook.Worksheets("棋士名比較")
    .Cells.Font.ColorIndex = aColor(0)
    i = 2
    Do While .Cells(i, 1).Value <> "" Or .Cells(i, 3).Value <> "" _
          Or .Cells(i, 5).Value <> "" Or .Cells(i, 7).Value <> ""
        ' 漢字表記のチェック
        oCount.RemoveAll
        For j = 1 To 7 Step 2
            s = .Cells(i, j).Value
            If s <> "" Then CountString oCount, s
        Next
        CountToOrder oCount, oOrder
        For j = 1 To 7 Step 2
            s = .Cells(i, j).Value
            If s <> "" Then .Cells(i, j).Font.ColorIndex = aColor(oOrder(s))
        Next
        ' よみがなのチェック
        oCount.RemoveAll
        For j = 4 To 8 Step 2
            s = .Cells(i, j).Value
            If s <> "" Then CountString oCount, s
        Next
        CountToOrder oCount, oOrder
        For j = 4 To 8 Step 2
            s = .Cells(i, j).Value
            If s <> "" Then .Cells(i, j).Font.ColorIndex = aColor(oOrder(s))
        Next
        i = i + 1
    Loop
End With
```

連想配列を用いて文字列のカウントを行うサブルーチンである。先ほどのサンプルコードと同じである。

```vb:実際の VBA コード（サブルーチンその１）
Private Sub CountString(oCount As Object, s As String)
    If oCount.Exists(s) Then
        oCount(s) = oCount(s) + 1
    Else
        oCount.Add s, 1
    End If
End Sub
```

ソートを行って順位付けを行うサブルーチンである。先ほどのサンプルコードとほぼ同じである。ただ COM オブジェクトの新規生成は比較的重い処理なので，初回呼び出し時のみに１回だけ行うようにした。

```vb:実際の VBA コード（サブルーチンその２）
Private Sub CountToOrder(oCount As Object, oOrder As Object)
Static  oSorted As Object
    Dim k       As Variant
    Dim i       As Long
    Dim a       As Variant
    If oSorted Is Nothing Then
        Set oSorted = CreateObject("System.Collections.ArrayList")
    End If
    oSorted.Clear
    i = 0
    For Each k In oCount.Keys
        oSorted.Add oCount(k) & vbTab & i & vbTab & k
        i = i + 1
    Next
    oSorted.Sort
    oSorted.Reverse
    oOrder.RemoveAll
    For i = 0 To oSorted.Count - 1
        a = Split(oSorted(i), vbTab)
        oOrder.Add a(2), i
    Next
End Sub
```

# 実行結果

相違部分のみを表２に示す。日本将棋連盟のよみがなはあくまで参考用であり，比較は行っていない。

<table>
<thead>
<tr><th colspan=8>$\textsf{表２　各データサイトの将棋棋士一覧（相違部分のみ）}\hspace{600px}$</th>
<tr><th colspan=2>日本将棋連盟</th>
    <th colspan=2>棋士成績DB</th>
    <th colspan=2>棋士別成績一覧</th>
    <th colspan=2>Wikipedia</th></tr>
</thead>
<tbody>
<tr><td><font color=red>広津久雄</font></td><td>Hisao Hirotsu</td><td>廣津久雄</td><td>ひろつひさお</td><td>廣津久雄</td><td>ひろつひさお</td><td>廣津久雄</td><td>ひろつひさお</td></tr>
<tr><td>清野静男</td><td>Shizuo Seino</td><td>清野静男</td><td><font color=red>きよのしずお</font></td><td>清野静男</td><td>せいのしずお</td><td>清野静男</td><td>せいのしずお</td></tr>
<tr><td>桐山清澄</td><td>Kiyozumi Kiriyama</td><td>桐山清澄</td><td>きりやまきよずみ</td><td>桐山清澄</td><td><font color=red>きりやまきよすみ</font></td><td>桐山清澄</td><td>きりやまきよずみ</td></tr>
<tr><td>武市三郎</td><td>Saburou Takeichi</td><td>武市三郎</td><td><font color=red>たけいちちさぶろう</font></td><td>武市三郎</td><td><font color=blue>たけちさぶろう</font></td><td>武市三郎</td><td>たけいちさぶろう</td></tr>
<tr><td>達正光</td><td>Masamitsu Tachi</td><td>達正光</td><td>たちまさみつ</td><td>達正光</td><td><font color=red>たつまさみつ</font></td><td>達正光</td><td>たちまさみつ</td></tr>
<tr><td><font color=red>高野秀行</font></td><td>Hideyuki Takano</td><td>髙野秀行</td><td>たかのひでゆき</td><td>髙野秀行</td><td>たかのひでゆき</td><td>髙野秀行</td><td>たかのひでゆき</td></tr>
<tr><td><font color=red>高崎一生</font></td><td>Issei Takazaki</td><td>髙﨑一生</td><td>たかざきいっせい</td><td>髙﨑一生</td><td>たかざきいっせい</td><td>髙﨑一生</td><td>たかざきいっせい</td></tr>
<tr><td><font color=red>高見泰地</font></td><td>Taichi Takami</td><td>髙見泰地</td><td>たかみたいち</td><td>髙見泰地</td><td>たかみたいち</td><td><font color=red>高見泰地</font></td><td>たかみたいち</td></tr>
<tr><td><font color=red>高野智史</font></td><td>Satoshi Takano</td><td>髙野智史</td><td>たかのさとし</td><td>髙野智史</td><td>たかのさとし</td><td>髙野智史</td><td>たかのさとし</td></tr>
<tr><td></td><td></td><td>藤川義夫</td><td><font color=red>ふじかわわよしお</font></td><td>藤川義夫</td><td>ふじかわよしお</td><td>藤川義夫</td><td>ふじかわよしお</td></tr>
</tbody>
</table>

表３に女流棋士を示す。

<table>
<thead>
<tr><th colspan=8>$\textsf{表３　各データサイトの将棋女流棋士一覧（相違部分のみ）}\hspace{544px}$</th>
<tr><th colspan=2>日本将棋連盟</th>
    <th colspan=2>棋士成績DB</th>
    <th colspan=2>棋士別成績一覧</th>
    <th colspan=2>Wikipedia</th></tr>
</thead>
<tbody>
<tr><td>谷川治恵</td><td>Harue Tanikawa</td>
    <td><font color=red>谷川治惠</font></td><td>たにかわはるえ</td>
    <td>谷川治恵</td><td>たにかわはるえ</td>
    <td>谷川治恵</td><td>たにかわはるえ</td></tr>
<tr><td><font color=red>小高佐季子</font></td><td>Sakiko Odaka</td>
    <td>小髙佐季子</td><td>おだかさきこ</td>
    <td>小髙佐季子</td><td>おだかさきこ</td>
    <td><font color=red>小高佐季子</font></td><td>おだかさきこ</td></tr>
<tr><td></td><td></td>
    <td>村山幸子</td><td><font color=red>むらやまゆきこ</font></td>
    <td>村山幸子</td><td>むらやまさちこ</td>
    <td>村山幸子</td><td>むらやまさちこ</td></tr>
</tbody>
</table>

谷川さんの漢字の違いを下記に示す。

```plantuml
@startuml
<style>
root {
  margin: 0
  padding: 0
}
title {
  fontstyle: normal
  fontsize: 64
  fontcolor: red
}
</style>
title 恵・惠
@enduml
```

タイトルでは間違い探しと書いてあるが，正しくは相互比較によるチェックであろう。少数派のほうが正しい場合もあり得るし，すべて間違っている可能性もある。たとえば高野智史さん本人のtwitterでは「高野智史」表記となっており，少数派のほうが正しいように思えるケースもある。逆に「髙野智史」表記の出所を探している。ウィキペディアも将棋棋士一覧のページでは「髙野智史」表記だが，個人のページでは「高野智史」と記載されている。※2023年10月3日時点

https://twitter.com/sa_takano302

一方，小髙佐季子さんのほうは本人のtwitterで「小髙佐季子」表記となっている。※2023年10月8日時点

https://twitter.com/sko_6417

# 備考
やはり Wikipedia は間違いが少ないように思える。なお，棋士成績DBおよび棋士別成績一覧の管理者殿には本記事で気づいた誤記を報告済みである。※2023年10月4日付

# 参考
本記事で引用した各データサイトを以下に示す。
- [日本将棋連盟：棋士データベース（棋士番号順）](https://www.shogi.or.jp/player/numerical_order.html)
- [棋士成績DB：棋士一覧](http://kenyu1234.php.xdomain.jp/list.php)
- [棋士別成績一覧](http://kishibetsu.com/index.html)
- [Wikipedia：将棋棋士一覧](https://ja.wikipedia.org/wiki/将棋棋士一覧)

本記事で参考にした情報を以下に示す。
- [VBA SortedList Class - Qiita](https://qiita.com/Q11Q/items/5c87f2999de7d2216e86)
- [VBAでコールバック関数を使ういろんな方法 - Qiita](https://qiita.com/Kamo123/items/e676e0cbe6de83410980)
- [VBA Collection を自作して処理速度を向上してみた - Qiita](https://qiita.com/019_takorice_/items/56bc2d13849622ccf37e)
- [VBAでインタフェースを実装する - Qiita](https://qiita.com/narikkyo/items/7e48a34e797fa6af2f04)
- [独自コレクションクラスのすゝめ - Qiita](https://qiita.com/tatsuya-fujii-LITALICO/items/213159ea4c59543b12b8)
- [VBAの備忘録　DictionaryをKey昇順でソートする - Qiita](https://qiita.com/OhiKazuma/items/0bae367afee13c77f7de)
- [Excel VBAで使える可変長なコレクションまとめ - Qiita](https://qiita.com/minoru-nagasawa/items/fcd317fa58c2372f681d)
- [VBAで使えるデータ構造とメリット・デメリット - Qiita](https://qiita.com/nukie_53/items/cebe4162e6d9347671e3)
- [VBAのコレクションへの添え字によるアクセスを早くする - Qiita](https://qiita.com/nukie_53/items/bd5338db5e4f332feb11)