コレクションの要素の順番は特定のコレクション型においては重要な側面と言えます。
例えば、要素が全く同じ2つのリストでも、要素の順番が違うと等しいものとはみなされません。
Kotlinではオブジェクトの順番はいくつかの方法で定義することができます。はじめに順番の種類を確認しておきましょう。
一つ目はナチュラルオーダー(natural order)と呼ばれるものです。
これはComparableインターフェースを継承しているクラスに対して定義されます。他の順番が指定されていないときに適用されます。
ほとんどの組み込み型はComparableインターフェースを継承しているので、最初から比較できるようになっています。
自分で定義した型にナチュラルオーダーを定義するには、その型にComparableを継承させます。そのためにはcompareTo()関数を実装しなければなりません。またcompareTo()は同じ型の別のオブジェクトを引数にとり、対象となっているオブジェクトとどちらが大きいかを示すInt値を返すようにします。
以下が実装例です。
class Version(val major: Int, val minor: Int): Comparable<Version> {
override fun compareTo(other: Version): Int {
if (this.major != other.major) {
return this.major - other.major
} else if (this.minor != other.minor) {
return this.minor - other.minor
} else return 0
}
}
fun main() {
println(Version(1, 2) > Version(1, 3)) //false 両オブジェクトのmajorは等しいがminorは右辺のほうが大きいため
println(Version(2, 0) > Version(1, 5)) //true 左辺のmajorが右辺よりも大きいため
}
基本的な関数であるsorted()とsortedDescending()はナチュラルオーダーで昇順、または降順に並び替えられたコレクションの要素を返します。この関数はComparableを継承している要素のコレクションに適用できます。
val numbers = listOf("one", "two", "three", "four")
println("Sorted ascending: ${numbers.sorted()}") //Sorted ascending: [four, one, three, two]
println("Sorted descending: ${numbers.sortedDescending()}") //Sorted descending: [two, three, one, four]
カスタムオーダー(custom order)はどの型のインスタンスでも好きなように並び替えることができます。
Comparableを継承していないため比較出来ないオブジェクトに対してや、比較できるオブジェクトにナチュラルオーダー以外の順番を定義したりすることができます。
カスタムオーダーをある型に定義するには、Comparatorをその型に作成してあげます。
Comparatorはcompare()という関数を含んでおり、そのクラスのインスタンスを2つ、引数にとり、それらの比較結果を示すIntを返します。比較結果のIntは上のcompareTo()で示したものと同じ方式で解釈されます。
val lengthComparator = Comparator { str1: String, str2: String -> str1.length - str2.length }
println(listOf("aaa", "bb", "c").sortedWith(lengthComparator)) //[c, bb, aaa]
上はString型を辞書形式ではなく、lengthComparatorを使うことで文字の長さで比べるカスタムオーダーの例です。
Comparatorを実装するより簡単な方法はスタンダードライブラリのcompareBy()関数という使います。
compareBy()はインスタンスからComparable値を生成するラムダ関数を引数にとり、生成された値のナチュラルオーダーで並び替えるカスタムオーダーを定義します。紛らわしい言い回しになってしまいますが、もともとのインスタンスのナチュラルオーダーではありません。
compareBy()を用いると、上のコードは以下のように書き換えられます。
println(listOf("aaa", "bb", "c").sortedWith(compareBy { it.length }))
比較出来ないオブジェクトをカスタムオーダーで並び替えるには、sortedBy()またはsortedByDescending()を用います。
これらはコレクションの要素をComparable値にするためのセレクター関数をとり、その値のナチュラルオーダーで並び替えます。以下が使用例です。
val numbers = listOf("one", "two", "three", "four")
val sortedNumbers = numbers.sortedBy { it.length } //要素の長さで昇順
println("Sorted by length ascending: $sortedNumbers") //Sorted by length ascending: [one, two, four, three]
val sortedByLast = numbers.sortedByDescending { it.last() } //最後の文字で降順
println("Sorted by the last letter descending: $sortedByLast") //Sorted by the last letter descending: [four, two, one, three]
コレクションの並び替えにカスタムオーダーを定義するには、独自のComparatorを使用することもできます。
それにはComparatorにsortedWith()関数を渡します。この関数を用いて、文字列を長さで並び替えるコードを書くと以下のようになります。
val numbers = listOf("one", "two", "three", "four")
println("Sorted by length ascending: ${numbers.sortedWith(compareBy { it.length })}")
//Sorted by length ascending: [one, two, four, three]
reversed()関数を用いることで、逆順に並び替えたコレクションを取得することができます。
val numbers = listOf("one", "two", "three", "four")
println(numbers.reversed()) //[four, three, two, one]
reversed()で返されたコレクションはもとのコレクションの要素をコピーした新しいコレクションなので、もとのコレクションを後で変更しても、すでにreversed()で取得されたコレクションには影響しません。
最後がランダムオーダーです。Kotlinにはコレクションの要素をランダムに並び替えて格納された新しいリストを返す関数、shuffled()があります。引数なし、またはRandomオブジェクトを用いて呼び出すことができます。
val numbers = listOf("one", "two", "three", "four")
println(numbers.shuffled())
以上、Kotlinのコレクションのパッケージにはナチュラル、カスタム、さらにランダムオーダーでソートする関数が用意されています。このページでは読み込み専用のコレクションに適用できるソート関数を説明しました。
最後に確認問題を解いてみましょう。
顧客(Customer)の注文数(Order)で降順に並び替えて返す関数を実装してください。
実装にはsortedByDescendingを使ってください。
なお、Shopクラスと全ての関連するクラスはShop.ktに含まれています。
問題コード:
// 顧客の注文数を降順で並び替えた顧客のリストを返す
fun Shop.getCustomersSortedByOrders(): List<Customer> =
TODO()
Shop.kt
data class Shop(val name: String, val customers: List<Customer>)
data class Customer(val name: String, val city: City, val orders: List<Order>) {
override fun toString() = "$name from ${city.name}"
}
data class Order(val products: List<Product>, val isDelivered: Boolean)
data class Product(val name: String, val price: Double) {
override fun toString() = "'$name' for $price"
}
data class City(val name: String) {
override fun toString() = name
}
[expander_maker id=”1″ more=”答え” less=”非表示”]
fun Shop.getCustomersSortedByOrders(): List<Customer> =
customers.sortedByDescending { it.orders.size }
[解説]
セレクター関数として、it.orders.sizeを取ることで顧客の注文数の数を比較しています。
考える順番としては、まずShopクラスに対して使う関数なので、Shopクラスのcustomers(List型)に対してsortedByDescendingを使い、そのcustomersの中のそれぞれのcustomerがもつorders(List型)のサイズ(ordersに含まれるorderの数=注文数)を調べています。
[/expander_maker]その他の問題はこちらからどうぞ。
プログラミング学習はどうしても一人だとつまづいてしまう時がきます。調べればわかることも少なくないですが、最初のうちは調べ方もわからないことが多いため、あまり効率的ではありません。
効率的かつ挫折せずにプログラミングを学習したい方はスクールを検討してみるのも一つの手です。
中には無料で通えるスクールや、就職保証をしてくれるスクールなどもあるので、きっとあなたの目的に応じて最適のスクールが見つかります!以下の記事で評判がよく特におすすめのスクールをいくつかピックアップしているので、スクール選びで後悔したくない方は御覧ください!
Kotlinの文法をまず学びたい!という方には以下の書籍がおすすめです。Kotlinは日本語書籍がまだ豊富とは言えない状況ですが、細かく解説されており、Kotlin入門者のかたでもつまずくことなく学習できると思います。
[itemlink post_id=”1743″]実際にアプリを作りながら覚えていきたい!という方には以下もお勧めです。はじめに上の書籍で文法をさらっと学んでから取り組むのがお勧めです。
[itemlink post_id=”1745″]コレクション(Collection)とはSet、List、Mapといった型のことで、複数の値を格納したり、格納した値に番号を割り振ることで任意の値を取り出したりすることができるものです。
それらはコレクションヒエラルキーというものに属しており、根幹にはCollection<T>というインターフェースがあります。このインターフェースはそのコレクションのサイズを読み込んだり、値を調べるといった読み込み専用コレクションの基本的な挙動を表すものです。
Collectionは繰り返し処理を定義しているIterable<T>インターフェースを継承しています。
またCollectionを関数のパラメーターとして設定すると以下のコードのように、異なる型のコレクションにも適用させることができます。
fun printAll(strings: Collection<String>) {
for(s in strings) print("$s ")
println()
}
fun main() {
val stringList = listOf("one", "two", "one")
printAll(stringList) //ここではList型が渡されている
val stringSet = setOf("one", "two", "three")
printAll(stringSet) //ここではSet型が渡されている
}
以下ではList、Set、Map、それぞれの違いを説明していきます。
List<T>は指定された順番で要素を格納し、それぞれに紐づいたインデックス番号を振り分けます。番号は最初の要素が0から始まり、最後の要素がlastIndexになります。lastIndexはリストの要素数から1を引いた(list.size – 1)と表すこともできます。
val numbers = listOf("one", "two", "three", "four")
println("Number of elements: ${numbers.size}")
println("Third element: ${numbers.get(2)}")
println("Fourth element: ${numbers[3]}")
println("Index of element \"two\" ${numbers.indexOf("two")}")
//出力結果
//Number of elements: 4
//Third element: three
//Fourth element: four
//Index of element "two" 1
Listの要素(nullを含む)は重複することもあります。同じリスト内に何個でも等しいオブジェクトがあったり、一つのオブジェクトが何回も使われることがあります。
リスト同士は同じサイズ(要素数が同じ)で同じ位置に同じ要素がある場合、等しいものとみなされます。
val bob = Person("Bob", 31)
val people = listOf(Person("Adam", 20), bob, bob)
val people2 = listOf(Person("Adam", 20), Person("Bob", 31), bob)
println(people == people2) //true
bob.age = 32
println(people == people2) //false
Set<T>は一意性を持つ(かぶりがない)要素を格納します。通常順番は定義されていません。null要素も同様に一意であり、一つのSetにはnullは一つしか含むことができません。
Set同士はサイズが同じ(要素数が同じ)で、Setのそれぞれの要素と等しい要素がもう一方のSetに含まれていれば等しいものとみなされます。
val numbers = setOf(1, 2, 3, 4)
println("Number of elements: ${numbers.size}")
if (numbers.contains(1)) println("1 is in the set")
val numbersBackwards = setOf(4, 3, 2, 1)
println("The sets are equal: ${numbers == numbersBackwards}")
//出力結果
//Number of elements: 4
//1 is in the set
//The sets are equal: true
冒頭でListやSet、Mapはコレクションヒエラルキーに属していると説明しましたが、実はMap<K, V>だけはCollectionインターフェースを継承していません。しかし他の型と同様にMapもKotlinコレクション型の一つです。
Mapはキーと値(またはエンティティ)のペアを格納します。キーには一意性がありますが、異なるキーが等しい値と紐づけられていることもありえます。Mapインターフェースにはキーで値にアクセスしたり、キーや値を検索するなどといった関数が定義されています。
/val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
println("All keys: ${numbersMap.keys}")
println("All values: ${numbersMap.values}")
if ("key2" in numbersMap) println("Value by key \"key2\": ${numbersMap["key2"]}")
if (1 in numbersMap.values) println("The value 1 is in the map")
if (numbersMap.containsValue(1)) println("The value 1 is in the map") //上のコードと同じ意味
//出力結果
//All keys: [key1, key2, key3, key4]
//All values: [1, 2, 3, 1]
//Value by key "key2": 2
//The value 1 is in the map
//The value 1 is in the map
Map同士は等しいペアを格納をしていれば、ペアの順番に関係なく等しいものをみなされます。
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
val anotherMap = mapOf("key2" to 2, "key1" to 1, "key4" to 1, "key3" to 3)
println("The maps are equal: ${numbersMap == anotherMap}") //true
Kotlinスタンダードライブラリにはコレクションをより便利に活用するための拡張関数が数多く含まれています。例えば、あるコレクションを別の型のコレクションに変換するための ‘to‘から始まる関数です。(toSetやtoListなど)
拡張関数Shop.getSetOfCustomers()を実装してください。Shopクラスと全ての関連するクラスはShop.ktに含まれています。
問題コード:
fun Shop.getSetOfCustomers(): Set<Customer> =
TODO()
Shop.kt
data class Shop(val name: String, val customers: List<Customer>)
data class Customer(val name: String, val city: City, val orders: List<Order>) {
override fun toString() = "$name from ${city.name}"
}
data class Order(val products: List<Product>, val isDelivered: Boolean)
data class Product(val name: String, val price: Double) {
override fun toString() = "'$name' for $price"
}
data class City(val name: String) {
override fun toString() = name
}
[expander_maker id=”1″ more=”答え” less=”非表示”]
fun Shop.getSetOfCustomers(): Set<Customer> =
customers.toSet()
[解説]
Shopクラスに含まれているCustomersはList<Customer>型なので、Set<Customer>型に変換する必要があります。問題はじめで説明したように、Kotlinスタンダードライブラリには既にコレクション型を別のコレクション型に変換する関数が含まれているので、それを活用すれば簡単にListからSetに変換することができます。
[/expander_maker]その他の問題はこちらからどうぞ。
プログラミング学習はどうしても一人だとつまづいてしまう時がきます。調べればわかることも少なくないですが、最初のうちは調べ方もわからないことが多いため、あまり効率的ではありません。
効率的かつ挫折せずにプログラミングを学習したい方はスクールを検討してみるのも一つの手です。
中には無料で通えるスクールや、就職保証をしてくれるスクールなどもあるので、きっとあなたの目的に応じて最適のスクールが見つかります!以下の記事で評判がよく特におすすめのスクールをいくつかピックアップしているので、スクール選びで後悔したくない方は御覧ください!
Kotlinの文法をまず学びたい!という方には以下の書籍がおすすめです。Kotlinは日本語書籍がまだ豊富とは言えない状況ですが、細かく解説されており、Kotlin入門者のかたでもつまずくことなく学習できると思います。
[itemlink post_id=”1743″]実際にアプリを作りながら覚えていきたい!という方には以下もお勧めです。はじめに上の書籍で文法をさらっと学んでから取り組むのがお勧めです。
[itemlink post_id=”1745″]invoke()は演算子のオーバーロードで説明した演算子と同じように、演算子の一つという扱いがなされている関数です。どのクラスに対しても実装することができ、instance()のようにインスタンスのあとに()を付けるだけで呼び出すことが可能になります。
instance()はコンパイラにはinstance.invoke()と解釈されます。
もちろん引数をつけたり、戻り値を設定して実装することも可能です。
以下に簡単な実装例を載せておきます。
引数無し、戻り値無しの場合
class HelloWorld {
operator fun invoke() {
print("Hello World!")
}
}
val helloWorld = HelloWorld()
helloWorld() //"Hello World!"が出力されます。
引数有り、戻り値無しの場合
class HelloName {
operator fun invoke(name: String){
print("Hello " + name)
}
}
val helloName = HelloName()
helloName("Takashi") //"Hello Takashi"が出力されます。
引数無し、戻り値有りの場合
class InvokeCount {
var count: Int = 0
operator fun invoke(): Int {
count++
return count
}
}
val invokeCount = InvokeCount()
print(invokeCount()) // 1
print(invokeCount()) // 2
どのクラスに対しても実装することができますが、乱用するとかえってコードが読みづらくなる可能性があるので注意しましょう。
次のコードにInvokable.invoke()を実装し、呼び出された回数をカウントする機能を追加してください。
class Invokable {
var numberOfInvocations: Int = 0
private set
operator fun invoke(): Invokable {
TODO()
}
}
fun invokeTwice(invokable: Invokable) = invokable()()
[expander_maker id=”1″ more=”答え” less=”非表示”]
class Invokable {
var numberOfInvocations: Int = 0
private set
operator fun invoke(): Invokable {
numberOfInvocations++
return this
}
}
fun invokeTwice(invokable: Invokable) = invokable()()
[解説]
今回のinvoke()では戻り値にInvokable型を指定しているので、invoke()を呼び出したインスタンスをそのまま返すようにしています。
()は演算子の一つという扱いですので、invokable()()のように連続で使うこともできます。
動作確認したい場合は以下をKotlin Playgroundで試してみると良いでしょう。
fun main(){
class Invokable {
var numberOfInvocations: Int = 0
private set
operator fun invoke(): Invokable {
numberOfInvocations++
return this
}
}
fun invokeTwice(invokable: Invokable) = invokable()()
val invokable = Invokable()
invokeTwice(invokable)
invokable()
print(invokable.numberOfInvocations)
}
[/expander_maker]
その他の問題はこちらからどうぞ。
プログラミング学習はどうしても一人だとつまづいてしまう時がきます。調べればわかることも少なくないですが、最初のうちは調べ方もわからないことが多いため、あまり効率的ではありません。
効率的かつ挫折せずにプログラミングを学習したい方はスクールを検討してみるのも一つの手です。
中には無料で通えるスクールや、就職保証をしてくれるスクールなどもあるので、きっとあなたの目的に応じて最適のスクールが見つかります!以下の記事で評判がよく特におすすめのスクールをいくつかピックアップしているので、スクール選びで後悔したくない方は御覧ください!
Kotlinの文法をまず学びたい!という方には以下の書籍がおすすめです。Kotlinは日本語書籍がまだ豊富とは言えない状況ですが、細かく解説されており、Kotlin入門者のかたでもつまずくことなく学習できると思います。
[itemlink post_id=”1743″]実際にアプリを作りながら覚えていきたい!という方には以下もお勧めです。はじめに上の書籍で文法をさらっと学んでから取り組むのがお勧めです。
[itemlink post_id=”1745″]演算子のオーバーロード:比較演算子で演算子をオーバーロードする方法について解説しました。
復習のために簡単に説明すると、
自分で作ったクラスなどで演算子をオーバーロードすることで、+、-、*、/といった演算子をそのクラスのインスタンスに直接使用することができるようになります。
もっと具体的に言うと、1+1はInt型同士の演算なので当たり前のように2という数値が計算できますが、自分で作ったclass MyAge(val age: Int)というクラスがあったとして、
fun main() {
class MyAge(val age: Int)
val age = MyAge(20)
print("あなたは5年後" + (age + 5) + "歳です")
//コンパイルエラー
}
上のコードはMyAgeクラスで+演算子をオーバーロードしていないのにageに対して使ってしまっているので、コンパイルエラーがおきてしまいます。下のコードでは+演算子をオーバーロードしているので、25歳が出力されます。
fun main() {
class MyAge(val age: Int)
val age = MyAge(20)
operator fun MyAge.plus(num: Int) = //+演算子のオーバーロード
this.age + num
print("あなたは5年後" + (age + 5) + "歳です")
}
a+bやa-bのように、二つの値を計算させる際に使われる演算子を二項演算子と呼びます。
Kotlinで二項演算子をオーバーライドするには以下の表で対応している関数をオーバーライドすることで、その演算子が独自クラスでも機能するようになります。
式 | コンパイル時に使われる関数 |
---|---|
a + b | a.plus(b) |
a - b | a.minus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.rem(b) , a.mod(b) (非推奨) |
a..b | a.rangeTo(b) |
上のコードにもあるように、+という演算子をオーバーロードするには実際にはplusという関数を実装するようになります。
今回の問題は少し難易度高めです。
以下のコードを日付の計算をできるようにしましょう。dateに対して、年・週・日の加算に対応させましょう。
例えばdate + YEAR * 2 + WEEK * 3 + DAY * 15のようにコードを書けるようになります。
①はじめにMyDateクラスにTimeIntervalを引数として取るplus()拡張関数を追加してください。
拡張関数の実装にはDateUtil.ktに宣言されているMyDate.addTimeIntervals()関数を使用してください。
②その後、TimeIntervalを乗算(*)もできるようにしてください。新しいクラスを追加する必要があります。
問題コード:
import TimeInterval.*
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int)
// 日付に追加される期間を表すenumクラス
enum class TimeInterval { DAY, WEEK, YEAR }
operator fun MyDate.plus(timeInterval: TimeInterval): MyDate = TODO()
fun task1(today: MyDate): MyDate {
return today + YEAR + WEEK
}
fun task2(today: MyDate): MyDate {
TODO("乗算に対応したらコメント解除してください") //return today + YEAR * 2 + WEEK * 3 + DAY * 5
}
DateUtil.kt:
import java.util.Calendar
/*
* 与えられたインターバルを追加した日付を返すメソッド
* インターバルは与えられた数の日数、週数、年数で指定されます
* 使い方:
* 'date.addTimeIntervals(TimeInterval.DAY, 4)'
* 'date.addTimeIntervals(TimeInterval.WEEK, 3)'
*/
fun MyDate.addTimeIntervals(timeInterval: TimeInterval, amount: Int): MyDate {
val c = Calendar.getInstance()
c.set(year + if (timeInterval == TimeInterval.YEAR) amount else 0, month, dayOfMonth)
var timeInMillis = c.timeInMillis
val millisecondsInADay = 24 * 60 * 60 * 1000L
timeInMillis += amount * when (timeInterval) {
TimeInterval.DAY -> millisecondsInADay
TimeInterval.WEEK -> 7 * millisecondsInADay
TimeInterval.YEAR -> 0L
}
val result = Calendar.getInstance()
result.timeInMillis = timeInMillis
return MyDate(result.get(Calendar.YEAR), result.get(Calendar.MONTH), result.get(Calendar.DATE))
}
[expander_maker id=”1″ more=”ヒント” less=”非表示”]
問題①で変更すべきコードは
operator fun MyDate.plus(timeInterval: TimeInterval): MyDate = TODO()
ですが、これは単純にtimeIntervalとして与えられた値(日、週、年のいずれか)を一つ分足す処理です。DAYであれば一日、WEEKであれば一週、YEARであれば一年足されます。日付計算の複雑な処理はDateUtil.ktのaddTimeIntervalsに既に実装されているので、まずは、このメソッドの使い方をよく確認してください。
②の問題は以下の三つの手順で実装します。
import TimeInterval.*
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int)
// 日付に追加される期間を表すenumクラス
enum class TimeInterval { DAY, WEEK, YEAR }
operator fun MyDate.plus(timeInterval: TimeInterval) =
addTimeIntervals(timeInterval, 1)
class RepeatedTimeInterval(val timeInterval: TimeInterval, val number: Int)
operator fun TimeInterval.times(number: Int) =
RepeatedTimeInterval(this, number)
operator fun MyDate.plus(timeIntervals: RepeatedTimeInterval) =
addTimeIntervals(timeIntervals.timeInterval, timeIntervals.number)
fun task1(today: MyDate): MyDate {
return today + YEAR + WEEK
}
fun task2(today: MyDate): MyDate {
return today + YEAR * 2 + WEEK * 3 + DAY * 5
}
[/expander_maker]
その他の問題はこちらからどうぞ。
プログラミング学習はどうしても一人だとつまづいてしまう時がきます。調べればわかることも少なくないですが、最初のうちは調べ方もわからないことが多いため、あまり効率的ではありません。
効率的かつ挫折せずにプログラミングを学習したい方はスクールを検討してみるのも一つの手です。
中には無料で通えるスクールや、就職保証をしてくれるスクールなどもあるので、きっとあなたの目的に応じて最適のスクールが見つかります!以下の記事で評判がよく特におすすめのスクールをいくつかピックアップしているので、スクール選びで後悔したくない方は御覧ください!
Kotlinの文法をまず学びたい!という方には以下の書籍がおすすめです。Kotlinは日本語書籍がまだ豊富とは言えない状況ですが、細かく解説されており、Kotlin入門者のかたでもつまずくことなく学習できると思います。
[itemlink post_id=”1743″]実際にアプリを作りながら覚えていきたい!という方には以下もお勧めです。はじめに上の書籍で文法をさらっと学んでから取り組むのがお勧めです。
[itemlink post_id=”1745″]イテレーターとはforループが動作するために必要な概念で、イテレーターが実装されていないオブジェクトではforループを使うことができません。
具体的にはイテレーターとは次の要素を返すnext()メソッドと次の要素が存在するかを確かめるhasNext()メソッドに分解することができます。これらを実装することで、forループがいつまで繰り返されるべきなのかを知ることができるのです。
Kotlinにおけるfor構文は以下のようになります。
for (item in collection) print(item)
for (i in 1..3) {
println(i) //1,2,3
}
上の1,2,3が出力されるforループでは、1..3というIntの値の範囲を指定するIntRange型にイテレーターが実装されているため、1の次には2があり、2の次には3があり、3の次には値がないといった判断をコンピューターができるのです。
従って、独自に作ったクラスでforループを使いたい場合、自身でイテレーターを実装する必要があります。
以下のDateRangeクラスにIterable<MyDate>を実装し、繰り返し処理ができるようにしてください。
なお、DateUtil.ktファイルに既にMyDate.followingDate()という関数が定義されているので、次の日を見つけるロジックを自身で実装する必要はありません。
DateUtil.ktとMyDate.ktは下にあります。
class DateRange(val start: MyDate, val end: MyDate) //ここにIterable<MYDate>を実装する
fun iterateOverDateRange(firstDate: MyDate, secondDate: MyDate, handler: (MyDate) -> Unit) {
for (date in firstDate..secondDate) {
handler(date)
}
}
DateUtil.kt
import java.uti.Calendar
fun MyDate.folowwingDate(): MyDate {
val c = Calendar.getInstance()
c.set(year, month, dayOfMonth)
val millisecondsInADay = 24 * 60 * 60 * 1000L
val timeInMillis = c.timeInMillis + millisecondsInADay
val resuly = Calendar.getInstance()
result.timeInMillis = timeInMillis
return MyDate(result.get(Calendar.YEAR), result.get(Calendar.MONTH), result.get(Calendar.DATE))
}
MyDate.kt
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) : Comparable<MyDate> {
override fun compareTo(other: MyDate): Int {
if (year != other.year) return year - other.year
if (month != other.month) return month - other.month
return dayOfMonth - other.dayOfMonth
}
}
operator fun MyDate.rangeTo(other: MyDate) = DateRange(this, other)
[expander_maker id=”1″ more=”ヒント” less=”非表示”]
イテレーターを実装するクラスはIterable<T>を継承している必要があります。
今回の場合、TはMyDateになるので、
class DateRange(val start: MyDate, val end: MyDate) : Iterable<MyDate>のようになります。
イテレーターを実装するには、クラスの中でIterator<MyDate>を返すiterator()関数をオーバーライドし、その関数内で次の値を返すnext()と、次の値の有無をチェックするhasNext()を実装します。
next()内では、次の値がある場合には次の値を返し、ない場合には例外を投げるようにしましょう。
[/expander_maker] [expander_maker id=”1″ more=”答え” less=”非表示”]class DateRange(val start: MyDate, val end: MyDate) : Iterable<MyDate> {
override fun iterator(): Iterator<MyDate> {
return object : Iterator<MyDate> {
var current: MyDate = start
override fun next(): MyDate {
if (!hasNext()) throw NoSuchElementException()
val result = current
current = current.followingDate()
return result
}
override fun hasNext(): Boolean = current <= end
}
}
}
fun iterateOverDateRange(firstDate: MyDate, secondDate: MyDate, handler: (MyDate) -> Unit) {
for (date in firstDate..secondDate) {
handler(date)
}
}
[解説]
DateRangeというIterable<MyDate>を継承したクラスでMyDateのイテレーターを実装しています。
iterator()関数では範囲内最初の日付であるstartをcurrentに代入し、next()内で次の日付があればcurrentに代入しています。次の日付の取得には既に定義されたfollowingDate()関数を使用しています。
hasNext()ではcurrentがendを超えた場合にfalseを返すように設定されているので、next()が繰り返し使用され、currentの値がendを超えた時にfalseになります。
イテレーターの実装は次の値を取得するnext()と次の値があるかを確かめるhasNext()の実装と考えましょう。
[/expander_maker]その他の問題はこちらからどうぞ。
プログラミング学習はどうしても一人だとつまづいてしまう時がきます。調べればわかることも少なくないですが、最初のうちは調べ方もわからないことが多いため、あまり効率的ではありません。
効率的かつ挫折せずにプログラミングを学習したい方はスクールを検討してみるのも一つの手です。
中には無料で通えるスクールや、就職保証をしてくれるスクールなどもあるので、きっとあなたの目的に応じて最適のスクールが見つかります!以下の記事で評判がよく特におすすめのスクールをいくつかピックアップしているので、スクール選びで後悔したくない方は御覧ください!
Kotlinの文法をまず学びたい!という方には以下の書籍がおすすめです。Kotlinは日本語書籍がまだ豊富とは言えない状況ですが、細かく解説されており、Kotlin入門者のかたでもつまずくことなく学習できると思います。
[itemlink post_id=”1743″]実際にアプリを作りながら覚えていきたい!という方には以下もお勧めです。はじめに上の書籍で文法をさらっと学んでから取り組むのがお勧めです。
[itemlink post_id=”1745″]KotlinではrangeTo()関数を用いることで、簡単に値の範囲を作成することができます。
rangeToの代わりに演算子”..“を用いることもできます。rangeTo()はよくinや!inと共に使われます。
if (i in 1..4) { //iが1から4の間 1 <= i && i <= 4と同じ コンパイラにはi in 1.rangeTo(4)と解釈される
print(i)
}
ちなみに1..4で渡される値はIntRange型という範囲を指定する型になります。
ある値(i)がその範囲内にあるかを確認するにはcontains、またはinという演算子を用います。
上記のコードをcontainsを用いて表現する場合、以下のようになります。
if ((1..4).contains(i)) {
print(i)
}
範囲指定を用いて、ある日付(date)がそれぞれ日付を表すfirstとlastの間(それぞれの値も含む)に含まれているかをチェックする関数を実装してください。
fun checkInRange(date: MyDate, first: MyDate, last: MyDate): Boolean {
return TODO()
}
※MyDateクラスは以下のように定義されているものとします。
//MyDate.kt
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) : Comparable<MyDate> {
override fun compareTo(other: MyDate): Int {
if (year != other.year) return year - other.year
if (month != other.month) return month - other.month
return dayOfMonth - other.dayOfMonth
}
}
[expander_maker id=”1″ more=”ヒント” less=”非表示”]
val list = listOf("a", "b")
"a" in list // list.contains("a")と同じ
"a" !in list // !list.contains("a")と同じ
date1..date2 // date1.rangeTo(date2)と同じ
Kotlinでは上記コードのようにinはcontainsに、..はrangeToに対応します。
[/expander_maker] [expander_maker id=”1″ more=”答え” less=”非表示”]fun checkInRange(date: MyDate, first: MyDate, last: MyDate): Boolean {
return date in first..last
}
[解説]
dateがIntRange型のfirst..lastに含まれているかをdate in first..lastでチェックしています。
戻り値はBoolean型です。
[/expander_maker]その他の問題はこちらからどうぞ。
プログラミング学習はどうしても一人だとつまづいてしまう時がきます。調べればわかることも少なくないですが、最初のうちは調べ方もわからないことが多いため、あまり効率的ではありません。
効率的かつ挫折せずにプログラミングを学習したい方はスクールを検討してみるのも一つの手です。
中には無料で通えるスクールや、就職保証をしてくれるスクールなどもあるので、きっとあなたの目的に応じて最適のスクールが見つかります!以下の記事で評判がよく特におすすめのスクールをいくつかピックアップしているので、スクール選びで後悔したくない方は御覧ください!
Kotlinの文法をまず学びたい!という方には以下の書籍がおすすめです。Kotlinは日本語書籍がまだ豊富とは言えない状況ですが、細かく解説されており、Kotlin入門者のかたでもつまずくことなく学習できると思います。
[itemlink post_id=”1743″]実際にアプリを作りながら覚えていきたい!という方には以下もお勧めです。はじめに上の書籍で文法をさらっと学んでから取り組むのがお勧めです。
[itemlink post_id=”1745″]Kotlinでは型に事前に定義されている演算子のセットに対して独自の実装を施すことができます。
これらの演算子には前もって定義された記号(+や*など)があり、優先順位も決まっています。演算子を実装するには、対応する型用の特定の名前をもつメンバ関数、または拡張関数を用意します。
例えばa.unaryMinus()という関数は-aという式に対応しており、以下のコードのようにPoint.unaryMinus()をオーバーロードしておけば、-Pointが実際にはオーバーロードされたPoint.unaryMinus()として機能するようになります。
data class Point(val x: Int, val y: Int)
operator fun Point.unaryMinus() = Point(-x, -y)
val point = Point(10, 20)
fun main() {
println(-point) // "Point(x=-10, y=-20)"
}
それぞれの演算子に対して対応する関数があるので、必要に応じて公式リファレンスで確認してください。
演算子をオーバーロードするにはoperator修飾子を使って対応する関数をマーキングします。
interface IndexedContainer {
operator fun get(index: Int)
}
また演算子のオーバーロードをオーバーライドする際には、operator修飾子を省くことができます。
class OrdersList: IndexedContainer {
override fun get(index: Int) { /*...*/ }
}
MyDateクラスにcompareTo関数を追加し、MyDateクラスを比較できるようにしてください。
date1 < date2がエラー無くコンパイルできるようにしましょう。
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) : Comparable<MyDate> {
/* TODO */
}
fun test(date1: MyDate, date2: MyDate) {
// this code should compile:
println(date1 < date2)
}
[expander_maker id=”1″ more=”ヒント” less=”非表示”]
date1 < date2は実際にはコンパイラによって、date1.compareTo(date2) < 0と解釈されます。
ですので、MyDateクラスの中でcompareTo関数をオーバーライドし、MyDateクラスのパラメーターに渡されたInt型のyear、month、dayOfMonthとcompareToに渡されたMyData型のyear、month、dayOfMonthとを比較できるようにしましょう。
[/expander_maker] [expander_maker id=”1″ more=”答え” less=”非表示”]data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) : Comparable<MyDate> {
override fun compareTo(other: MyDate) = when {
year != other.year -> year - other.year
month != other.month -> month - other.month
else -> dayOfMonth - other.dayOfMonth
}
}
fun test(date1: MyDate, date2: MyDate) {
// this code should compile:
println(date1 < date2)
}
[解説]
when式の中で、yearが異なればyearからother.yearを引いた値を返すようにしています。
yearの値とother.yearの値が同じ場合はmonthを…という形で同様にdayOfMonthまで比較させています。
もしdate1.year < date2.yearであれば、date1.compareTo(date2)で返ってくる値は負(<0)になるので、date1.compareTo(date2) < 0はtrueを返すようになります。従って、date1 < date2もtrueになります。
[/expander_maker]その他の問題はこちらからどうぞ。
プログラミング学習はどうしても一人だとつまづいてしまう時がきます。調べればわかることも少なくないですが、最初のうちは調べ方もわからないことが多いため、あまり効率的ではありません。
効率的かつ挫折せずにプログラミングを学習したい方はスクールを検討してみるのも一つの手です。
中には無料で通えるスクールや、就職保証をしてくれるスクールなどもあるので、きっとあなたの目的に応じて最適のスクールが見つかります!以下の記事で評判がよく特におすすめのスクールをいくつかピックアップしているので、スクール選びで後悔したくない方は御覧ください!
Kotlinの文法をまず学びたい!という方には以下の書籍がおすすめです。Kotlinは日本語書籍がまだ豊富とは言えない状況ですが、細かく解説されており、Kotlin入門者のかたでもつまずくことなく学習できると思います。
[itemlink post_id=”1743″]実際にアプリを作りながら覚えていきたい!という方には以下もお勧めです。はじめに上の書籍で文法をさらっと学んでから取り組むのがお勧めです。
[itemlink post_id=”1745″]拡張関数とは特定の型に独自の関数を追加できる機能です。
拡張関数を宣言するには、関数を追加したい型のあとに .関数名(パラメーター) の形で拡張関数を追加します。例えば、以下はMutableList<Int>という型にswapというリストの中身を入れ替える拡張関数を追加するコードです。ちなみに拡張関数が追加される型(MutableList<Int>)のことをレシーバー型を呼びます。
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // thisはリストに対応しています。
this[index1] = this[index2]
this[index2] = tmp
}
拡張関数内のthisというキーワードはレシーバーオブジェクト(ドットの前で実際に渡されるレシーバー型のオブジェクト)に対応します。これでどのMutableList<Int>上でもswapという関数が呼び出せるようになりました。
val list = mutableListOf(1, 2, 3)
list.swap(0, 2)
さらに以下のようにMutableListの中身の型を<T>にすれば、どのMutableListに対してもswapを使えるようにできます。Tのことをジェネリック型と呼びます。
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
レシーバー型の式の中でジェネリック型を使えるようにするために、関数名の一番最初でもジェネリック型パラメーターを宣言します。
次のコード中の拡張関数 Int.r()とPair.r()を実装し、IntとPairをデータクラスであるRationalNumber型に変換できるようにしてください。Pairはスタンダードライブラリに定義されているクラスです。ちなみにRationalNumberとは日本語で有理数を意味します。二つの整数を用いて分数で表せる数のことですね。
Pair:
data class Pair<out A, out B>(
val first: A,
val second: B
)
問題コード:
fun Int.r(): RationalNumber = TODO()
fun Pair<Int, Int>.r(): RationalNumber = TODO()
data class RationalNumber(val numerator: Int, val denominator: Int) //numerator:分子 denominator:分母
Kotlin Playgroundでコードの編集・動作確認ができます。
[expander_maker id=”1″ more=”ヒント” less=”非表示”]Int.r()ではRationalNumberのコンストラクタのnumeratorにはレシーバーオブジェクトを、denominatorには1を渡します。
Pair.r()ではnumeratorにはfirst、denominatorにはsecondを渡します。
[/expander_maker] [expander_maker id=”1″ more=”答え” less=”非表示”]fun Int.r(): RationalNumber = RationalNumber(this, 1)
fun Pair<Int, Int>.r(): RationalNumber = RationalNumber(first, second)
data class RationalNumber(val numerator: Int, val denominator: Int)
[解説]
Int.r()のRationalNumber(this, 1)で、thisはレシーバーオブジェクトを意味します。たとえば、
4.r()とした場合、RationalNumber(4, 1)となります。
Pairクラスではパラメーターの最初に渡されたIntをfirst、二つ目をsecondとしているので、RationalNumber型に変換される際にはfirstがnumerator(分子)、secondがdenominator(分母)になります。
[/expander_maker]その他の問題はこちらからどうぞ。
プログラミング学習はどうしても一人だとつまづいてしまう時がきます。調べればわかることも少なくないですが、最初のうちは調べ方もわからないことが多いため、あまり効率的ではありません。
効率的かつ挫折せずにプログラミングを学習したい方はスクールを検討してみるのも一つの手です。
中には無料で通えるスクールや、就職保証をしてくれるスクールなどもあるので、きっとあなたの目的に応じて最適のスクールが見つかります!以下の記事で評判がよく特におすすめのスクールをいくつかピックアップしているので、スクール選びで後悔したくない方は御覧ください!
Kotlinの文法をまず学びたい!という方には以下の書籍がおすすめです。Kotlinは日本語書籍がまだ豊富とは言えない状況ですが、細かく解説されており、Kotlin入門者のかたでもつまずくことなく学習できると思います。
[itemlink post_id=”1743″]実際にアプリを作りながら覚えていきたい!という方には以下もお勧めです。はじめに上の書籍で文法をさらっと学んでから取り組むのがお勧めです。
[itemlink post_id=”1745″]クラスや関数をimportする際、import文の後にas 新しい名前と書き加えることで別の名前を指定することができます。別のライブラリからの似たような名前のクラスや関数を複数個使いたい時など、混乱を避けるのに便利です。
import org.test.Message as testMessage //testMessageはorg.test.Messageを表している
以下のコードのコメント部分を解除し、KotlinパッケージからのRandomをKRandomに、javaパッケージからのRandomをJRandomという名前に変更してコンパイルできるようにしてください。
// import kotlin.random.Random
// import java.util.Random
fun useDifferentRandomClasses(): String {
return "Kotlin random: " +
// KRandom.nextInt(2) +
" Java random:" +
// JRandom().nextInt(2) +
"."
}
Kotlin Playgroundでコードの編集・動作確認ができます。
[expander_maker id=”1″ more=”答え” less=”非表示”]import kotlin.random.Random as KRandom
import java.util.Random as JRandom
fun useDifferentRandomClasses(): String {
return "Kotlin random: " +
KRandom.nextInt(2) +
" Java random:" +
JRandom().nextInt(2) +
"."
}
[解説]
kotlin.random.Random as KRandomとすることによって、useDifferentRandomClasses()内でもKRandomという名前で使用することができるようになっています。
[/expander_maker]その他の問題はこちらからどうぞ。
プログラミング学習はどうしても一人だとつまづいてしまう時がきます。調べればわかることも少なくないですが、最初のうちは調べ方もわからないことが多いため、あまり効率的ではありません。
効率的かつ挫折せずにプログラミングを学習したい方はスクールを検討してみるのも一つの手です。
中には無料で通えるスクールや、就職保証をしてくれるスクールなどもあるので、きっとあなたの目的に応じて最適のスクールが見つかります!以下の記事で評判がよく特におすすめのスクールをいくつかピックアップしているので、スクール選びで後悔したくない方は御覧ください!
Kotlinの文法をまず学びたい!という方には以下の書籍がおすすめです。Kotlinは日本語書籍がまだ豊富とは言えない状況ですが、細かく解説されており、Kotlin入門者のかたでもつまずくことなく学習できると思います。
[itemlink post_id=”1743″]実際にアプリを作りながら覚えていきたい!という方には以下もお勧めです。はじめに上の書籍で文法をさらっと学んでから取り組むのがお勧めです。
[itemlink post_id=”1745″]シールドクラスとは継承をより制御するための制限がなされたクラスです。シールドクラスの全てのサブクラスはコンパイル時に認知されます。シールドクラスがコンパイルされるモジュールの後で別のサブクラスが発生することはありません。つまりシールドクラスが宣言されたファイル内でしかそのシールドクラスは継承できないということです。
例えばサードパーティのクライアントがあなたのシールドクラスを別のファイル上のコードで継承することはできません。したがって、シールドクラスのインスタンスはそのクラスのコンパイル時に存在していたものに限ります。ですので、エラー発生時にも確認する場所が限られているので、効率的です。
シールドクラスは簡単にいうとEnumクラスのようなものですが、若干違いがあります。それらがEnumと比べた際のメリットにもっているのでポイントを追って説明していきます。
その違いの一つはシールドクラスはクラスやオブジェクト宣言をクラス内に内包できるという点です。
シールドクラス内にそのシールドクラスを継承するクラスを宣言することができるため、より細かい状態を表現することができます。
例えばテレビの状態を表すEnumクラスがあったとします。ONとOFFという列挙型を保持しており、テレビがついているか、ついていないかを表現することができます。
しかしついていた場合、映されているチャンネルまで表すことはできません。
シールドクラスではクラスを内包することができるので、”テレビの状態を表すシールドクラス”の中で、それぞれON/OFFの状態を表すクラスを作成しシールドクラスを継承させます。
ONの場合にはチャンネルを引数にとってインスタンスを作ることで、ON時のチャンネルまで表現することができるのです。
ちなみに同ファイル内であれば、シールドクラス内に内包しなくても、それを継承したサブクラスの作成は可能です。
ポイント1でもさらっと述べましたが、Enumと決定的に違うのはインスタンスを作れるという点です。
Enumはシングルトンなので、中身の列挙型を変更することはできません。しかしシールドクラスはインスタンスを作成して使用することができるため、各インスタンスの状態を後から変更することもできます。
上の例に続いて述べれば、テレビのチャンネルをあとから変更するメソッドを用意することで、インスタンス生成時とは別のチャンネルを設定することができるのです。
シールドクラスの一番のメリットはwhen式を利用するときに現れます。はじめに述べたように、シールドクラスの中身はenum同様、宣言時に書いたものに限られているので、全ての可能性はシールドクラスの中身に限られています。従って、通常のwhen式のようにelse文を追加して分岐の中でカバーしきれていない可能性について考慮する必要がありません。シールドクラスを利用したwhen式ではelseを省くことができるのです。
シールドクラスを定義するにはsealed修飾子をクラス名の前につけるだけです。
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
前回のスマートキャストの記事で利用したコード(下に載せてあります)のインターフェースをシールドクラスで置き換えてください。そうすることでwhen式のelseが必要なくなります。
前回のコード:
fun eval(expr: Expr): Int =
when (expr) {
is Num -> TODO()
is Sum -> TODO()
}
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
[expander_maker id=”1″ more=”答え” less=”非表示”]
fun eval(expr: Expr): Int =
when (expr) {
is Num -> expr.value
is Sum -> eval(expr.left) + eval(expr.right)
}
sealed class Expr
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
[解説]
interface Exprをsealed classとして宣言することで、when式内のelseが無くてもコンパイルエラーが起こらなくなっています。
これはevalの引数として渡されているexprが確実にExprを継承しているNumかSumのインスタンスであることが保証されているためです。
[/expander_maker]その他の問題はこちらからどうぞ。
プログラミング学習はどうしても一人だとつまづいてしまう時がきます。調べればわかることも少なくないですが、最初のうちは調べ方もわからないことが多いため、あまり効率的ではありません。
効率的かつ挫折せずにプログラミングを学習したい方はスクールを検討してみるのも一つの手です。
中には無料で通えるスクールや、就職保証をしてくれるスクールなどもあるので、きっとあなたの目的に応じて最適のスクールが見つかります!以下の記事で評判がよく特におすすめのスクールをいくつかピックアップしているので、スクール選びで後悔したくない方は御覧ください!
Kotlinの文法をまず学びたい!という方には以下の書籍がおすすめです。Kotlinは日本語書籍がまだ豊富とは言えない状況ですが、細かく解説されており、Kotlin入門者のかたでもつまずくことなく学習できると思います。
[itemlink post_id=”1743″]実際にアプリを作りながら覚えていきたい!という方には以下もお勧めです。はじめに上の書籍で文法をさらっと学んでから取り組むのがお勧めです。
[itemlink post_id=”1745″]