このページについて
このページは、paiza ラーニング内に開設されているコンテンツ「レベルアップ問題集」で取り扱われているプログラミング課題について、独自の見解を述べたものです。
見解については、paizaラーニングの規約に基づき、許可されている範囲でのみ公開していますが、その内容については paiza とは一切関係なく、また paiza の立場を反映したものではありませんのでご注意ください。
挑戦する課題
レベルアップ問題集の配列活用メニューから「指定の要素のカウント (paizaランク D 相当)」を取り上げます。
以下は、問題公開 Web ページからの引用です。
問題
配列 A の要素数 N と整数 K, 配列 A の各要素 A_1, A_2, ..., A_N が与えられるので、配列 A に K がいくつ含まれるか数えてください。
入力される値
1 2 3 4 |
N K A_1 ... A_N |
- 1 行目では、配列 A の要素数 N と整数 K が半角スペース区切りで与えられます。
- 続く N 行では、配列 A の要素が先頭から順に与えられます。
期待する出力
配列
A に含まれる
K の数を 1 行で出力してください。
また、出力の末尾には改行を入れてください。
考え方
2行目以降 N 行入力される数値のうち、整数 K と一致するものだけ出力させる課題です。
ここでは、 for 文を使った平易な方法と、高階関数を使った方法の2つをご紹介したいと思います。
for 文を使った方法
基本的な考え方は、指定された行を逐一読み込み、それが K に等しければカウンタとして用意した変数をインクリメントするという流れになります。
まず必要となるのは、先頭行で与えられる配列の要素数
N と検索する数値
K を取得することです。
このように、空白で区切られたいくつかの値を取得するためには、次のような方法を使うことが定番となります。
1 2 3 4 |
// 1. 1行読み込み // 2. 空白で分割し // 3. Array<SubString> を Array<String> に変換する let ia = readLine()!.split(separator: " ").map { String($0) } |
この作業を行うことにより、
"123 456" と空白区切りで渡された1行の文字列を、
["123", "456"] といった感じの文字列の配列に変換することができます。
今回の場合、最後の
map { String($0) } は必ずしも必要ではないのですが、
split(separator:) の戻り値は
[Substring] と
String 型の配列ではなくなってしまうためあえて変換しています。
もし、これを数値( Int)型の配列にするのであれば、
1 |
let ia = readLine()!.split(separator: " ").map { Int($0)! } |
と、メソッドチェーンの最後で使っている map(_:) の処理を Int(_:) のイニシャライザにすることによって、 "123 456" を [123, 456] といった具合に Int 型の配列に変換することが可能です。
このような作業を行った結果、
1 2 |
ia[0] -> N ia[1] -> K |
といった感じに値が代入されていますので、必要に応じてそれらを使います。
プログラムの流れとしてご自身が判断できるのであれば配列名のまま使っても構いませんし、分かりやすくするのであれば再代入させてもいいかと思います。
いきなりレベルの高い話になりましたが、paiza などで問題を解く際には、「空白で区切られた1行の文字列を分解する」という作業は多くの出題で取り扱いますので、これは一つのパターンとして覚えておく(メモしておく)のがいいかと思います。
さて、上記で
N と
K を取り出しましたが、このうち
N だけは
Int 型として扱う必要があります。
それは、
for-in 文で指定する繰り返し範囲に指定できるのは
Int 型(や範囲型、シーケンスなど)と限定されているためです。
また、プログラムの見通しを良くするためにも次のようにして一度再代入を行なっておきます。
1 2 3 |
// 行数 N 、見つけたい数値 K let n = Int(ia[0])! let k = ia[1] |
さて、続く行では入力された行が K に等しいかどうかをチェックすることになります。
そのために、まず
K と等しい文字が現れた回数をカウントするための変数
count を準備しておきます。
つぎに、
for-in 文を使い、
N 行分繰り返すように定義します。
この取り出した1行が K と等しければ、 count の値に 1加算します。
Swift の場合、文字列の比較には比較演算子 == を使うことが可能です。
このようにして指定された行数カウントを行ったら、最後に結果を表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 発見した個数 var count = 0 for _ in 0..<n { // 配列の要素を1行読み込む let w = readLine()! // 読み込んだ要素が K に等しければ、count をインクリメント if w == k { count += 1 } } // 結果出力 print(count) |
全体の流れは解答例1に挙げますので、そちらもあわせてご参照ください。
高階関数を使った方法
基本的な考え方としては、 N 行の入力を順次配列に取り込み、そのうち K と一致する要素だけを filter(_:) メソッドを使い抽出した上で、抽出された配列の個数をカウントすることで達成します。
- filter(_:)
Returns an array containing, in order, the elements of the sequence that satisfy the given predicate.
ここで注目しておきたいのは、整数 K となっていますが、問題全体としては整数と考える必要はなく、入力された文字列をそのまま扱えば良い、という点です。もちろん、整数に一度変換しても良いのですが、 filter(_:) を使った比較作業は、この問題の範囲であれば文字列でも整数でも変わりはないため、あえて変換する必要はありません。したがって、この解法では文字列として検索する数値 K を扱いたいと思います。
次に、2行目から N 行続く入力に対する処理です。
ある行から末尾まで一括して取り込む方法として、
AnyIterator 構造体と
readLine() を組み合わせる方法があります。
この方法を使うことによって、
N 行を意識することなく、最終行まで一括して取り込むことが可能となります。
- AnyIterator
A type-erased iterator of Element.
一括して取り込まれた Element に対して、条件を満足する要素を取り出す必要がありますが、 AnyIterator のメソッドにも filter(_:) がありますので、それを使って要素が K と同じ Element のみフィルタリングします。
最後に、条件に合った要素の数を count プロパティを用いることで取得します。この値を解答として出力させれば終了です。
1 2 3 4 |
// 1. 続く行を行末まで読み込み // 2. 配列の要素のうち K と同じものだけを抽出し // 3. その個数を数えて表示する print(AnyIterator { readLine() }.filter { $0 == k }.count) |
コード全体を解答例2に挙げますのであわせてご参照ください。
解答例
解答例1
逐次読み込み、カウントを行う例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// 1. 1行読み込み // 2. 空白で分割し // 3. Array<SubString> を Array<String> に変換する let ia = readLine()!.split(separator: " ").map { String($0) } // 行数 N 、見つけたい数値 K let n = Int(ia[0])! let k = ia[1] // 発見した個数 var count = 0 for _ in 0..<n { // 配列の要素を1行読み込む let w = readLine()! // 読み込んだ要素が K に等しければ、count をインクリメント if w == k { count += 1 } } // 結果出力 print(count) |
解答例2
高階関数を使ったメソッドチェーンを使った例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 1. 1行読み込み // 2. 空白で分割し // 3. Array<SubString> を Array<String> に変換する let ia = readLine()!.split(separator: " ").map { String($0) } // 必要なのは K の値だけ let k = ia[1] // 1. 続く行を行末まで読み込み // 2. 配列の要素のうち K と同じものだけを抽出し // 3. その個数を数えて表示する print(AnyIterator { readLine() } .filter { $0 == k } .count) |