Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails以外の開発一般

いろいろな言語の連想配列系リテラル記法を比較してみた

参考: いろいろな言語での Map, Dictionary 的なものの名前 - Qiita

上の記事を見ていて、連想配列系の構文でどんなリテラル記法が使われているのかが気になったので、リテラル記法に絞って順不同(思い付いた順とも言う)で調べてみました。メジャーな言語のほか、新し目の言語もチェックしてみました。

あくまで連想配列系構文の基本的なリテラル記法を知りたかったので、細かな機能やヘルパー関数などについては省略しています。サンプルコードが洗練されてないのはご容赦🙇。

参考: 連想配列 - Wikipedia

map/dict/hash専用のリテラルがある言語とない言語でざっくり分けました。また、メソッドなどを用いるアクセスは最小限にとどめています。検証には主に以下のサイトを使いました。

間違いがありましたら@hachi8833までお知らせください。

専用の生成リテラル記法のある言語
RubyCrystalElixirPythonPerlJavaScriptTypeScriptDartGoV言語SwiftPHPGroovyLua
専用の生成リテラル記法のない言語
JavaKotlinC#RustScalaJuliaOcamlC言語/C++

専用のリテラル記法のある言語

Ruby

参考: class Hash (Ruby 2.7.0 リファレンスマニュアル)

  • 名称: hash
  • キーはシンボル:でも表記できる(現在は主流)
    • 文字列キーとシンボルキーは文字が同じでも等価にならない点に注意
  • 追加したキーバリューの順序がeachなどで保たれる点が特徴
# リテラルによる生成
hash1 = { 'apple' => 'りんご', 'peach' => 'もも' }

# リテラルによる生成(キーがシンボルの場合)
hash2 = { apple: 'りんご', peach: 'もも' }
  • 実行可能なサンプルコード
hash1 = { 'apple' => 'りんご', 'peach' => 'もも' }
hash2 = { apple: 'りんご', peach: 'もも' } 

# 参照
hash1['apple']
a = hash2[:apple]
puts a # 出力用

# 更新
hash2[:apple] = "林檎"
puts hash2 # 出力用

# 追加
hash2[:pear] = "なし"
puts hash2 # 出力用

# 削除
hash2.delete(:pear)
puts hash2 # 出力用

Crystal

参考: Hash · GitBook

CrystalはRubyコンシャスな文法なので、Rubyととてもよく似ていますが、Rubyにない書き方もできます。

  • 名称: hash
  • シンボルをキーにする場合でも=>は省略できない
# リテラルによる生成
hash = { "apple" => "りんご", "peach" => "もも" }
hash2 = { :apple => "りんご", :peach => "もも" }

以下のようにすることでキーや値の型を明示的に指定できるようです。

# キーはStringかSymbol、値はStringかInt32
hash = Hash(String | Symbol, String | Int32){"foo" => "bar", :baz => 1}

以下のようにリテラルを使わずに生成もできます。

hash = Hash(typeof("foo", "bar"), typeof(1, "baz")).new
hash["foo"] = 1
hash["bar"] = "baz"

# 上は以下と同等
Hash{"foo" => 1, "bar" => "baz"}
  • 実行可能なサンプルコード
hash = { :apple => "りんご", :peach => "もも" }

# 参照
a = hash[:apple]
puts a # 出力用

# 更新
hash[:apple] = "林檎"
puts hash # 出力用

# 追加
hash[:pear] = "なし"
puts hash # 出力用

# 削除
hash.delete(:pear)
puts hash # 出力用

Elixir

参考: Keyword lists and maps - Elixir
参考: 急いで覚えるElixir: Enumerable編 - Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

RubyコンシャスといえばElixirもそうみたいですね。map、keyword listとありますが、どちらもdictとして振る舞えます。ここではmapのみを取り上げます。

  • 名称: map(dict)
  • %{}で生成
  • キーに任意の値を使える
  • 破壊的操作はできず、[] =で更新することもできない
  • シンボルをキーにする場合でも=>は省略できない
map = %{ :apple => "りんご", :peach => "もも" }
  • 実行可能なサンプルコード
map = %{ :apple => "りんご", :peach => "もも" }

# 参照
m = map[:apple]
IO.puts m # 出力用

