😽

Let's Kotlin! Kotlin Koansを解いて学ぶ基礎知識: (Introduction5問)

2023/11/24に公開

Kotlin Koans

kotlin公式の文法問題! "Kolin Koans"を 解いて学ぼう!ということで
今回はこれの問題解説と、参考リンクをともに書いていきます。
自分の勉強のために自分で書いてましたが、
載せることで誰かの勉強の助けにもなればと思います(^^)

今回はIntroductionの5問を、解説とともに書いていきますが、
割と長いので、以下の項目押すと飛べるのでそれで興味の範囲見てもいいかと思います!
そして他問題はまた次の記事で書きます!

How about "Kotlin Koans"

Kotlin Koans is a series of exercises to get you familiar with the Kotlin syntax and some idioms. Each exercise is created as a failing unit test, and your job is to make it pass. Here you can play with Koans online, but the same version of exercises is also available via JetBrains educational plugin right inside IntelliJ IDEA or Android Studio.
According to our surveys, Kotlin Koans is one of the most popular and most effective ways to get into Kotlin for people who already know Java. In just a few hours, you'll feel able to write idiomatic Kotlin code.
Let's Kotlin!

「Kotlin Koans」は、Kotlinの構文といくつかのイディオムに慣れるための一連の演習で,
各演習は失敗する単体テストとして作成され、これらのコードを正しく直して進めていきます。

Javaをすでに知っている人々にとって、
Kotlinに入門するための最も人気で効果的な方法の一つだ。
わずか数時間で、あなたはKotlinの習慣的なコードを書くことができるように感じるでしょう!

と、いうことで!!!! Let's Kotlin!

■ Introduction問題

< Introduction問題1: Simple Functions >

Check out the function syntax and change the code to
make the function start return the string "OK"
.
In the Kotlin Koans tasks, the function TODO() will throw an exception. To complete Kotlin Koans, you need to replace this function invocation with meaningful code according to the problem.

文字列「OK」を返すようにコードを変更してください。

fun start(): String = TODO()

▶️ 解説

// 正解①
fun start(): String = "OK"

// これでもいいが...!
fun start(): String {
    return "OK"
}

下の例のようにreturnでかく方法もありますが、kotlinでは、
関数が単一の式で構成されている場合、中括弧 { } を使用せずに、
返り値の型を指定して=を使って直接その式を直接返すことができる

< Introduction問題2: Named arguments >

Make the function joinOptions() return the list in a
JSON format (for example, [a, b, c]) by specifying only two arguments.

Default and named arguments help to minimize the number of overloads and improve the readability of the function invocation. The library function joinToString is declared with default values for parameters:

2つの引数だけを指定して、関数joinOptions()がリストをJSON形式で返すようにしてください
(例: [a, b, c])。
デフォルトと名前付き引数は、オーバーロードの数を最小限に抑え、
関数呼び出しの可読性を向上させるのに役立ちます。
ライブラリ関数joinToStringは、パラメータのデフォルト値を持つように宣言されています:

fun joinToString(
    separator: String = ", ",
    prefix: String = "",
    postfix: String = "",
    /* ... */
): String

It can be called on a collection of Strings.

▶️ 解説

問題では、2つの引数だけを指定してjoinOptions()関数が
リストをJSON形式で返すように求められている。
<回答>

fun joinOptions(options: Collection<String>) =
        options.joinToString( prefix = "[",postfix = "]")

ここでのポイントは、まずは"Default and named arguments"の理解。
<"Default and named arguments">
Kotlinでは、関数の宣言時に引数にデフォルト値を設定することができる
それが上記の問題内でもあったこれだ。
=でdefault値(初期値)の設定が可能。

fun joinToString(
    separator: String = ", ",
    prefix: String = "",
    postfix: String = "",
    /* ... */
): String

そしてこのjoinToString()は存在するメソッドで、Kotlin標準ライブラリ内の kotlin.collections パッケージに含まれる関数だ。
<joinToString()>
コレクションの要素を文字列に連結するための標準のメソッド
この関数は、Iterable インターフェースに拡張関数として提供されており、
指定されたデフォルト値を持つパラメータにより、
要素の区切り文字、接頭辞、接尾辞、制限、省略記号などを指定することができる。
transform パラメータを使用すると、各要素に対して適用する変換関数を指定できる。

joinToString()
fun <T> Iterable<T>.joinToString(
    separator: CharSequence = ", ",
    prefix: CharSequence = "",
    postfix: CharSequence = "",
    limit: Int = -1,
    truncated: CharSequence = "...",
    transform: ((T) -> CharSequence)? = null
): String

よってこの問題では、TODO()部分に引数二つ、prefixpostfixを指定してあげることで解決。

余談:
a,b,cどこやねん、と私はアホなのでわからなくなりましたが、
こういうことでした。

val options = listOf("a", "b", "c")
fun joinOptions(options: Collection<String>) =
       options.joinToString( prefix = "[",postfix = "]")

< Introduction問題3: Default arguments >

Imagine you have several overloads of 'foo()' in Java:
下記はJavaにおける’foo()’関数に対するオーバーロードだ。

public String foo(String name, int number, boolean toUpperCase) {
    return (toUpperCase ? name.toUpperCase() : name) + number;
}
public String foo(String name, int number) {
    return foo(name, number, false);
}
public String foo(String name, boolean toUpperCase) {
    return foo(name, 42, toUpperCase);
}
public String foo(String name) {
    return foo(name, 42);
}	

You can replace all these Java overloads with one function in Kotlin.
Change the declaration of the foo function in a way that makes the code
using foo compile. Use default and named arguments.

