JavaScriptのArrayとLuaのtableの比較


はじめに

Qiitaに下書きが多すぎて新しい記事作れませんと言われたので途中まで書いて放っておいた記事をとりあえず投稿して下書きを減らします、すみません。


JavaScript の Array は「0以上の整数のキーを特別扱いするコレクションオブジェクト」と捉えると
Lua の テーブルに結構似ています。(テーブルは1以上ですが)
(実際にはArrayオブジェクトはObjectを継承しているものの、独自にプロパティを
 設定することは推奨されていない気がします)

  • JavaScriptのArrayとLua(5.3)のテーブルの比較
  • Arrayにあってテーブルにないものの中で便利そうなのはLuaでも実装してみる

をやっていこうと思います。

  • JavaScriptは下記参照先の表記優先(その他、ブラウザやVScodeのデバッグ)
  • Luaは lua.exe (lua-5.3.5_Win64_bin)にて確認
  • Luaはやっとメタテーブルをなんとなく理解してきたレベル
  • JavaScriptは調べながらじゃないとFizzBuzzも怪しいレベル

Array - Javascript|MDN
Lua 5.3 リファレンスマニュアル

作成と(簡易)文字列出力

JavaScript
var fruits = ["りんご","バナナ"];
console.log(fruits);
// ["りんご","バナナ"] <- 実際には先頭に Array(2) とか [Object Array](2) とかが入る
Lua5.3
fruits = {"りんご","バナナ"}
print(fruits)
--> table: 0000000000d3a390

Luaのテーブル型は参照なので識別子を型名とともに出力しています。(環境によっては4byte表記)
これはまあそうなのですが、ちょっと中身を確認したい時がありますので、
文字列変換を実装(とりえあず多次元テーブルはスルー)
後、テーブル用標準ライブラリ(table テーブルに格納されている)をメソッドっぽく
使用できるようにメタテーブルを利用します。

Lua5.3
table.of=function(...)
  return setmetatable({...},{
    __index=table,
    __tostring=function(t)
      return ("{%s}"):format(t:concat(", "))
    end
  })
end

fruits=table.of("りんご","バナナ")
print(fruits)
--> {りんご, バナナ}

デフォルトの参照先を設定する、prototype と メタテーブルは似ているけど、

長さ(要素数)を取得する

JavaScript
console.log(fruits.length);
// 2
Lua5.3
print(#fruits)
--> 2

プロパティで持っているJavaScript、長さ演算子#があるLua。どちらもめずらしい部類でしょうか?
添え字に抜けがある場合に返す値に関しては異なりますが、とりあえずスルーで。
個人的にarray.lenght=0とするとArray部分をクリアできるのはうらやましいのでclearとして追加。

Lua
table.clear=function(t)
  for n=1,#t do t[n]=nil end
end

要素を取得する

JavaScript
var first = fruits[0]
// りんご

var last = fruits[fruits.length - 1];
// バナナ
Lua5.3
first = fruits[1]
--> りんご

last = fruits[#fruits]
--> バナナ

自動で割り振ったり、要素数算出のために利用する順番号(添え字)が
javascriptでは0から開始なのに対し、Luaでは1から開始

番号割り振り(numbering)を数え上げ(counting)に寄せた Lua(1オリジン)と、
基準とそこからの相対値(offset)に寄せた javascript(0オリジン) の違い。

プログラミング初心者にとっては1オリジンの方が自然に感じますが、
他のプログラミング言語では0オリジンの方が多数派のため混乱の元となっているよう。
(C言語の配列がアドレス算出に都合がよいために 0 オリジンを採用し、
 それに倣った言語が多かったという説が有力のようです)

ループ処理

JavaScript
fruits.forEach(function(item, index, array) {
  console.log(item, index);
});
// りんご 0
// バナナ 1
Lua5.3
for index,item in ipairs(fruits) do
  print(item,index)
end
--> りんご 1
--> バナナ 2

末尾に要素を追加する

JavaScript
var newLength = fruits.push("みかん");
// ["りんご", "バナナ", "みかん"]
Lua5.3
fruits:insert("みかん")
--> {りんご, バナナ, みかん}

pushメソッドは末尾に要素を追加し、追加後の要素数を返す

table.insert 関数は何も返さない

末尾の要素を削除する

JavaScript
var last = fruits.pop(); // 配列の末尾の要素 "みかん" を削除
// ["りんご", "バナナ"];
console.log(last);
// みかん
Lua5.3
last = fruits:remove()
--> {りんご, バナナ}
print(last)
--> みかん

先頭の要素を削除する

JavaScript
var first = fruits.shift(); // 配列の末尾の要素 "みかん" を削除
// ["バナナ"];
console.log(first);
// りんご
Lua5.3
first = fruits:remove(1)
--> {バナナ}
print(first)
--> りんご

先頭に要素を追加する

JavaScript
var newLength = fruits.unshift("いちご") // 配列の先頭に追加
// ["いちご", "バナナ"];
Lua5.3
fruits:insert(1,"いちご")
print(fruits)
--> {いちご, バナナ}

fruits:insert("いちご",1)ではないことに注意

要素のインデックスを取得する

JavaScript
fruits.push("マンゴー");
// ["いちご", "バナナ", "マンゴー"]

var pos = fruits.indexOf("バナナ");
// 1
Lua5.3
table.indexOf=function(tbl,val)
  for n,v in ipairs(tbl)do
    if v==val then return n end
  end
  return false
end

fruits:insert("マンゴー")
--> {いちご, バナナ, マンゴー}
pos = fruits:indexOf("バナナ")
--> 2

事前に{ [val1]={key1,key2,..},[val2]={key}..}みたいなテーブルを作成しておくのも有り(?)

インデックスから複数の要素を削除する

JavaScript
var vegetables = ['Cabbage', 'Turnip', 'Radish', 'Carrot'];
console.log(vegetables); 
// ["Cabbage", "Turnip", "Radish", "Carrot"]

var pos = 1, n = 2;

var removedItems = vegetables.splice(pos, n); 
// 複数の要素を削除するには、n で削除する要素数を定義します
// 指定位置(pos)以降から n 個分の要素が削除されます

console.log(vegetables); 
// ["Cabbage", "Carrot"] (元の配列が変化)

console.log(removedItems); 
// ["Turnip", "Radish"]
Lua5.3
table.splice=function(t,pos,n)
  local rmvd=table.of()
  for nums=1,n do
    rmvd:insert(t:remove(pos))
  end
  return rmvd
end

vegetables = table.of('Cabbage', 'Turnip', 'Radish', 'Carrot')
print(vegetables)
--> {Cabbage, Turnip, Radish, Carrot}

pos = 2; n = 2 -- posは 2番目(Turnip)を指定

removedItems = vegetables:splice(pos, n)
print(vegetables)
--> {Cabbage, Carrot}

print(removedItems);
--> {Turnip, Radish}

こんなメソッドがあるんだなあ。
何個取り出すというより、同じ場所から指定回数取り出す、という実装にしたけど遅そうですね。

いつか続きを書くかも知れません。