Senの競技プログラミング備忘録

こけた問題を自分用の解説で載せる。けんちょんさんのブログを目指したい。質的にも量的にも。こけた問題だけに限定するけど

(競プロメイン)C++erから見たKotlin勉強記録-2 Array, List, 関数

テスト環境

Try Kotlin
try.kotlinlang.org
paizaのKotlin環境
paiza.io

配列(Array, List)

Kotlinでは、ArrayとList、2つの(固定長)配列型がある。

違い:

  • Arrayは中身を変更可能なクラスという構造体。
  • Listは中身が読み取り専用(変更できなくもないけど基本的にしない)のインターフェイス(多くの機能の共用ベース的なイメージ。継承元とも違う)。

C++ではこういうデータを格納する構造をコンテナと言ってたけど、Kotlinではコレクションという。

Array

C++では

int ar[5];
ar[0]=3, ar[1]=1, ar[2]=4;
int ar2[3]={1, 5, 9};

Kotlinでは、配列型は〇〇Arrayとなる。(IntArray, DoubleArray etc)
初期化は、String以外の基本形は、intArrayOf()関数(メンバ関数ではない!)を使う。C++のように={要素1, 要素2, ..}のように初期化はできない。
Stringを始めとするそれ以外の型では、ArrayOf()関数を使う。
添え字は、C++Javaと同じくサイズNで [0, N-1]である。

val ar1 : IntArray = intArrayOf(3, 1, 4, 1, 5, 9)
val ar2 : StringArray = ArrayOf("一刀両断", "十二神将", "三千世界", "四面楚歌")

//昔のC++っぽく書くとこう
for(i in 0 until ar1.count())println(a[i])
//Kotlin特有の記述でこうかける。Range-Best-For文のイメージ
for(v in ar1)printf(v)

List

//Arrayだとこう書く。
val ar1 : IntArray = intArrayOf(3, 1, 4, 1, 5, 9)
for(v in ar1)println(v)

//Listだとこう書く。
val lst1 : List<Int> = listOf(3, 1, 4, 1, 5, 9)
for(v in lst1)println(v)

val ar2 : IntArray = intArrayOfNulls(5)
//これでサイズだけが5で空の配列を作れる。

val lst2 : ListOfNulls(5)
//同じように、Listを作っている

Listの型はList<要素の型名>であり、初期化はlistOf()関数を使う。
そして、Listの中身は少なくとも直接的にプログラムの中では変更することができず、読み取り専用という扱いになる。(定数みたいな感じ)

Array, List達のメンバ関数

重要そうなメンバ関数たちを、かなり端折りながら載せておく。配列名をarとすると、みんな ar.名前() で使える。
基本的に、iterable(つまり大体のコンテナ)なものは大体これらを持っている。

詳しいものは、以下のQiitaの記事で使用例と共にあるので、ぜひ見よう。
qiita.com

count()

C++でいうとsize()。紛らわしいが、Kotlinではcount()がサイズを返すのである!

forEach()

C++のstd::for_each()に相当。コンテナ内のすべての要素に、引数で渡した関数の操作を施す。関数とは?は下にある関数の説明を読むとわかる(例ではラムダ式にしている)。for_each()と違ってイテレータを指定しないので範囲はコンテナ内のすべてと固定される。

forEach()にラムダ式を渡すと、すべての要素に対して○○、という操作をしてくれる。

val ar : Array<String> = arrayOf("61", "15", "5333", "43", "135350", "3442p3")
ar.forEach({println("the ${it} 's size = ${it.length}.")})
map(), mapIndexed()

すべての要素に対して、引数で渡した関数の処理を施したものを新しいコレクションを返す。

Todo:ソースコードを書く

max(), min(), sum()

C++では、主にstd::max_element(), std::min_element(), std::sum()の機能達。max()やmin()はC++のmax_element(), min_element()と違って値自体を返すので注意!

val ar : IntArray = intArrayOf(3, 1, 4, 1, 5, 9)
println("Sum is ${ar.sum()}. Max is ${ar.max()}. Min is ${ar.min()}")

distinct()

中身の重複した要素を1つになるまで消したコレクションを返す。2つ以上ある要素は1つ目のみ残す。

val ar = intArrayOf(3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5)
val ar_press = ar.distinct()
for(i in ar_press)print("${i} ")
println()
//3 1 4 5 9 2 6
sorted(), sortedDescending()

C++での、std::sort()。コレクションの中身を昇順ソート(sort())、降順ソート(sortedDescending())ed

val ar : List<Int> = listOf(3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5)
val slist = ar.sorted()
for(v in slist)print(v + " ")
println()
//1 1 2 3 3 4 5 5 5 6 9 

val sdlist = ar.sortedDescending()
for(v in sdlist)print(v + " ")
println()
//9 6 5 5 5 4 3 3 2 1 1 

関数

基本

C++での関数

int f1(int a, int b){
	return a + b;
}

int f2(){
	return 462;
}

void f3(){
	std::cout << "something" << std::endl;
}

Kotlinでは

fun f1(a : Int, b : Int) : Int {
    return a + b
}

fun f2() : Int{
    return 462
}

//C++におけるvoid型は、Unitである。
fun f3() : Unit{
    println("something")
}

//このように、Kotlinで直感的に書ける。
fun f1_soficicated(a : Int, b : Int) : Int = a + b
//返り値が明確にわかってるこの場合、ここまで省略可能
fun f1_more_soficicated(a : Int, b : Int) = a + b

Kotlinでは、Unitというのがvoidに相当する型(もちろん途中で止めるためにreturnするのも可能)だが、エラー検出のためにNothingという型もある。Nothing型関数が実行中にreturnがされるとエラーになるため、デバッグの助けになれる。(C++のvoidだとそもそも値をreturnできない)

