🍻

連想配列

2023/08/06に公開

はじめに

夏バテもあり、面接だったりの緊張感が続いていて、最近食欲が全然湧かないです😹
でも、本当に行きたいと思う会社に出会えてから、やる気倍増中です!
結果はまだわからないけれど、今できることを悔いのないよう、最大限頑張ろう!

連想配列についてもっと学んでいきます!

連想配列とは

連想配列(マップ、ハッシュマップ、ディクショナリとも呼ばれる)は、
一連の一意のキーとそれに対応する値からなるデータ構造です。

キーを使用して特定の値に直接アクセスできるため、
データの検索にとって非常に効率的なデータ構造です!

この概念は多くのプログラミング言語に存在し、それぞれ異なる形で実装されています👀

例えば、Pythonではディクショナリとして、JavaScriptではオブジェクトやMapとして、
RubyではHashとして、JavaではHashMapとして、C++ではstd::mapとして実装されています。

ただの配列
  • 配列は、オブジェクトの順序付けられたリスト。
  • 各要素は特定の順序(インデックス)で格納され、通常は0から始まる整数で参照されます!
javascript
let fruits = ['apple', 'banana', 'cherry'];
console.log(fruits[0]);  // "apple" を出力
ruby
fruits = ['apple', 'banana', 'cherry']
puts fruits[0]  # "apple" を出力
  • 配列は、順序に関連性がある一連の値を扱うのに適している(例:商品のリスト)
  • 連想配列は、名前付きのプロパティやキーを持つ値を扱うのに適している(例:商品の詳細情報)

連想配列の基本的な操作には次のようなものがあります!

操作
挿入 キーと値のペアを連想配列に追加
削除 指定したキーとそれに対応する値を連想配列から削除
検索 指定したキーに対応する値を連想配列から探す
更新 指定したキーに対応する値を新しい値に更新
javascript
// オブジェクトの作成
let fruitColors = {
    'apple': 'red',
    'banana': 'yellow',
    'grape': 'purple'
};

// 要素の追加
fruitColors['orange'] = 'orange';

// 要素の削除
delete fruitColors['apple'];

// 要素の検索
console.log(fruitColors['banana']);  // Output: "yellow"

// 要素の更新
fruitColors['banana'] = 'green';
javascript
// Mapの作成
let fruitColors = new Map();
fruitColors.set('apple', 'red');
fruitColors.set('banana', 'yellow');
fruitColors.set('grape', 'purple');

// 要素の追加
fruitColors.set('orange', 'orange');

// 要素の削除
fruitColors.delete('apple');

// 要素の検索
console.log(fruitColors.get('banana'));  // Output: "yellow"

// 要素の更新
fruitColors.set('banana', 'green');

どちらの方法を使用するかは、状況や目的によります!
Mapはキーとして任意の型を持つことができ、挿入順序を保持するなどの特性がありますが、JSONへのシリアライズなど一部の機能はオブジェクトの方が扱いやすいことがあります🤔

Rubyだと。。

ruby
# ハッシュの作成
fruit_colors = {'apple' => 'red', 'banana' => 'yellow', 'grape' => 'purple'}

# 要素の追加
fruit_colors['orange'] = 'orange'

# 要素の削除
fruit_colors.delete('apple')

# 要素の検索
puts fruit_colors['banana']  # Output: "yellow"

# 要素の更新
fruit_colors['banana'] = 'green'

Rubyのハッシュを用いて連想配列のような動作を行うためのものです。
キーと値のペアを追加、削除、検索、更新するためのメソッドが用意されています!

2重の連想配列

(またはネストされた連想配列、多次元連想配列とも呼ばれる)

連想配列の各値がさらに別の連想配列であるデータ構造を指します。
より複雑なデータを管理することができます。

例えば、JavaScriptでネストされたオブジェクトを作成すると次のようになります!

javascript
let obj = {
    "John": {
        "age": 30,
        "city": "New York"
    },
    "Jane": {
        "age": 40,
        "city": "Chicago"
    }
};

この場合、"John"と"Jane"というキーそれぞれに対応する値が、
さらに別のオブジェクトとなっています。