# 参照2
m = Map.get(map, :apple)
IO.puts m # 出力用

# 更新
u = Map.put(map, :apple, "林檎")
IO.inspect u # 出力用

a = Map.put(map, :pear, "なし")
IO.inspect a # 出力用

# 削除
d = Map.delete(map, :pear)
IO.inspect d # 出力用

Python

参考: 組み込み型 — Python 3.8.4rc1 ドキュメント

  • 名称: dict(マッピング型)
# リテラルによる生成
d = {'apple': 'りんご', 'peach': 'もも'}
  • 実行可能なサンプルコード
d = {'apple': 'りんご', 'peach': 'もも'}

# 参照
a = d['apple']
print(a) # 出力用

# 更新
d['apple'] = "林檎"
print(d)

# 更新2(このappleは引用符で囲めない)
d.update(apple="林檎")
print(d) # 出力用

# 追加
d['pear'] = "なし"
print(d) # 出力用

# 削除
del d['pear']
print(d) # 出力用

Perl

  • 名称: hash
  • 生成時のハッシュ変数に%を付けるのが特徴
  • 生成リテラルに()を使う
  • ハッシュ変数へのアクセス時は%ではなく$
# リテラルによる生成
my %h = ('apple' => 'りんご', 'peach' => 'もも');
  • 実行可能なサンプルコード
use strict;
use warnings;
$, = "\n";      # 出力用

my %h = ('apple' => 'りんご', 'peach' => 'もも');

# 参照
$a = $h{'apple'};
print $a, "\n"; # 出力用

# 更新
$h{'apple'} = "林檎";
print %h, "\n"; # 出力用

# 追加
$h{'pear'} = "なし";
print %h, "\n"; # 出力用

# 削除
delete $h{'pear'};
print %h, "\n"; # 出力用

JavaScript

参考: Map - JavaScript | MDN
参考: キー付きコレクション - JavaScript | MDN
参考: Object - JavaScript | MDN
参考: Object.fromEntries()でオブジェクトをmapする - 藤 遥のブログ

昔ながらのObjectと、機能が多く新しいMapがあります。ここではObjectを扱いました。Objectを連想配列と呼んでいいのかどうか判断がつかなかったのですが、知りたいのはリテラルなのでとりあえずここに置きました。

  • 名称: Object(オブジェクトリテラル)
  • キーは「プロパティ」と呼ばれる
  • obj.pear = "なし";のように、存在しないプロパティ(キーに相当)をいきなり書いて追加できるのが特徴
// リテラルによる生成
const obj = {apple: 'りんご', peach: 'もも'};
  • 実行可能なサンプルコード
const obj = {apple: 'りんご', peach: 'もも'};
console.log(obj) // 出力用

// 参照(以下の2つは同じ)
obj['apple'];
obj.apple;

// 更新(以下の2つは同じ)
obj['apple'] ="林檎";
obj.apple = "林檎"
console.log(obj) // 出力用

// 追加(以下の2つは同じ)
obj['pear'] = "なし";
obj.pear = "なし";
console.log(obj) // 出力用

// 削除
delete obj.pear;
console.log(obj) // 出力用

なおMapには生成用のリテラル構文がなく、new Map([ [キー, 値]... ])で生成します。Object.fromEntries()を使うとObjectに変換できます。

const map = new Map([ ['apple', 'りんご'], ['peach', 'もも']]);
const obj = Object.fromEntries(map);

TypeScript

参考: TypeScriptの型: 辞書型を定義する (Dictionary)|まくろぐ

リテラル記法はJavaScriptのを使っていますが、独自の辞書型も定義できるので、ここに置いていいかどうか迷っています(追記: この{ [expr]: value }という記法はECMAScript 2015以降でも使えると教わりました)。

  • 名称: 辞書オブジェクト(ユーザー定義)
  • interfaceを用いて、キーや値の型を指定した辞書型をユーザーが定義できる
  • (JavaScriptのオブジェクトリテラル記法に乗っかった形だが、オブジェクト型とは異なるらしい)
interface UserDictionary {
  [id: string]: string;
}

const dic: UserDictionary = {'apple': "りんご", 'peach': "もも"};
  • 実行可能なサンプルコード