Kotlinでは、これらのJavaオーバーロードを1つの関数で置き換えることができる。
default引数とnamed argumentsを利用して
foo関数を1つにまとめる方法で関数fooの宣言を変更してください。

fun foo(name: String, number: Int, toUpperCase: Boolean) =
        (if (toUpperCase) name.uppercase() else name) + number

fun useFoo() = listOf(
        foo("a"),
        foo("b", number = 1),
        foo("c", toUpperCase = true),
        foo(name = "d", number = 2, toUpperCase = true)
)

▶️ 解説


fun foo(name: String, number: Int=42, toUpperCase: Boolean=false) =
        (if (toUpperCase) name.uppercase() else name) + number

fun useFoo() = listOf(
        foo("a"),
        foo("b", number = 1),
        foo("c", toUpperCase = true),
        foo(name = "d", number = 2, toUpperCase = true)
)	

問題の最初で出てきた元のJavaコードは、
同じ名前の foo 関数を異なる引数でオーバーロードしている。
これは、名前が同じであるが引数の型や数が異なる複数の関数を定義していて、
各関数は文字列と数値の組み合わせを受け取り、それらを結合して文字列を返している。

kotlinでは、前の問題の解説でも書いたが、
"Default and named arguments"の設定ができるので、
今回の問題では、Javaで書かれたコードを簡潔に、これを使用し書くことができます。

デフォルト引数を使って numbertoUpperCase の初期値を指定することで、
引数を省略した場合にはデフォルトの値が使われ、
名前付き引数を使用して引数の順序を指定せずに関数を呼び出すことができる、という問題でした!!!

<Introduction問題4: Triple-quoted strings>

Learn about the different string literals and string templates in Kotlin.

You can use the handy library functions trimIndent and trimMargin
to format multiline triple-quoted strings in accordance with the
surrounding code.

便利なライブラリ関数trimIndenttrimMarginを使用すると、
複数行のトリプルクォート文字列を周囲のコードに従ってフォーマットできる。

Replace the trimIndent call with the trimMargin call taking # as the
prefix value so that the resulting string doesn't contain the prefix character.
trimIndentの呼び出しを、#をプレフィックス値とするtrimMarginの呼び出しに置き換えて、
結果の文字列にプレフィックス文字が含まれないようにしよう。

const val question = "life, the universe, and everything"
const val answer = 42

val tripleQuotedString = """
    #question = "$question"
    #answer = $answer""".trimIndent()

fun main() {
    println(tripleQuotedString)
}

▶️ 解説

今回の問題は、
trimIndentの呼び出しを、#をプレフィックス値とするtrimMarginの呼び出しに置き換えて、
結果の文字列にプレフィックス文字が含まれないように変更しようという問題だ。

ということで正解が、.trimIndent()の部分を以下のように変更した形だ。

const val question = "life, the universe, and everything"
const val answer = 42

val tripleQuotedString = """
    #question = "$question"
    #answer = $answer""".trimMargin("#")

fun main() {
    println(tripleQuotedString)
}	

今回の問題はそのままだったので、
今回のテーマ"Kotlinのさまざまな文字列リテラルと文字列テンプレート"の部分を
少し解説書きたいと思う。

2 種類の文字列リテラル

Kotlinには2種類の文字列リテラルが存在する。

  1. Escaped strings(escapeされた文字列)
  2. Multiline strings(複数行の文字列)

ここでは特徴的な"Multiline strings"を取り上げる。Multiline stringsは、
トリプルクォート(""")で文字列を囲むことで、改行がそのまま文字列に含まれるもの
これにより、複数の行をまたがる文字列を簡潔に表現できる。

そして、複数行の文字列の見た目を整えるのに役立つ便利な関数が今回出てきた以下の2つ。

<trimIndent>

複数行の文字列の各行の先頭から最も先頭に位置する空白を取り除く

ex.

val indentedString = """
    This is an indented
    multiline
    string.
""".trimIndent()	

これは以下のようになる。

This is an indented
multiline
string.	

また、特徴として、trimIndenttrimMarginと違って、引数を受けない。

<trimMargin>

特定の文字をマージンプレフィックスとして認識し、その文字以降の先頭の空白を取り除く。
通常、パイプ記号 | が使われるが、必要に応じて他の文字を指定することもできる。

val indentedString = """
    |This is an indented
    |multiline
    |string.
""".trimMargin()

trimIndent は各行の先頭の全ての空白を取り除き、
trimMargin は指定されたマージンプレフィックス以降の空白を取り除く。

<Introduction5: String templates>

Triple-quoted strings are not only useful for multiline strings but also for creating regex patterns as you don't need to escape a backslash with a backslash.
The following pattern matches a date in the format 13.06.1992
(two digits, a dot, two digits, a dot, four digits):
三重引用符で囲まれた文字列は、複数行の文字列に便利なだけでなく、
バックスラッシュをバックスラッシュでエスケープする必要がないため、
正規表現パターンを作成する際にも便利だ。

fun getPattern() = """\d{2}\.\d{2}\.\d{4}"""	

Using the month variable, rewrite this pattern in such a way that it matches the date in the format 13 JUN 1992
(two digits, one whitespace, a month abbreviation,
one whitespace, four digits).
変数monthを使って、このパターンを1992年6月13日の書式にマッチするように書き換えよう。

val month = "(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)"

fun getPattern(): String = TODO()

▶️ 解説

val month = "(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)"
fun getPattern(): String = """\d{2} $month \d{4}"""

トリプルクォートを使うとエスケープする必要がないため、見た目がすっきり書けるようになる。

ちなみに、ダブルクォートを使うとこのようになります。

val month = "(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)"
fun getPattern(): String = "\\d{2} $month \\d{4}"

今回はこれで以上!
また続き書きます☺️

Discussion