このような構造を用いることで、各人物に対して年齢や居住都市など、
複数の属性を保持することが可能になります!

javascript
// 値を取り出す
console.log(obj["John"]["age"]);  // Output: 30

// 値を更新する
obj["John"]["age"] = 31;
console.log(obj["John"]["age"]);  // Output: 31

2重の連想配列は、リレーショナルデータベースのテーブルやJSONデータなど、
実際のデータ構造を表現するのに便利な形式となっています。

この概念は、更に深くネストされたデータ構造にも拡張可能です!

配列操作メソッド

JavaScriptの配列は様々なメソッドを持っており、それぞれが配列操作に対する特定のタスクを実行します!
いくつかの主要なメソッドを以下に紹介します!

push()

push()メソッドは新しい要素を配列の最後に追加します。このメソッドは新しい配列の長さを返します。

javascript
let fruits = ['apple', 'banana'];
fruits.push('cherry');
console.log(fruits);  // ["apple", "banana", "cherry"] を出力

sort()

sort()メソッドは配列の要素をソートします。
デフォルトでは、要素は文字列として、その文字列のシーケンスに従ってソートされます。

javascript
let fruits = ['banana', 'apple', 'cherry'];
fruits.sort();
console.log(fruits);  // ["apple", "banana", "cherry"] を出力
比較関数

sort()メソッドでは、比較関数は次の形式を持ちます!

javascript
// 未ソートの配列を定義
let array = [5, 1, 4, 2, 3];
console.log(array);  // Output: [5, 1, 4, 2, 3]

// 比較関数を定義
function compare(a, b) {
  if (a < b) {
    return -1;
  }
  if (a > b) {
    return 1;
  }
  // a must be equal to b
  return 0;
}

この関数をsort()メソッドに渡すと、sort()は配列の各要素ペアに対してこの関数を呼び出します!関数の返り値に基づいて、sort()は要素を並び替えます。

  • a < bの場合、関数は-1を返し、その結果、aはbよりも前に配置されます。
  • a > bの場合、関数は1を返し、その結果、aはbよりも後に配置されます。
  • aとbが等しい場合、関数は0を返し、その結果、aとbの順序はそのままです。

例えば、数値の配列を昇順にソートするには以下のようにします。

javascript
// この比較関数を使って配列をソート
array.sort(compare);
console.log(array);  // Output: [1, 2, 3, 4, 5]

sort()関数は、配列のすべての要素ペア(つまり、5と1、1と4、4と2、2と3など)に対してcompare関数を呼び出します!compare関数の返り値(-1、0、または1)に基づいて、sort()関数はそれらの要素がソートされた配列内でどのように配置されるべきかを決定します。

reduce()

reduce()メソッドは配列の全要素に対して指定したリデューサ関数を実行し、単一の出力値を生成します。リデューサ関数は4つの引数を取ります!(累積値、現在の値、現在のインデックス、元の配列)

javascript
let numbers = [1, 2, 3, 4, 5];
let sum = numbers.reduce(function(accumulator, currentValue) {
  return accumulator + currentValue;
}, 0); // 0 は accumulator の初期値

console.log(sum); // 15 を出力
上記解説
  • reduce()メソッドはnumbers配列のすべての要素を合計しています。
  • reduce()メソッドが呼び出されると、累積値accumulatorは初期値の0から始まり、各要素currentValueが累積値に加算され、その結果が新たな累積値となります。
  • 全ての配列要素を処理した後、reduce()は累積した合計値を返します。
もう少し詳しく!
  • reduce()メソッドは配列の各要素に対して関数(リデューサー)を適用し、その結果を単一の値に累積します。
  • 配列の要素を"減少"または"リデュース"して、一つの結果を得るという意味で"reduce"と呼ばれています。

リデューサー関数は4つの引数をとります

  • accumulator(累積値)
    これはリデューサーの返り値の累積値です。つまり、リデューサーの前回の呼び出しから返された値です。

  • currentValue(現在の値)
    処理している配列の現在の要素です。

  • currentIndex(現在のインデックス)
    処理している配列の現在の要素のインデックスです。
    これはオプションで、省略することも可能です!

  • array(元の配列)
    reduce()を呼び出した元の配列です。これもオプションです!

