メインコンテンツにスキップ

[Kotlin] 拡張されたループ

· 3分の読み時間
Haril Song
Owner, Software Engineer at 42dot

Kotlinでは、Javaに比べてはるかにシンプルで便利なループを書くことができます。どのように使うか見てみましょう。

1. .. 演算子

val fruits = listOf("Apple", "Banana", "Cherry", "Durian")

fun main() {
for (index in 0..fruits.size - 1) {
val fruit = fruits[index]
println("$index: $fruit")
}
}

.. を使うと、1ずつインクリメントする従来のループが作成されます。

2. downTo

val fruits = listOf("Apple", "Banana", "Cherry", "Durian")

fun main() {
for (index in fruits.size - 1 downTo 0) {
val fruit = fruits[index]
println("$index: $fruit")
}
}

downTo を使うと、期待通りにデクリメントするループが作成されます。

3. step

val fruits = listOf("Apple", "Banana", "Cherry", "Durian")

fun main() {
for (index in 0..fruits.size - 1 step 2) {
val fruit = fruits[index]
println("$index: $fruit")
}
}

step キーワードを使うと、特定の数の要素をスキップするループを実装できます。これは downTo にも適用されます。

4. until

val fruits = listOf("Apple", "Banana", "Cherry", "Durian")

fun main() {
for (index in 0 until fruits.size) {
val fruit = fruits[index]
println("$index: $fruit")
}
}

until を使うと、最後の数を含まないループが作成され、-1 を使う必要がなくなります。

5. lastIndex

val fruits = listOf("Apple", "Banana", "Cherry", "Durian")

fun main() {
for (index in 0 .. fruits.lastIndex) {
val fruit = fruits[index]
println("$index: $fruit")
}
}

lastIndex プロパティを使うと、ループが読みやすくなります。しかし、もちろんまだ他にも方法があります。

6. indices

val fruits = listOf("Apple", "Banana", "Cherry", "Durian")

fun main() {
for (index in fruits.indices) {
val fruit = fruits[index]
println("$index: $fruit")
}
}

indices はコレクションのインデックス範囲を返します。

7. withIndex()

val fruits = listOf("Apple", "Banana", "Cherry", "Durian")

fun main() {
for ((index, fruit) in fruits.withIndex()) {
println("$index: $fruit")
}
}

withIndex() を使うと、インデックスと値を同時に抽出でき、コードがシンプルになります。これはPythonのシンプルさに似ています。これでほとんどのループシナリオに対応できますが、もう一つ方法があります。

8. forEachIndexed

val fruits = listOf("Apple", "Banana", "Cherry", "Durian")

fun main() {
fruits.forEachIndexed { index, fruit ->
println("$index: $fruit")
}
}

forEachIndexed にラムダ関数を使うと、コードがより簡潔で直感的になります。ニーズに合った適切な方法を選びましょう。


参考

Kotlin Tips: Loops

Kotlinで地球の楕円体を利用する

· 4分の読み時間
Haril Song
Owner, Software Engineer at 42dot

背景

earth 画像の参照1

地球が平らでも完全な球体でもなく、不規則な楕円体であることを考えると、異なる経度と緯度の2点間の距離を迅速かつ正確に計算するための完璧な公式は存在しません。

しかし、geotoolsライブラリを使用することで、数学的に補正された近似値を簡単に取得することができます。

依存関係の追加

geotoolsで地球の楕円体を使用するには、関連するライブラリの依存関係を追加する必要があります。

repositories {
maven { url "https://repo.osgeo.org/repository/release/" }
maven { url "https://download.osgeo.org/webdav/geotools/" }
mavenCentral()
}

dependencies {
...
implementation 'org.geotools:gt-referencing:26.2'
...
}

コードの記述

まず、ソウルと釜山の座標をenumクラスとして定義します。

enum class City(val latitude: Double, val longitude: Double) {
SEOUL(37.5642135, 127.0016985),
BUSAN(35.1104, 129.0431);
}

次に、テストコードを通じて簡単な使用例を見てみましょう。

