順番に読み進めながら学べます

並べ替えと検索 — sort / find / some / every

配列を並べ替える sort と、条件に合う要素を探す find を扱います。比較関数の戻り値で決まる並び順、元の配列を残す toSorted、真偽値だけを返す some と every まで確かめます。

一覧を並べ替えるたびに for で要素の位置を入れ替える処理を書くと、比べ方と入れ替えの手順を毎回自分で管理することになります。条件に合う 1 件を探すときも、forbreak を組み合わせて、見つかった時点で抜ける形が残ります。

この記事では、並べ替える sort と、条件に合う要素を探す find を扱います。

並び順を関数で指定する — sort と比較関数

商品の一覧を価格の安い順に並べ替えて画面に出したいとします。並べ替えは配列のメソッドで行えますが、products.sort() とだけ書いても価格の順にはなりません。何を基準に比べるかを渡していないためです。

sort(配列の要素を並べ替えるメソッド)には 比較関数(2 つの要素を受け取って数値を返す関数)を渡します。受け取る 2 件を順に ab と呼ぶと、返った数値が負なら a、正なら b を前に置きます。a.price - b.price と書けば価格の昇順です。

const products = [
  { name: "ワイヤレスマウス", price: 2980 },
  { name: "モバイルバッテリー", price: 4380 },
  { name: "USB-C ケーブル", price: 980 },
  { name: "マウスパッド", price: 2980 },
];

// 引き算の結果が負なら a が前、正なら b が前に並ぶ
products.sort((a, b) => a.price - b.price);
console.log(products.map((item) => item.price).join(", "));  // 980, 2980, 2980, 4380

// 価格が同じ 2 件の前後を確かめる
console.log(products[1].name, products[2].name);             // ワイヤレスマウス マウスパッド

// 引く向きを入れ替えると降順になる
products.sort((a, b) => b.price - a.price);
console.log(products[0].name);                               // モバイルバッテリー
比較関数が返す数値が前後を決める
比較関数a.price - b.pricea は 980b は 2980a は 2980b は 2980a は 4380b は 980-2000負の値が返る0差が無い3400正の値が返るa を前に置く元の順のまま残るb を前に置く
同じ比較関数でも、渡された 2 件の価格によって返る数値が変わります。差が 0 の 2 件は、並べ替える前の順のまま残ります

sort が見ているのは返った数値の符号だけで、-2000-1 は同じ扱いです。同じ価格の商品どうしにも順を付けたいときは、差が 0 になった場合に別の値を比べる処理を、比較関数の中に書き足します。

フリマアプリの出品一覧を、画面に出す並び順に整えます。listings は宣言済みです。

① 商品名の文字数が少ない順に並べ替えて、商品名を「, 」でつないで表示してください。

② 価格の安い順に並べ替えて、商品名を同じ形で表示してください。

③ 価格が同じ出品はいいねの多い順になるように並べ替え直して、商品名を同じ形で表示してください。

(正しく実行できれば解説が表示されます)

JavaScript / TypeScript エディタ

コードを実行してください

そのまま呼ぶと起きること — 既定の比較と toSorted

ページの滞在秒数を集めた数値の配列を、短い順に並べたいとします。比較関数を渡さずに sort() と書くと、25 秒より 1180 秒が前に来るような、大小と合わない並びが返ります。

比較関数を省いた sort は、要素をいったん文字列に直してから 1 文字ずつ比べます。1180"1180"25"25" として扱われ、先頭の 12 で前後が決まります。sort は呼ばれた配列そのものを並べ替えるので、元の並びも残したいときは toSorted(並べ替えた新しい配列を返すメソッド)を使います。

const viewSeconds = [1180, 980, 25, 4380];

// 比較関数がないと文字列として比べられる
console.log(viewSeconds.sort().join(", "));                 // 1180, 25, 4380, 980

// 比較関数を渡すと数値の大小で並ぶ
console.log(viewSeconds.sort((a, b) => a - b).join(", "));  // 25, 980, 1180, 4380

// toSorted は新しい配列を返し、元の配列は変えない
const stayTimes = [1180, 980, 25, 4380];
console.log(stayTimes.toSorted((a, b) => a - b).join(", "));  // 25, 980, 1180, 4380
console.log(stayTimes.join(", "));                            // 1180, 980, 25, 4380
比較関数の有無で比べる対象が変わる
sort() と書く数値を文字列に直す1180 と 25 を先頭から比べる1180 が前に残る(a, b) => a - bを渡す数値のまま引き算する1180 - 25 が正の値になる25 が前に来る
同じ 1180 と 25 でも、比較関数を渡したかどうかで比べる値の種類が変わります。前に来る要素が、2 つの行で逆になります

文字列にした時点で "1180""25" より前と決まるため、数値の配列を並べ替えるときは比較関数を渡します。上のコードの 2 回目の sort は、1 回目で並びが書き換わった viewSeconds を、さらに並べ替えています。

sort と toSorted で配列がどう変わるか
viewSeconds.sort(...)同じ配列を書き換える戻り値も同じ配列元の並びは残らないstayTimes.toSorted(...)元の配列は読むだけ新しい配列を返す元の並びが残る
上の行の sort が返すのは、書き換えた viewSeconds そのものです。新しい配列ができるのは toSorted の側だけです

古いブラウザには toSorted がありません

toSorted は ES2023 で仕様に加わったメソッドで、対応していない古いブラウザでは、呼び出した行が TypeError で止まります。そうした環境でも動かすときは、[...stayTimes].sort((a, b) => a - b) のように、スプレッド構文でコピーした配列に sort を呼びます。