文字列の配列を一つの文字列に結合する使い方
javascript
let words = ['Reduce', 'is', 'really', 'useful!'];
let sentence = words.reduce(function(accumulator, currentValue) {
  return accumulator + ' ' + currentValue;
}, '');  // 空文字列はaccumulatorの初期値

console.log(sentence);  // "Reduce is really useful!" を出力
  • この例では、配列の全ての要素(文字列)が結合されて一つの大きな文字列が作られます。
  • リデューサー関数の中では、accumulatorとcurrentValueが連結されて、その結果が新しいaccumulatorとして次のイテレーションに引き継がれます。

map()

map()メソッドは配列の全要素に対して指定した関数を実行し、その結果から新しい配列を生成します。

javascript
let numbers = [1, 2, 3, 4];
let squares = numbers.map(function(number) {
  return number * number;
});
console.log(squares);  // [1, 4, 9, 16] を出力

連想配列の要素をループで取り出す

for...inループ

for...inループがオブジェクトの各プロパティ(name、age、city)を
keyとして一つずつ取り出し、そのkeyに対応する値はperson[key]という形式でアクセスしています。

javascript
let person = {
  name: "John",
  age: 30,
  city: "New York"
};

for (let key in person) {
  console.log("Key: " + key + ", Value: " + person[key]);
  // 上記の行は各キーと値を次の形式でコンソールに出力します:
  // "Key: name, Value: John"
  // "Key: age, Value: 30"
  // "Key: city, Value: New York"
}

Object.entries()

このObject.entries()メソッドがオブジェクトから[キー, 値]のペアを表す配列を生成し、それをfor...ofループで一つずつ取り出しています。

for...ofループの左辺に配列の構造分解を使用することで、配列の各要素(この場合、[キー, 値])を個別の変数keyとvalueに分割しています。

javascript
let person = {
  name: "John",
  age: 30,
  city: "New York"
};

for (let [key, value] of Object.entries(person)) {
  console.log(`Key: ${key}, Value: ${value}`);
  // 上記の行は各キーと値を次の形式でコンソールに出力します:
  // "Key: name, Value: John"
  // "Key: age, Value: 30"
  // "Key: city, Value: New York"
}
for...inループとfor...ofループの違い

for...inループ

オブジェクトの列挙可能なプロパティに対して反復処理を行います。
つまり、オブジェクトのすべてのプロパティ(自身のプロパティだけでなく、プロトタイプチェーンから継承したプロパティも含む)に対してループを行います。
そのため、主にオブジェクトのプロパティを走査するときに使用されます。

const object = { a: 1, b: 2, c: 3 };

for (const property in object) {
  console.log(`${property}: ${object[property]}`);
}
// 出力:
// "a: 1"
// "b: 2"
// "c: 3"

for...ofループ

Iterableオブジェクト(配列、マップ、セット、argumentsオブジェクト、文字列など)の要素に対して反復処理を行います。
そのため、主に配列やその他の反復可能なオブジェクトの要素を走査するときに使用されます。

通常のオブジェクト(連想配列)には直接使用できず、そのためにはObject.keys()Object.values()Object.entries()のいずれかを使用してオブジェクトのキー、値、またはキーと値のペアを配列として取得する必要があります🤔

const array = ['a', 'b', 'c'];

for (const element of array) {
  console.log(element);
}
// 出力:
// "a"
// "b"
// "c"

まとめ

for...inループはオブジェクトのキー(プロパティ名)を取得するのに適していますが、配列に使用すると予期しない結果を引き起こすことがあります(配列はオブジェクトの一種であり、継承されたプロパティなどもループ対象になりえます)。

一方、for...ofループは配列やその他のイテラブルなオブジェクトの値を取得するのに適しています。

参考にさせていただいた記事

https://developer.mozilla.org/ja/docs/Learn/JavaScript/First_steps/Arrays

https://qiita.com/kentarok/items/5d38c3f7df37a7396ef9

https://code-database.com/knowledges/32

Discussion