interface UserDictionary {
  [id: string]: string;
}

const dic: UserDictionary = {'apple': "りんご", 'peach': "もも"};
console.log(dic); // 出力用

// 参照1
console.log(dic['apple']);

// 参照2
console.log(dic.apple);

// 更新
dic['apple'] = "林檎";
console.log(dic);  // 出力用

// 追加
dic['pear'] = "なし";
console.log(dic);  // 出力用

// 削除
delete dic['pear'];
console.log(dic);  // 出力用

Dart

参考: コレクション – Dart逆引きリファレンス | Developers.IO
参考: 【Dart】Mapの順番(HashMap, LinkedHashMap, SplayTreeMap) - のんびり精進
参考: Dart 2 Language Guide

Map、LinkedHashMap、HashMap、SplayTreeMapというものがあるそうです。ここではMapのみを取り上げます。

  • 名称: Map
  • JavaScriptのようなドット表記ではアクセスできない
// リテラルによる生成
var a = {'apple': 'りんご', 'peach': 'もも'};

// この場合Map<String, String>と推論される
  • 実行可能なサンプルコード
void main() {
  var a = {'apple': 'りんご', 'peach': 'もも'};

  // 参照
  print(a['apple']);

  // 更新(以下の2つは同じ)
  a['apple'] ="林檎";
  print(a); // 出力用

  // 追加(以下の2つは同じ)
  a['pear'] = "なし";
  print(a); // 出力用

  a.remove('pear');
  print(a); // 出力用
}

Go

参考: Goプログラミング言語仕様 - golang.jp
参考: 逆引きGolang (マップ)

  • 名称: map
// map[]とリテラルによる生成
m := map[string]int{ "apple": "りんご", "peach": "もも" }

// 参考: 型定義のみの場合
var m map[string]int

// 参考: makeにはリテラルを直接書けない
m = make(map[string]int, 20)
  • 実行可能なサンプルコード
package main

import (
    "fmt"
)

func main() {
    m := map[string]string{"apple": "りんご", "peach": "もも"}

    // 参照
    a := m["apple"]
    fmt.Println(a) // 出力用

    // 更新
    m["apple"] = "林檎"
    fmt.Println(m) // 出力用

    // 追加
    m["pear"] = "なし"
    fmt.Println(m) // 出力用

    // 削除
    delete(m, "pear")
    fmt.Println(m) // 出力用
}

V言語

参考: v/docs.md at master · vlang/v

  • 名称: map
    • おそらく現時点は文字列のみをキーにできる
// リテラルによる生成
mut m := {
    'apple': "りんご"
    'peach': "もも"
}

// 参考: 定義(空のmap)
mut m := map[string]int
  • 実行可能なサンプルコード
mut m := {
    'apple': "りんご"
    'peach': "もも"
}

// 参照
a := m['apple']
println(a) //出力用

// 更新
m['apple'] = "林檎"
println(m) // 出力用

// 追加
m['pear'] = "なし"
println(m) // 出力用

// 削除
m.delete('pear')
println(m) // 出力用

Swift

参考: 配列と辞書(連想配列) - Swift開発者Wiki
参考: Swiftの配列とハッシュ(連想配列) - Qiita

配列と連想配列がほぼ同じスタイル。

  • 名称: 辞書(連想配列)
  • nilを代入することで削除する点が特徴。
// リテラルによる生成
var d = [ "apple":"りんご", "peach":"もも" ]
  • 実行可能なサンプルコード
var d = [ "apple":"りんご", "peach":"もも" ]

// 参照
var a = d["apple"]
print(a)

// 更新
d["apple"] = "林檎"
print(d) // 出力用

// 追加
d["pear"] = "なし"
print(d) // 出力用

// 削除
d["pear"] = nil
print(d) // 出力用

PHP

参考: PHP: 配列 - Manual

PHPでは配列と連想配列の区別がないそうです。

  • 名称: 配列(配列の要素に名前を付ける機能)
  • 生成用リテラルに[]を使っている
    • PHP 5.4までは[]によるリテラル記法はなかった
# リテラルによる生成
$a = ['apple' => 'りんご', 'peach' => 'もも'];

# 参考: 昔のarray()による生成
$a = array('apple' => 'りんご', 'peach' => 'もも');
  • 実行可能なサンプルコード
