[AutoHotKey]インデックスが連番でない配列のinsert・removeについて


リストビューでアイテムの複数選択を実装しようとしていて詰まったポイント。

## `insert`メソッドの挙動
`insert`メソッドで配列オブジェクトに値を格納するには、例えば以下のようなコードになる。

```sample.ahk
Array := object()
Array.insert("hoge")
Array.insert("fuga")
Array.insert("foo")
Array.insert("bar")
```

配列のキーを省略した場合、整数値のインデックスで最大値のもの+1がインデックスとなる。既存の値がない場合は1になる。
つまり、↑のコードは↓と同じ。

```sample.ahk
Array := object()
Array.insert(1,"hoge")
Array.insert(2,"fuga")
Array.insert(3,"foo")
Array.insert(4,"bar")
```

ところで、配列のインデックスは連番である必要はない。飛び飛びの数値も取る事ができる。
たとえば↓

```sample.ahk
Array := object()
Array.insert(1,"hoge")
Array.insert(3,"fuga")
Array.insert(7,"foo")
Array.insert(15,"bar")
```

ここまではなんの問題もない。

ここで、既存のインデックス以下の数値のインデックスに値を格納するとき、注意が必要になる。

```sample.ahk
Array := object()
Array.insert(15,"bar")
Array.insert(3,"fuga")
Array.insert(1,"hoge")
Array.insert(7,"foo")
```

↑のコードで、配列を書き下ろすと↓のようになる。

```
 1, hoge
 4, fuga
 7, foo
18, bar
```

つまり、手前に格納した分、インデックスがずれている。
（参照：[Objectオブジェクト \- AutoHotkey Wiki#註釈](http://ahkwiki.net/Object_Objects#.E8.A8.BB.E9.87.88)）

さらにややこしいのが、ずれた結果インデックスが等数値になる場合。

```sample.ahk
Array := object()
Array.insert(5,"foo")
Array.insert(3,"fuga")
Array.insert(1,"hoge")
Array.insert(8,"bar")
```

↑コードだと、配列は↓。

```sample.ahk
1, hoge
4, fuga
7, foo
8, bar
```

このように、**空いている数値までずれる**、といった挙動になる。

## `remove`メソッドでの問題
冒頭で書いたように、今回はリストビューで複数選択を実装しようとしていた。

どうも、マウスクリックなりのイベント発生時に「選択されている行のすべて」を取得することはできないらしい。
なので、「選択したとき`insert`」「選択解除したとき`remove`」という形で処理しようとした。
1列のリストビューなので、あとで`remove`できるように行番号をインデックスにしようとした。
しかし、このケースだと、前節で書いたようなインデックスのずれが発生してしまい、行番号を指定して`remove`することができなくなってしまう。

つまり、以下のコードは期待通り動作しない。インデックス`3`には値が格納されていないので。

```sample.ahk
Array := object()
Array.insert(5,"foo")
Array.insert(3,"fuga")
Array.insert(1,"hoge")
Array.insert(8,"bar")

Array.remove(3)
```

どうすれバインダー。

## 「数値」ではなく「数字」の連想配列
「数値」のインデックスだからずれるのであって、「数字」つまり文字列のキーとして扱えばよくね？

```sample.ahk
Array := object()
Array.insert("5","foo")
Array.insert("3","fuga")
Array.insert("1","hoge")
Array.insert("8","bar")
```

```
1, hoge
3, fuga
5, foo
8, bar
```

このように、ずれることなく格納できる。
その代わり、キーがかぶると上書きされてしまうのだが、この場合キーはユニークな行番号なので問題はない。

当然、`remove`メソッドも文字列キーに対して行う必要がある。

```sample.ahk
Array := object()
Array.insert("5","foo")
Array.insert("3","fuga")
Array.insert("1","hoge")
Array.insert("8","bar")

Array.remove("3")
```

## で、`A_EventInfo`変数は数値なの、文字列なの？
リストビューからアイテムの値を取得するには、`LV_GetText()`関数を用いる。
こんな感じ。

```sample.ahk
; 前略
If (A_GuiEvent = "I" and InStr(ErrorLevel, "S", true))
{
    LV_GetText(RowText, A_EventInfo)
    Selection.insert(A_EventInfo, RowText)
    Return
}
If (A_GuiEvent = "I" and InStr(ErrorLevel, "s", true))
{
    Selection.remove(A_EventInfo)
    Return
}
; 後略
```

で、この`A_EventInfo`変数に行番号が格納されるのだが、数のみが格納された組み込み変数は数値扱いになる。
変数の内容を直書きできる場合は普通にクォートすれば文字列扱いになるのだが、組み込み変数の場合はそうもいかない。

```sample.ahk
var = "2" ; クォートしているので「数値」ではなく「文字列」
var += 1  ; 加算。「数値」としては空なので……

MsgBox, , , %var% ; 「1」と表示される
```

つまりこの場合、`A_EventInfo`変数は数値なので、`Selection`のインデックスがずれてしまう。
じゃあどうするかというと、文字くっつけて文字列にしてしまうしかない。

```ListViewSelection.ahk
; 前略
If (A_GuiEvent = "I" and InStr(ErrorLevel, "S", true))
{
    LV_GetText(RowText, A_EventInfo)
    Selection.insert("r" . A_EventInfo, RowText)
    Return
}
If (A_GuiEvent = "I" and InStr(ErrorLevel, "s", true))
{
    Selection.remove("r" . A_EventInfo)
    Return
}
; 後略
```

さいわい、文字列をキーとしても配列はキー降順で整列してくれるので、あとでループ処理などする場合も特に問題はない。

なお、キーから文字部分を削除して数値のみを再格納すれば、改めて数値でインデックスされた配列として扱うこともできる。
けっこういろいろやりようがある。

```ListViewSelection.ahk
; 前略
; 文字列キーを数値インデックスに直す
for index, element in Selection
{
    if (A_Index = 1) ; 1ループ目のみ配列を初期化
        Array := Object()
    StringTrimLeft, index, index, 1 ; 文字"r"を削除
    Selection.insert(index, element)
}
; 後略
```

```ListViewSelection.ahk
; 前略
; 文字列キーを数値インデックスに直す
for index, element in Selection
{
    ; キーごとに削除と再格納を行う
    Selection.remove(index)
    StringTrimLeft, index, index, 1 ; 文字"r"を削除
    Selection.insert(index, element)
}
; 後略
```

```ListViewSelection.ahk
; 前略
; 文字列キーを数値インデックスに直す
for index, element in Selection
{
    ; キーごとに削除と再格納を行う
    Selection.remove(index)
    StringTrimLeft, index, index, 1 ; 文字"r"を削除
    Selection.insert(A_Index, element) ; 連番インデックスに直す
}
; 後略
```