class EllipsoidTest {

@Test
internal fun createEllipsoid() {
val ellipsoid = DefaultEllipsoid.WGS84 // GPSで使用されるWGS84測地系を使用して、地球に最も近い楕円体を作成

val isSphere = ellipsoid.isSphere // 球体か楕円体かを判定
val semiMajorAxis = ellipsoid.semiMajorAxis // 赤道半径、楕円体の長い半径
val semiMinorAxis = ellipsoid.semiMinorAxis // 極半径、楕円体の短い半径
val eccentricity = ellipsoid.eccentricity // 離心率、楕円体が球体にどれだけ近いかを示す
val inverseFlattening = ellipsoid.inverseFlattening // 逆扁平率の値
val ivfDefinitive = ellipsoid.isIvfDefinitive // この楕円体に対して逆扁平率が決定的かどうかを示す

// 大円距離
val orthodromicDistance = ellipsoid.orthodromicDistance(
City.SEOUL.longitude,
City.SEOUL.latitude,
City.BUSAN.longitude,
City.BUSAN.latitude
)

println("isSphere = $isSphere")
println("semiMajorAxis = $semiMajorAxis")
println("semiMinorAxis = $semiMinorAxis")
println("eccentricity = $eccentricity")
println("inverseFlattening = $inverseFlattening")
println("ivfDefinitive = $ivfDefinitive")
println("orthodromicDistance = $orthodromicDistance")
}
}
isSphere = false
semiMajorAxis = 6378137.0
semiMinorAxis = 6356752.314245179
eccentricity = 0.08181919084262128
inverseFlattening = 298.257223563
ivfDefinitive = true
orthodromicDistance = 328199.9794919944

DefaultEllipsoid.WGS84を使用して地球の楕円体を作成できます。WGS84の代わりにSPHEREを使用すると、半径6371kmの球体が作成されます。

距離の結果はメートル(m)で表示されるため、キロメートルに変換すると約328kmになります。Googleで検索すると325kmと表示されるかもしれませんが、私が選んだ座標とGoogleが選んだ座標の違いを考慮すると、これは悪くない数字です。

他にも多くの機能がありますが、すべてをこの投稿でカバーするのは難しいため、必要に応じて別の投稿で取り上げます。

情報

ビジネス要件によっては誤差が満足できない場合があるため、実際の実装前にgeotoolsの他の方法を十分にテストしてください。


Footnotes

  1. SRIDと座標系の概要

@JsonNamingの使い方

· 3分の読み時間
Haril Song
Owner, Software Engineer at 42dot

APIで使用されるJSONの命名規則が、アプリケーション内の命名戦略と異なる場合があります。

{
"Title": "Frozen",
"Year": "2013",
"Type": "movie",
"Poster": "https://m.media-amazon.com/images/M/MV5BMTQ1MjQwMTE5OF5BMl5BanBnXkFtZTgwNjk3MTcyMDE@._V1_SX300.jpg",
"imdbID": "tt2294629"
}
private String title;
private String year;
private String imdbId;
private String type;
private String poster;

変数名がJSONのキーと一致しない場合、データは正しくマッピングされません。

このような場合、プロジェクト内の変数名を変更せずにデータをマッピングするために@JsonProperty(value)を使用できます。しかし、多くのフィールドが異なる命名戦略を持つ場合、各フィールドに@JsonProperty(value)を使用すると、コードが多くのアノテーションで乱雑になります。

ここで@JsonNamingアノテーションが役立ちます。クラス全体の命名戦略を一度に変更することができます。

@JsonNaming

v2.12以前

以下のようにエレガントに解決できます:

@Data
@JsonNaming(value = PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class Movie {

private String title;
private String year;

@JsonProperty("imdbID") // 必要な場合のみ!
private String imdbId;
private String type;
private String poster;

}

image この方法は非推奨で、取り消し線が引かれています。

しかし、この方法はJackson 2.12以降非推奨となったため、新しいアプローチを見てみましょう。

v2.12以降

バージョン2.12以降では、PropertyNamingStrategiesを使用する必要があります。

@JsonNaming(value = PropertyNamingStrategies.UpperCamelCaseStrategy.class)

内部実装の詳細な説明は長くなりすぎるため割愛しますが、非常に興味深い実装となっているので、一度見てみることをお勧めします!

情報

簡単に言うと、更新された内部実装にはNamingBaseという抽象クラスが含まれており、これは元のPropertyNamingStrategyを継承し、その後命名戦略がNamingBaseを継承します。NamingBaseは中間実装クラスとして使用されます。