読者です 読者をやめる 読者になる 読者になる

倭算数理研究所

科学・数学・学習関連の記事を、「倭マン日記」とは別に書いていくのだ!

2.6 コーヒーの冷却の問題 : プログラミング演習 2.3 数のリストの取り扱い

今回はプログラミング演習2.3『数のリストの取り扱い』(記事一覧)。 Groovy のプログラミング演習デス。

問題

a. xmax の値を x と xmax の大きい方の値で置き換えるサブルーチンを書け。
b. 最小値も計算するようなサブルーチンを書け。 キーボードからいくつかの値を入力し、一連の数の最大値と最小値を表示せよ。
c. 数 y を入力し、数のリストから y を探すようにプログラムを書き換えよ。
d. プログラムを修正し、設問 a, b で書いたサブルーチンを関数で置き換えよ。

ちょっと問題いじってますが、気にしないで下さい。 また、Groovy ではサブルーチンで書けないので、関数(メソッド)で書きます。 つまり、設問 d をやってるのと同じになります。

a.


Groovy で素直に実装するとこんな感じ:

def x = 2, xmax = 1
xmax = max(x, xmax)
assert xmax == 2

def max(x, y){
    if(x >= y)
        return x
    else
        return y
}

Groovy ではサブルーチンではなくメソッドなので、返り値を変数に代入するのを忘れずに。 max メソッドは3項演算子を使うと簡潔に書けます:

def max(x, y){
    return (x >= y) ? x : y
}

なんか、メソッドにする必要ある?って気もするけど。 可変長引数のメソッドにするのも簡単です:

assert max(1, 2, 3, 4) == 4

def max(Comparable... args){
    return args.max()
}

args に対して呼び出している max() メソッドは Groovy が配列に対して追加しているメソッドです。 同様のメソッドは Collection に(もちろん List や Set にも)対しても追加されています。

b.


最小値を返すメソッド min() はこんな感じでしょう:

assert min(1, 2) == 1
assert min(1, 0) == 0

def min(x, y){
    return (x <= y) ? x : y
}

さてこれを踏まえて、キーボードから入力された一連の数の最大値、最小値を表示するコードを書いてみます。 アプローチとしては

  1. 値を入力される毎に最大値、最小値を計算していく
  2. 値を全てストックしておいて、最後に最大値、最小値を計算する

という2つがあると思います。 全データが必要でない限り、1つ目のアプローチの方がメモリ的にもパフォーマンス的にも無難でしょうけど、ここではプログラミング演習のため両方やっておきます。

値を入力される毎に最大値、最小値を計算していく

まずは入力毎に最大値・最小値を計算する場合:

def maxValue = null, minValue = null

System.in.withReader{
    boolean hasNext = true
    while(hasNext){
        println 'Input : '; String line = it.readLine()

        if(line.isNumber()){    // 入力文字列が数値に変換できる場合
            def d = line.toDouble()

            // 一番最初は maxValue, minValue が null なので
            // max() メソッドを使わないように3項演算子で判定してます。
            maxValue = (maxValue != null) ? max(maxValue, d) : d
            minValue = (minValue != null) ? min(minValue, d) : d
        }else{
            hasNext = false
        }
    }
}

println """
max : $maxValue
min : $minValue
"""

/** 引数が null だとマズイ */
def max(x, y){
    return (x >= y) ? x : y
}

def min(x, y){
    return (x <= y) ? x : y
}

max(), min() メソッドに(maxValue, minValue の初期値である) null を渡さないようにするために3項演算子なんかを使ってみました。 max(), min() メソッドを、null 値が渡されてもいいように変更する方が分かりやすいかも知れませんね。

ちなみに、ここでは整数値(になる文字列)を入力しても表示されるのは double 値になります。 気になる場合は String#isInteger() メソッドで整数に変換できる場合を別途書くと修正できます。 if 文の代わりに switch 文なんかを使って

        switch(line){
            case { it.isInteger() }:    // 入力文字列が整数値に変換できる場合
                def i = line.toInteger()
                maxValue = (maxValue != null) ? max(maxValue, i) : i
                minValue = (minValue != null) ? min(minValue, i) : i
                break

            case { it.isNumber() }:    // 入力文字列が数値に変換できる場合
                def d = line.toDouble()
                maxValue = (maxValue != null) ? max(maxValue, d) : d
                minValue = (minValue != null) ? min(minValue, d) : d
                break

             default:
                 hasNext = false
        }

という風に書けます。 case 文による条件判定にクロージャを使っている事に注意。 他にも「null かどうか」や「正規表現にマッチするかどうか」なども case 文に書けます:

        switch(line){
            case null:    // null の場合
                hasNext = false
                break

             case ~/\d+/:    // 正規表現にマッチする場合(今の場合数字だけからなる文字列)
                def i = line.toInteger()
                maxValue = (maxValue != null) ? max(maxValue, i) : i
                minValue = (minValue != null) ? min(minValue, i) : i
                break
            ...
        }

