要素の削除と検索
1 を消したはずが、2 が消えた
remove には、見た目のよく似た 2 つの顔があります。
Java
ArrayList<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
ids.add(3);
ids.remove(1); // [1, 3] ─ 消えたのは 1 番目の 2
ids.remove(Integer.valueOf(1)); // [3] ─ こちらは値の 1 が消えるJava には remove(int index) と remove(Object o) の 2 つが用意されていて、渡した引数の型で自動的に振り分けられます。int のリテラルを渡すと必ず番号扱いになるので、値を消したいときは Integer.valueOf(1) のように包んで型をずらします。
ArrayList<String> なら list.remove("sato") は文字列そのものを渡しているので、迷わず値の削除になります。番号と値が同じ見た目になるのは、要素が整数のときだけの問題です。削除すると後ろの要素が 1 つずつ前に詰まり、size() もその場で減ります。
消す前に、入っているかを聞く
Java
ArrayList<String> members = new ArrayList<>();
members.add("sato");
members.add("suzuki");
boolean hasSato = members.contains("sato"); // true
int where = members.indexOf("suzuki"); // 1
int none = members.indexOf("tanaka"); // -1contains は == ではなく各要素の equals で比べます。文字列なら中身が同じなら true です。何番目かまで知りたいときは indexOf を使い、見つからないときは -1 が返ります。if (where >= 0) で見つかった場合を判定する書き方に慣れておくと安全です。
要素数は size()、空かどうかは isEmpty() で分かります。size() > 0 より !isEmpty() のほうが意図が伝わる、というのはレビューでよく言われる話です。
走査しながら消すと、1 つ飛ばされる
条件に合うものを消したくなって、拡張 for の中で remove を呼ぶと事故になります。
Java
for (String name : members) {
if (name.startsWith("s")) {
members.remove(name); // ConcurrentModificationException
}
}拡張 for は「今どこを見ているか」を内部で数えながら進みます。その途中で要素が減ると位置がずれて次の要素を飛ばしてしまうため、Java は異常に気づいた時点で例外を投げて止めます。運悪く例外が出ないまま、消し漏れだけが残ることもあります。
走査しながら消したいときは、Iterator を自分で取り出して remove を呼びます。
Java
Iterator<String> it = members.iterator();
while (it.hasNext()) {
String name = it.next();
if (name.startsWith("s")) {
it.remove(); // 位置がずれない
}
}Iterator 側から消せば、内部の位置も一緒に調整されるので飛ばされません。import java.util.Iterator; を忘れないでください。
課題で消すのは 1 件だけなので、ループは要りません。削除したあとの要素数と、残っているかどうかの判定を組み合わせて文字列を作ります。
要件
- クラス名は
Solution、メソッド名はsummarize、戻り値の型はString、引数なしで定義すること ArrayList<String>を作って"apple""banana""cherry"をこの順にaddしてから、"banana"をremoveすること- 戻り値は
size=+list.size()+,hasApple=+list.contains("apple")の形で連結したStringにすること
入出力例
summarize() → "size=2,hasApple=true"