<?php
  $a = ['apple' => 'りんご', 'peach' => 'もも'];

  // 参照
  $b = $a['apple'];
  var_dump($b); // 出力用

  // 更新
  $a['apple'] = "林檎";
  var_dump($a); // 出力用

  // 追加
  $a['pear'] = "なし";
  var_dump($a); // 出力用

  // 削除
  unset($a['pear']);
  var_dump($a); // 出力用
?>

Groovy

参考: 8. マップ - Apache Groovyチュートリアル

  • 名称: map
Map map = [apple:"りんご", peach:"もも"]
  • 実行可能なサンプルコード
class Code {
  static void main(String[] args) {
    Map m = [apple:"りんご", peach:"もも"]
    println(m); // 出力用

    // 参照
    println(m.apple); // 出力用

    // 更新
    m.apple = "林檎";
    println(m); // 出力用

    // 追加
    m["pear"] = "なし";
    println(m); // 出力用

    // 削除
    m.remove("pear");
    println(m); // 出力用
  }
}

Lua

参考: Lua テーブル(Table)

  • 名称: テーブル(配列と同じ扱い)
  • JavaScriptのようにフィールド名をドット.でも指定できる
  • nilを代入することで削除するのが特徴
table = { apple="りんご", peach="もも" }
  • 実行可能なサンプルコード
table = { apple="りんご", peach="もも" }

-- 参照1
table = { apple="りんご", peach="もも" }

-- 参照1
t = table["apple"]
print(t) -- 出力用

-- 参照2
t = table.apple
print(t) -- 出力用

-- 更新
table["apple"] = "林檎"
for key, val in pairs( table ) do -- 出力用
       print ( key, val )
end

-- 追加
table["pear"] = "なし"
for key, val in pairs( table ) do -- 出力用
       print ( key, val )
end

-- 削除
table["pear"] = nil
for key, val in pairs( table ) do -- 出力用
       print ( key, val )
end

専用のリテラル記法のない言語

Java

参考: 【HashMap】Javaで連想配列を扱う!サンプルつき

専用のリテラル構文はありません(HashMapなどのライブラリを用いる)。ここではHashMapのみをチェック。

  • 名称: HashMap
import java.util.HashMap;
public class Main {
    public static void main(String[] args) {
        HashMap<String, String> map;
        {
                map = new HashMap<String, String>();
                // 追加
                map.put("apple", "りんご");
                map.put("peach", "もも");

                // 参照
                System.out.println(map.get("apple"));

                // 更新
                map.put("apple", "林檎");
                System.out.println(map.get("apple")); // 出力用

                // 追加
                map.put("pear", "なし");
                System.out.println(map.get("pear")); // 出力用

                // 削除
                map.remove("pear", "なし");
                System.out.println(map.get("pear")); // 出力用
        }
    }
}

Kotlin

参考: KotlinとMap - Qiita

気になってKotlinも調べたら、[]というindexing operator構文が追加されていましたが、mapの生成リテラルはないようです。ここではhashMapOf()のみ調べています。

  • 名称: map(mutableとreadOnly)
  • []でのアクセスが推奨
    • get()put()set()なども使えるが非推奨
fun main() {
    // hashMapOfによる生成
    val id = "apple"
    val name = "りんご"
    val hashmap = hashMapOf(id to name)
    println(hashmap) // 出力用

    // 追加
    hashmap["peach"] = "もも"
    println(hashmap) // 出力用

    // 更新
    hashmap["apple"] = "林檎"
    println(hashmap) // 出力用

    // 追加
    hashmap["pear"] = "なし"
    println(hashmap) // 出力用

    // 削除
    hashmap.remove("pear")
    println(hashmap) // 出力用
}

C sharp