会員と在庫の一覧を並べ替えて表示します。memberIds と stockCounts は宣言済みです。

① memberIds を比較関数を渡さずに並べ替えて、「, 」でつないで表示してください。

② stockCounts を比較関数を渡さずに並べた新しい配列を作り、同じ形で表示してください。

③ stockCounts を少ない順に並べた新しい配列を作り、同じ形で表示してください。

④ stockCounts の 0 番目を表示してください。

JavaScript / TypeScript エディタ

コードを実行してください

条件に合う 1 件を取り出す — find と findIndex

予約番号を指定して 1 件の予約を取り出したいとします。filter でも書けますが、返るのは配列なので [0] を付けて 1 件目を読むことになり、条件に合う予約が 1 件も無ければ空の配列が返ります。

find(条件に合う最初の要素を返すメソッド)は、コールバックが最初に true を返した要素そのものを返します。位置が知りたいときは findIndex(条件に合う最初の要素の位置を返すメソッド)を使います。

const reservations = [
  { code: "RSV-201", room: "会議室 A" },
  { code: "RSV-202", room: "会議室 B" },
  { code: "RSV-203", room: "会議室 C" },
];

// 最初に true が返った要素そのものが戻る
const target = reservations.find((item) => item.code === "RSV-202");
console.log(target.room);  // 会議室 B

// 位置が欲しいときは findIndex
console.log(reservations.findIndex((item) => item.code === "RSV-202"));  // 1

// 条件に合う予約が無いとき
console.log(reservations.find((item) => item.code === "RSV-999"));       // undefined
console.log(reservations.findIndex((item) => item.code === "RSV-999"));  // -1
見つかった時点で呼び出しが止まる
1 件目 RSV-201false が返る次の要素へ進む2 件目 RSV-202true が返るこの要素がfind の戻り値3 件目 RSV-203呼ばれない判定は 2 件で打ち切られる
2 件目で true が返った時点で、3 件目のコールバックは呼ばれません。条件に合う要素が前にあるほど、呼び出す回数は少なくなります

find が返すのは条件に合う先頭の 1 件だけなので、該当する要素が複数あるときに全部を集める用途には使えません。すべて必要なときは配列を返す filter を使い、1 件で足りるときに find を選びます。

見つからないと undefined が返る

find が返した値をそのまま target.room のように読むと、条件に合う要素が無かったときに TypeError: Cannot read properties of undefined (reading 'room') で止まります。読む前に if (target) で確かめます。

配布中のクーポンから、条件を指定して 1 件を取り出します。coupons は宣言済みで、expired が true のものは期限切れです。

① コードが MEMBER15 のクーポンの割引率を表示してください。

② 期限切れのクーポンの位置(0 から数えた番号)を表示してください。

③ 期限切れでない最初のクーポンのコードを表示してください。

④ コードが AUTUMN30 のクーポンを探し、あれば割引率を、無ければ「該当なし」を表示してください。

JavaScript / TypeScript エディタ

コードを実行してください

配列全体を真偽で確かめる — some と every

注文の明細をまとめて出荷できるかを、在庫の状態から判断したいとします。在庫切れの明細を filter で集めてから件数を数えても判定できますが、欲しいのは配列ではなく truefalse の 1 つの答えです。

some(条件に合う要素が 1 つでもあれば true を返すメソッド)と every(すべての要素が条件に合えば true を返すメソッド)は、要素ではなく真偽値を返します。if の条件にそのまま書ける形です。

const orderItems = [
  { name: "詰め替えボトル", stock: 12 },
  { name: "ハンドソープ", stock: 0 },
  { name: "泡立てネット", stock: 5 },
];

// 1 つでも条件に合えば true
console.log(orderItems.some((item) => item.stock === 0));   // true

// すべてが条件に合ったときだけ true
console.log(orderItems.every((item) => item.stock > 0));    // false

// 空の配列では some が false、every が true
console.log([].some((item) => item.stock > 0));             // false
console.log([].every((item) => item.stock > 0));            // true
some と every が判定をやめる場所
some を呼ぶ在庫 0 を探す1 件目 12false が返る2 件目 0true が返る残りを見ずにtrue で確定every を呼ぶ在庫があるか1 件目 12true が返る2 件目 0false が返る残りを見ずにfalse で確定
someevery も、在庫 0 の 2 件目で答えが決まります。every は条件に合わない要素を見つけた時点で終わります

空の配列では、条件に合う要素が 1 つも無いので somefalse、条件に反する要素も無いので everytrue です。下の表は、この記事で扱った 4 つのメソッドが返す値をまとめたものです。

メソッド返すもの条件に合う要素が無いとき
find最初に条件に合った要素そのものundefined
findIndex最初に条件に合った要素の位置-1
some1 件でも合えば truefalse
everyすべて合えば truefalse(空の配列では true)

オンライン講座の受講者全員に修了証を出せるかを、課題の提出数と出席率から確かめます。learners は宣言済みです。

① すべての受講者が課題を 1 件以上提出しているかどうかを表示してください。

② 提出数が 0 の受講者が 1 人でもいるかどうかを表示してください。

③ 提出数が 0 の受講者の名前を表示してください。

④ すべての受講者の出席率が 80% 以上かどうかを表示してください。

JavaScript / TypeScript エディタ

コードを実行してください
QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1比較関数が負の値を返したとき、sort はどちらの要素を前に置きますか?

Q2[1180, 980, 25] を比較関数なしで sort() すると、どの並びになりますか?

Q3在庫がすべて 1 以上かどうかを 1 つの真偽値で確かめたいとき、使うメソッドはどれですか?