動的引数

C++では...をつけることで動的引数にすることができた(printf()など)。僕は詳しくないので特に何も言えない。

Kotlinでも同じようなことができる。

fun product_all(vararg n : Int) : Int {
    var ret = 1
    for(v in n)ret *= v
    return ret
}

fun main(args: Array<String>){
    println(product_all(3, 1, 4, 1, 5))//60
    println(product_all(1, 2, 3))//6
    println(product_all(2, 3, 5, 7, 11))//2310
}

関数オブジェクト

C++のstd::functionや関数ポインタのように、Kotlinでも関数をオブジェクトとして変数に保存し、関数に渡すことも可能。

C++では

#include <bits/stdc++.h>
using namespace std;

int ADD(int a, int b){return a + b;}

void conduct(int a, int b, std::function<int(int, int)> ope){
	cout << "the result by ope is " << ope(a, b) << "\n";
}

int main(){
	std::function<int(int, int)> ADD_obj = ADD;
	conduct(114, 514, ADD_obj);
	return 0;
}

Kotlinでは

fun ADD(a : Int, b : Int) = a + b

//(Int, Int)を引数にとって、Intを返す関数を格納する型
fun conduct(a : Int, b : Int, ope : (Int, Int) -> Int){
    println("the result by ope is ${ope(a, b)}")
}

fun main(args : Array<String>){
    
    var function = ::ADD//代入するときは、::を関数名の前につけること!
    //var function : (Int, Int) -> Int = ::ADD
    //上のように型名を明示してもいいけど
    
    conduct(114, 514, function)
    conduct(114, 514, ::ADD)
    
}

高階関数

返り値として関数を返す関数はもちろん作れる。これを高階関数(こうかいかんすう)と呼ぶ。

また、KotlinはC++などのように、関数Bを返す関数Aを、オブジェクトに一時保存すると、保存したときに与えた関数Aの値は保存される。(それはそう)

また、返り値としての関数Bの中で参照される関数内Aの値だが、これは関数オブジェクトにAを代入して、呼び出される場合、通常ではスコープの関係上リフレッシュされると思われるが、そのようはことはない。(以下のコードを参照)

fun closure() : (String) -> String{
    var cnt = 1
    
    var ret = fun(inputS : String) : String{
        var rs : String = inputS + cnt.toString()
        cnt++
        return rs
    }
    return ret
}

fun main(args : Array<String>){
    
	var c1 = closure() //closure()の返ってきた関数を代入している。 
	//::closureならばclosure()という関数を関数オブジェクトとして保存
	var c2 = closure()
	
    println(c1("cnt in c1 is"))
    println(c1("cnt in c1 is"))
    println(c1("cnt in c1 is"))
    println(c2("cnt in c2 is"))
    println(c2("cnt in c2 is"))
    println(c1("cnt in c1 is"))
    
}

出力

cnt in c1 is1
cnt in c1 is2
cnt in c1 is3
cnt in c2 is1
cnt in c2 is2
cnt in c1 is4

無名関数とラムダ式

C++などにあるように、関数を右辺値として一時オブジェクトのまま定義することができる。この時、定義した関数には名前がないため、無名関数と呼ばれる。

無名関数の一種として、ラムダ式がある。とりわけ無名関数のうち即席で作れるような関数を指す。

C++では

//こう書く。
[](int a, int b){return a > b ? a : b}
auto obj = [](int a, int b){return a > b ? a : b};
こうすることもできる。
obj(2, 6);
//なんならすぐに呼び出してもいい
[](int a, int b){return a > b ? a : b}(114, 514)

Kotlinでは、

var f1 = fun(x : Int, y : Int) : Int{
    return x + y
 }
    
var f2 = fun(x : Int, y : Int) = x + y
//{x, y -> x+y}のような書き方をするには、(Int, Int) -> Intのように型を明示すべし
//これがラムダ式の書き方
var f3 : (Int, Int) -> Int = {x, y -> x + y}

究極にタイピングしない方々向けの書き方。

また、ラムダ式を引数として渡すとき、唯一の引数が関数オブジェクトなら、もっと省略して書ける。人間はついに考えることをやめたのだ。

f({x, y -> x + y})//今まで通り
f(){x, y -> x + y}
f{x, y -> x + y}

さらにさらに、引数が1つしかないような無名関数はitというのを使ってさらに略せる!

f({x -> x.toString() + "回腹筋をする。"})
f({it.toString() + "回腹筋をする。"})//1つ受け取って1つ返すラムダ式の究極系
fun main(args : Array<String>){	
    var f : (Int) -> String = {it.toString() + "回腹筋するぞ!"}
    println(f(121))
}

このように、itにメンバ関数を使うのも当然可能である。

forEach()

try-catch構文

C++にもJavaにもある例外発生時の処理を既定するコード。
Kotlinではこう書く。大枠はC++Javaとほぼ同じ。

var str : String = "aiueo"
try{
    var num = str.toInt()
    println(num)
}catch (e : NumberFormatException){
    println("変換不能")
}

//このようにcatchを続けることもできる
var str : String = "aiueo"
try{
    var num = str.toInt()
    println(num)
}catch (e : NumberFormatException){
    println("変換不能")
}catch (e: ArrayIndexOutOfBoundsException){
    println("サイズがおかしい。文字を入れてないとか?")
}finally{
    println("バグなかったぜ!")
}
//最後のfinallyは必須ではない。if文でいうelse的な存在

参考文献

やさしいKotlin入門 初版 野崎英一

買う場合はこれ、定価2400円+税なのでamazonポチするよりかは書店で買った方がいいと思う。

dogwood008.github.io
公式リファレンス