参考: ハッシュテーブル(連想配列)を使うには?[C#/VB、.NET 全バージョン]:.NET TIPS - @IT

  • 名称: ハッシュテーブル
using System;
using System.Collections;

public class test {
  static void Main() {
    Hashtable ht = new Hashtable();

    // 追加
    ht["apple"] = "りんご";
    ht.Add("peach","もも"); // Add()でも追加できる
    foreach (DictionaryEntry de in ht) { // 出力用
      Console.WriteLine("{0} : {1}", de.Key, de.Value);
    }

    // 更新
    ht["apple"] = "林檎";
    foreach (DictionaryEntry de in ht) { // 出力用
      Console.WriteLine("{0} : {1}", de.Key, de.Value);
    }

    // 追加
    ht["pear"] = "なし";
    foreach (DictionaryEntry de in ht) { // 出力用
      Console.WriteLine("{0} : {1}", de.Key, de.Value);
    }    

    // 削除
    ht.Remove("pear");
    foreach (DictionaryEntry de in ht) { // 出力用
      Console.WriteLine("{0} : {1}", de.Key, de.Value);
    }
  }
}

追記(2020/08/03): 以下の情報ありがとうございます🙇。

C#のHashtableは.net framework2.0 (15年前)から非推奨では。System.Collections.Generic.Dictionaryはリテラルというか専用の初期化構文がある - rkchy のブックマーク / はてなブックマーク

参考: Dictionary<TKey,TValue> クラス (System.Collections.Generic) | Microsoft Docs

Rust

参考: ハッシュマップ - The Rust Programming Language

専用の構文はなく、HashMapライブラリを用います。

  • 名称: HashMap
  • ハッシュのアルゴリズムを変更できる
  • 値の更新にinsert()を使うのが特徴
use std::collections::HashMap;

fn main() {
    // HashMap::new()による生成
    let mut map: HashMap<String, String> = HashMap::new();

    // 追加
    map.insert(String::from("apple"), String::from("りんご"));
    map.insert(String::from("peach"), String::from("もも"));
    println!("{:?}", map);  // 出力用

    // 参照
    let apple = String::from("apple");
    let name = map.get(&apple);
    println!("{:?}", name); // 出力用

    // 更新
    map.insert(String::from("apple"), String::from("林檎"));
    println!("{:?}", map); // 出力用

    // 追加(entry()とor_insert()を使用する場合)
    map.entry(String::from("pear")).or_insert(String::from("なし"));
    println!("{:?}", map); // 出力用

    // 削除
    map.remove("pear");
    println!("{:?}", map);  // 出力用
}

追記(2020/08/03): もっといい書き方があるそうです。ありがとうございます!

Scala

参考: Scala Mapメモ(Hishidama's Scala Map Memo)

Scalaをどちらに分類するか迷いましたが、連想配列のためだけのリテラルというわけではなさそうだったのでここに置きました。

  • 名称: Map(連想配列)
  • ->はタプルを作るメソッド
  • +で更新または追加、-で削除する点が特徴
// リテラルによる生成
var a = Map[String, String]( "apple"->"りんご", "peach"->"もも" )
  • 実行可能なサンプルコード
object Main {
  def main(args: Array[String]): Unit = {
    var a = Map[String, String]( "apple"->"りんご", "peach"->"もも" )

    // 参照
    var d = a("apple")
    println(a); // 出力用

    // 更新
    a = a + ("apple"->"林檎");
    println(a) // 出力用

    // 追加
    a = a + ("pear"->"なし");
    println(a); // 出力用

    // 削除
    a = a - ("pear")
    println(a); // 出力用
  }
}

Julia

参考: 実例で学ぶJuliaプログラミング言語入門
参考: Learn Julia in Y Minutes
参考: Collections and Data Structures · The Julia Language
参考: Julia入門 辞書(ハッシュテーブル)、Set型について - 0x00 nullbyte blog

  • 名称: Dict(Associative Collection)
d = Dict("apple"=>"りんご", "peach"=>"もも")

# 型も指定できる
d = Dict{String, String}("apple"=>"りんご", "peach"=>"もも")

# 以前は以下のリテラルが使えたが、0.4以降でdeprecateされた
d = { "apple"=>"りんご", "peach"=>"もも" }
d = [ "apple"=>"りんご", "peach"=>"もも" ]
  • 実行可能なサンプルコード
d = Dict("apple"=>"りんご", "peach"=>"もも")

# 参照
println(d["apple"])

# 更新
d["apple"] = "林檎"
println(d)

# 追加
d["pear"] = "なし"
println(d)

# 削除
delete!(d, "pear")
println(d)

OCaml

参考: Hashtbl

  • 名称: ハッシュテーブル
let hash = Hashtbl.create 5;;
Hashtbl.add hash "apple"  "りんご";;
Hashtbl.add hash "peach"  "もも";;
  • 実行可能なサンプルコード
let hash = Hashtbl.create 5;;
Hashtbl.add hash "apple"  "りんご";;
Hashtbl.add hash "peach"  "もも";;

(*参照*)
print_endline ((Hashtbl.find hash "apple"));; (*出力用*)
print_endline ((Hashtbl.find hash "peach"));; (*出力用*)

(*更新*)
Hashtbl.replace hash "apple"  "林檎";;
Hashtbl.iter (fun x y -> Printf.printf "%s -> %s\n" x y) hash;; (*出力用*)

(*追加*)
Hashtbl.add hash "pear"  "なし";;
Hashtbl.iter (fun x y -> Printf.printf "%s -> %s\n" x y) hash;; (*出力用*)

(*削除*)
Hashtbl.remove hash "pear";;
Hashtbl.iter (fun x y -> Printf.printf "%s -> %s\n" x y) hash;; (*出力用*)

C言語/C++

参考: GLib のハッシュテーブルを使う | Linux 修験道
参考: C 言語のハッシュテーブル! | Linux 修験道
参考: C/C++ で使える Hashtable - いけむランド
参考: C++ ハッシュ連想配列クラス unordered_map 入門

専用の構文はなく、定番のライブラリもひとつというわけではなさそうです。長くなりそうなのでサンプルコードは作りませんでした。ネットではハッシュテーブルを手作りする記事が多く見られました。

  • 名称: 「ハッシュテーブル」と呼ばれることが多い
  • 手作り、またはライブラリ次第

と思ったら、C++11以降ではstd::unordered_mapが使えると教わりました。後で追加してみます。

参考: C++ ハッシュ連想配列クラス unordered_map 入門


追記(2020/08/03): C++でやってみた方がいらっしゃいました。ありがとうございます!

調べてみて

専用のリテラル構文を持つ言語では、{}を生成用リテラルとし、[]でアクセスするものが多く、記法としてはこれが一応多数派なのかなと思えます。Perlでは()、PHPとSwiftでは[]を生成用リテラルに用いていることを知りましたが、Swiftのような新しい言語でも[]で生成しているのを見ると、それはそれで筋が通っていそうな気がしました。

キーバリューをつなぐ記号も、=>->:=などさまざまでした。

ばらつきが最も大きいのは、キーバリューの削除方法のようです。メソッドで削除する言語、削除用キーワードを使う言語、nil代入で削除する言語があり、語もさまざまです。

使う側としては、やはりRubyやPythonのようなシンプルな生成用リテラルがある方が断然楽です。RustのHashMapでキーバリューをいくつも書くのはかなり大変そう...

一方専用のリテラル構文を持たない言語は、おそらくパフォーマンス上最適なハッシュ関数を用途に応じて選びたいという要求から、あえてリテラルを固定しない傾向があるのかもしれないと思えました(あくまで推測です)。このタイプではScalaが比較的書きやすそうなリテラルだと思いました(個人の感想です)。

実際、以下のようにハッシュ関数にはいろいろなものがあり、特性もさまざまです。

参考: ハッシュ関数 - Wikipedia

Juliaが当初採用していた生成用リテラルを後にdeprecateしたというあたり、考えさせられました。

個人的には、TypeScriptのユーザー定義の辞書型が既存のJavaScriptとぶつからないよう割とうまく切り抜けていると思いました。

ざっくりまとめ

生成リテラル

{}
RubyCrystalPythonJavaScriptTypeScriptDartV言語Lua
%{}
Elixir
()
Perl
[]
SwiftPHPGroovy
map[型]型{キーバリュー}
Go
Map[型, 型](キーバリュー)
Scala

キーとバリューをつなげる記号

=>
RubyCrystalElixirPerlPHPJulia
:
PythonJavaScriptTypeScriptDartGoV言語SwiftGroovy
=
Lua
->
Scala

アクセス記号

[]
RubyCrystalElixirPythonJavaScriptTypeScriptDartGoV言語SwiftPHPGroovyLuaKotlinC#Julia
{}
Perl

おたより発掘


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。