配列に要素が含まれるか調べるには
配列(リスト)に特定の要素が含まれるかを調べる基本形を各言語で示す。
JavaScript の in 演算子の落とし穴、NaN やオブジェクトの比較、条件で探す書き方までを扱う。
公開:
各言語見出しの横のバッジは検証状態を表す。実行確認済みはコードを実際に実行して確認したもの、静的確認は構文と公式 API ドキュメントで確認したものである。
Python 実行確認済み
fruits = ["apple", "banana", "cherry"]
print("banana" in fruits) # True
print("grape" not in fruits) # True
# 位置も欲しいなら index。見つからないと ValueError になる
print(fruits.index("banana")) # 1
# 比較は == で行われるので、中身が同じリストも見つかる
print([1, 2] in [[1, 2], [3, 4]]) # True
# NaN は自分自身と等しくないが、in は先に同一性(is)を見る
nan = float("nan")
print(nan in [nan]) # True(同じオブジェクト)
print(float("nan") in [nan]) # False(別のオブジェクト)in 演算子は要素を先頭から == で順に比べるため、中身が同じリストでも見つかる。
位置が必要なときは index を使うが、無い値を渡すと ValueError になるので、先に in で確認するか例外を処理する。
JavaScript 実行確認済み
const fruits = ["apple", "banana", "cherry"];
console.log(fruits.includes("banana")); // true
console.log(fruits.indexOf("grape")); // -1(見つからないときは -1)
// NaN は includes なら見つかるが、indexOf では見つからない
console.log([NaN].includes(NaN)); // true
console.log([NaN].indexOf(NaN)); // -1
// オブジェクトは参照で比較されるので、中身が同じでも見つからない
console.log([{ id: 1 }].includes({ id: 1 })); // false
console.log([{ id: 1 }].some((u) => u.id === 1)); // true
// in 演算子が調べるのは要素ではなくインデックス
console.log("banana" in fruits); // false
console.log(1 in fruits); // trueincludes は真偽値を返し、NaN も見つけられる。
indexOf は === で比較するため NaN を見つけられず、見つからないときは -1 を返す。
オブジェクトの配列は参照で比べられるので、中身で探すときは some に条件を書く。
TypeScript 実行確認済み
const fruits: string[] = ["apple", "banana", "cherry"];
const input: string = "banana";
console.log(fruits.includes(input)); // true
// as const の配列は要素がリテラル型になるため、string を渡す includes は型エラーになる
const ROLES = ["admin", "editor"] as const;
const role: string = "admin";
// console.log(ROLES.includes(role)); // error TS2345
console.log((ROLES as readonly string[]).includes(role)); // true
// 型ガードにしておけば、通過後の値は "admin" | "editor" に絞られる
function isRole(value: string): value is (typeof ROLES)[number] {
return (ROLES as readonly string[]).includes(value);
}
console.log(isRole("editor"), isRole("guest")); // true false型の上では string[] の includes は string を受け取る。
ただし as const で作った読み取り専用のリテラル配列では引数もリテラル型の union に絞られるため、string 型の値を渡すとコンパイルエラーになる。
readonly string[] として扱い直すか、型ガード関数にまとめると、通過後の値の型も絞れる。
うまくいかない時: TypeScript で「Argument of type ... is not assignable」が解消できない
Go 実行確認済み
package main
import (
"fmt"
"slices"
)
type User struct{ ID int }
func main() {
fruits := []string{"apple", "banana", "cherry"}
fmt.Println(slices.Contains(fruits, "banana")) // true
fmt.Println(slices.Index(fruits, "grape")) // -1
// 条件で探すなら ContainsFunc(Go 1.21 以降)
users := []User{{ID: 1}, {ID: 2}}
fmt.Println(slices.ContainsFunc(users, func(u User) bool { return u.ID == 2 })) // true
}Go 1.21 以降は標準の slices パッケージに Contains と Index があり、手書きのループが要らなくなった。
比較は == で行われるため要素は比較可能な型である必要があり、条件で探すときは ContainsFunc を使う。
Rust 実行確認済み
fn main() {
let fruits = vec!["apple", "banana", "cherry"];
println!("{}", fruits.contains(&"banana")); // true(引数は参照で渡す)
println!("{:?}", fruits.iter().position(|&f| f == "grape")); // None
// Vec<String> に &"bob" を渡すと型が合わずコンパイルエラーになる
let names = vec![String::from("alice"), String::from("bob")];
println!("{}", names.iter().any(|n| n == "bob")); // true
println!("{}", names.contains(&"bob".to_string())); // true
}スライスの contains は要素への参照を受け取るので、&"banana" のように参照で渡す。
Vec<String> に &"bob"(&&str 型)を渡すと、期待される &String と合わずコンパイルエラー(E0308)になるため、iter().any で比べるか、String を作って渡す。
Java 実行確認済み
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> fruits = List.of("apple", "banana", "cherry");
System.out.println(fruits.contains("banana")); // true
System.out.println(fruits.indexOf("grape")); // -1
String[] arr = {"apple", "banana", "cherry"};
System.out.println(Arrays.asList(arr).contains("banana")); // true
// int[] を asList に渡すと、要素は int ではなく int[] が 1 個になってしまう
int[] nums = {1, 2, 3};
System.out.println(Arrays.asList(nums).contains(2)); // false
System.out.println(Arrays.stream(nums).anyMatch(n -> n == 2)); // true
// List.of が作るリストは null を許さず、contains(null) は例外になる
try {
fruits.contains(null);
} catch (NullPointerException e) {
System.out.println("NullPointerException");
}
}
}List の contains は equals で比較する。
配列は Arrays.asList で包めば同じ書き方で探せるが、int[] のようなプリミティブ配列は要素が int ではなく int[] 1 個として扱われ、常に false になる。
List.of が返す不変リストは null を許さないので、contains(null) は NullPointerException を投げる。
C# 実行確認済み
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void Main() {
string[] fruits = { "apple", "banana", "cherry" };
Console.WriteLine(fruits.Contains("banana")); // True(LINQ の拡張メソッド)
Console.WriteLine(Array.IndexOf(fruits, "grape")); // -1
// 大文字小文字を無視するなら比較子を渡す
Console.WriteLine(fruits.Contains("BANANA", StringComparer.OrdinalIgnoreCase)); // True
var list = new List<string>(fruits);
Console.WriteLine(list.Contains("banana")); // True
Console.WriteLine(list.Exists(f => f.StartsWith("ch"))); // True
}
}配列には LINQ の Contains が使え、List<T> には自前の Contains がある。
文字列は既定で大文字小文字を区別するので、無視したいときは StringComparer.OrdinalIgnoreCase を渡す。
条件で探すなら List の Exists か LINQ の Any を使う。
つまずき
「含まれるか」を調べる書き方は言語ごとに違い、同じ in や contains という名前でも調べる対象が違うことがある。
Python の in は要素を調べるが、JavaScript の in 演算子は配列のインデックス(キー)を調べるため、"banana" in fruits は false になり、1 in fruits は true になる。
要素の有無を調べたいなら、JavaScript では includes を使う。
「同じ」と判定される基準
見つかるかどうかは、各言語がどの比較で「同じ」とみなすかで決まる。
JavaScript の includes と indexOf は参照で比べるため、中身が同じオブジェクトでも見つからず、some に条件を書く必要がある。
Python は == で比べるので、中身が同じリストなら見つかる。
NaN は自分自身とも等しくない特殊な値だが、Python の in は先に同一性を調べるため、同じ NaN オブジェクトなら True になり、別に作った NaN なら False になる。
JavaScript の includes は NaN を見つけられるが、indexOf は見つけられない。
何度も調べるときは集合にする
リストに対する in や includes や contains は、先頭から順に比べるので、要素数が増えるほど時間がかかる。
同じ配列に何度も問い合わせるなら、先に Python の set、JavaScript の Set、Java や C# や Rust の HashSet、Go の map に詰めておくと、1 回あたりの検索が平均して定数時間で済む。
要素が少ないうちは差が出ないので、問い合わせの回数が多いときに切り替えれば足りる。