正規表現を使う場合は ~ を忘れずに。

値を全てストックしておいて、最後に最大値、最小値を計算する

次はとりあえず入力値をストックしておいて、入力が終わってから最大値、最小値を計算する場合:

def values = []

System.in.withReader{
    boolean hasNext = true
    while(hasNext){
        println 'Input : '
        String line = it.readLine()
        if(line.isNumber()){
            values << line.toDouble()    // List オブジェクト args に要素を加える
        }else{
            hasNext = false
        }
    }
}

assert !values.isEmpty()    // assert で妥当性検証すべきものでもないかもしれないけど
// assert !values

println """
max : ${values.max()}
min : ${values.min()}
"""

こちらでは、List オブジェクト values に値を保持させて、最大値、最小値の計算も List クラスのメソッドに任せているので、比較的簡単に実装できます。 max(), min() メソッド自体もなくしました。

c.


最後は入力された数値が、予め用意された数値のリストに含まれているかどうかを判定するプログラム。 まずは普通に Java API の Collection#contains() メソッドを使う場合。

def input = null
def values = (1..10).collect{ it*2 }    // [2, 4, 6, ... , 20] と同じ

System.in.withReader{
    println 'Input : '; String line = it.readLine()
    if(line.isInteger()) input = line.toInteger()
}

assert input != null
// assert !input だと、input が 0 だとマズイことに。

if(values.contains(input))
    println "$input が見つかりました。"
else
    println "$input が見つかりませんでした。"

まぁ、別にどうってことないですね。 これ以降は、Collection の要素検索あれこれ。

  • find() メソッドを使う
  • isCase() メソッドを使う
  • grep() メソッドを使う
  • in キーワードを使う

の4つの場合を見ていきます。

まずは Groovy が Collection に追加した find() メソッドを使う例:

if(values.find{ it == input } != null)
    println "$input が見つかりました。"
else
    println "$input が見つかりませんでした。"

find() メソッドに渡したクロージャは Collection の各要素に適用され、返り値が true ならその要素を、見つからなければ null を返します。 ここでは見つかるかどうかしか知る必要はないので null 判定だけをしています。 find() メソッドは、返り値に対してなにか操作をするときに使う方が普通かと思います。

次は isCase() メソッドを使う例。 これは Groovy が Object クラスに追加したメソッドです。 使い方はこんなの:

if(values.isCase(input))
    println "$input が見つかりました。"
else
    println "$input が見つかりませんでした。"

Collection オブジェクトに対して使うと contains() メソッドのように作用します。 isCase() メソッドはそれ自体で使うことはあまりなく、switch 文in 文、また次でみる grep() メソッドを介して使う方が多いと思います。

そして grep() 文を使う例。 grep() メソッドはフィルターを通すイメージで使うメソッドです*1。 contains() メソッドや isCase() メソッドとは、メソッドを呼び出される方と引数になる方が逆転していることに注意。 grep() メソッドの返り値は Collection で、フィルターを通った要素のみを含んでいます:

if(input.grep(values))
    println "$input が見つかりました。"
else
    println "$input が見つかりませんでした。"

今の場合、values は要素として含んでいるものを通し、含んでないものを通さないフィルターとして働いています。 したがって、grep() メソッドによって返される Collection は

  • input が values に含まれていれば input を要素に持つ。
  • input が values に含まれていなければ空になる。

という風に動作します。 if 文では返される Collection が空でないかどうかを判定しています。

最後は in キーワードを使用する例。 最後に回しておいてなんですが、今の問題の場合にはこれが一番しっくりくる方法だと思います(笑) 使い方はこんな感じ:

if(input in values)
    println "$input が見つかりました。"
else
    println "$input が見つかりませんでした。"

メソッド呼び出しとかは見られませんが、内部では grep() 同様、isCase() メソッドが呼ばれています。

とまぁ、1つのことにいろいろな実現方法があるわけですが、これらの方法はそれぞれ使い方によって便利になる場合があるので、状況によって使い分けられるようになっているといいと思います。

計算物理学入門

計算物理学入門

  • 作者: ハーベイゴールド,ジャントボチニク,Harvey Gould,Jan Tobochnik,鈴木増雄,石川正勝,溜渕継博,宮島佐介
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2000/12
  • メディア: 単行本
  • クリック: 5回
  • この商品を含むブログ (45件) を見る

プログラミングGROOVY

プログラミングGROOVY

*1:Unix 系の OS 使ってる人は慣れ親しんでるんでしょうね。