Kotlin的出现无疑是为了超越Java而存在。在Google I/O 2017中,Google 宣布 Kotlin 成为 Android 官方开发语言,背景就是Oracle告Google侵权使用java。众所周知,Java的跨平台的开发语言,得益于虚拟机。我比较关注Kotlin用于Android、IOS、与数据科学研究(机器学习范畴)。
目录
Kotlin多平台
Kotlin 多平台使用场景
Android 与 iOS 应用程序
全栈 web 应用程序
多平台库
移动端与 web 应用程序的公共代码
Kotlin 多平台的工作原理
不同平台间共享代码
入门
样例项目
Kotlin 用于服务器端开发
使用 Kotlin 进行服务器端开发的框架
部署 Kotlin 服务器端应用程序
Kotlin 用于服务器端的产品
Kotlin 用于 Android 开发
Kotlin 用于 JavaScript 开发
Kotlin/JS IR 编译器
Kotlin/JS 的使用场景
Kotlin 原生
为什么选用 Kotlin/Native?
目标平台
互操作
在多个平台之间共享代码
如何入门
教程与文档
Kotlin 用于数据科学
交互式编辑器
Jupyter Kotlin 内核
Kotlin Notebooks in Datalore
Zeppelin Kotlin 解释器
类库
Kotlin 库
Java 库
Kotlin 用于竞技程序设计
简单示例:可达数问题
函数式操作示例:长数问题
更多提示与技巧
学习 Kotlin
Kotlin多平台
Kotlin 多平台处于 Beta 阶段。它几乎是稳定的,但是将来可能需要迁移步骤。 我们会尽最大努力来减少开发者需要做的变更。
Kotlin 多平台使用场景
Android 与 iOS 应用程序
在移动平台间共享代码是 Kotlin 多平台的主要使用场景之一。 借助 Kotlin 多平台移动端, 可以构建跨平台移动端应用程序并在 Android 与 iOS 之间共享公共代码,例如业务逻辑、连接性等。
查看 Kotlin 多平台移动端入门与使用 Ktor 与 SQLDelight 创建多平台应用教程, 其中会创建包含两个平台共享代码的模块的 Android 与 iOS 应用程序。
全栈 web 应用程序
Another scenario when code sharing may bring benefits is a connected application where the logic can be reused on both the server and the client side running in the browser. This is covered by Kotlin Multiplatform as well.
See Build a full-stack web app with Kotlin Multiplatform tutorial, where you will create a connected application consisting of a server part, using Kotlin/JVM and a web client, using Kotlin/JS.
多平台库
Kotlin Multiplatform is also useful for library authors. You can create a multiplatform library with common code and its platform-specific implementations for JVM, JS, and Native platforms. Once published, a multiplatform library can be used in other cross-platform projects as a dependency.
See the Create and publish a multiplatform library tutorial, where you will create a multiplatform library, test it, and publish it to Maven.
移动端与 web 应用程序的公共代码
One more popular case for using Kotlin Multiplatform is sharing the same code across Android, iOS, and web apps. It reduces the amount of business logic coded by frontend developers and helps implement products more efficiently, decreasing the coding and testing efforts.
See the RSS Reader sample project — a cross-platform application for iOS and Android with desktop and web clients implemented as experimental features.
Kotlin 多平台的工作原理
- 公共 Kotlin 包括语言、核心库与基本工具。 用公共 Kotlin 编写的代码适用于所有平台的各个地方。
- 借助 Kotlin 多平台库,可以在公共代码以及平台相关代码中复用多平台逻辑。 公共代码可以依赖一组涵盖日常任务的库,例如 HTTP、 序列化(serialization)与管理协程(coroutines)。
- 如需与平台互操作,请使用平台相关的 Kotlin 版本。平台相关的 Kotlin 版本 (Kotlin/JVM、 Kotlin/JS、 Kotlin/Native)包含对 Kotlin 语言的扩展以及平台相关的库与工具。
- 通过这些平台可以访问平台原生代码(JVM、 JS 与 Native)并利用所有原生功能。
不同平台间共享代码
使用 Kotlin 多平台,花更少的时间为不同平台编写并维护相同的代码 ——只需使用 Kotlin 提供的机制进行共享即可:
- 在项目中用到的所有平台之间共享代码。 用以共享适用于所有平台的公共业务逻辑。
-
在项目中包含的某些平台(而不是所有平台)间共享代码。 当可以对类似的平台共享大量代码时请这么用。
-
如果需要从共享代码中访问平台相关的 API,请使用 Kotlin 的预期声明与实际声明机制。
入门
- Start with the Get started with Kotlin Multiplatform Mobile if you want to create iOS and Android applications with shared code
- Look through sharing code principles and examples if you want to create applications or libraries targeting other platforms
New to Kotlin? Take a look at Getting started with Kotlin.
样例项目
浏览跨平台应用程序样例以了解 Kotlin 多平台的工作原理:
- Kotlin 多平台移动端样例
- KotlinConf 应用
- KotlinConf Spinner 应用
- 使用 Kotlin 多平台构建全栈 web 应用
Kotlin 用于服务器端开发
Kotlin 非常适合开发服务器端应用程序。它可以让你编写简明且表现力强的代码, 同时保持与现有基于 Java 的技术栈的完全兼容性以及平滑的学习曲线:
- 表现力:Kotlin 的革新式语言功能,例如支持类型安全的构建器和委托属性,有助于构建强大而易于使用的抽象。
- 可伸缩性:Kotlin 对协程的支持有助于构建服务器端应用程序, 伸缩到适度的硬件要求以应对大量的客户端。
- 互操作性:Kotlin 与所有基于 Java 的框架完全兼容,因此可以使用你熟悉的技术栈,同时获得更现代化语言的优势。
- 迁移:Kotlin 支持大型代码库从 Java 到 Kotlin 逐步迁移。你可以开始用 Kotlin 编写新代码,同时系统中较旧部分继续用 Java。
- 工具:除了很棒的 IDE 支持之外,Kotlin 还为 IntelliJ IDEA Ultimate 的插件提供了框架特定的工具(例如 Spring)。
- 学习曲线:对于 Java 开发人员,Kotlin 入门很容易。包含在 Kotlin 插件中的自动 Java-to-Kotlin 的转换器有助于迈出第一步。Kotlin 心印 通过一系列互动练习提供了语言主要功能的指南。
使用 Kotlin 进行服务器端开发的框架
-
Spring 利用 Kotlin 的语言功能提供更简洁的 API, 从版本 5.0 开始。在线项目生成器可以让你用 Kotlin 快速生成一个新项目。
-
Vert.x 是在 JVM 上构建反应式 Web 应用程序的框架, 为 Kotlin 提供了专门支持,包括完整的文档。
-
Ktor 是 JetBrains 为在 Kotlin 中创建 Web 应用程序而构建的框架,利用协程实现高可伸缩性,并提供易于使用且合乎惯用法的 API。
-
kotlinx.html 是可在 Web 应用程序中用于构建 HTML 的 DSL。 它可以作为传统模板系统(如JSP和FreeMarker)的替代品。
-
Micronaut 是基于 JVM 的现代全栈框架,用于构建模块化、易于测试的微服务与无服务器应用程序。它带有许多有用的内置特性。
-
http4k是一个纯 Kotlin 编写、占用空间很小的用于 Kotlin HTTP 应用程序的函数式工具包。 该库基于 Twitter 的论文《你的服务器即函数》(Your Server as a Function),并将 HTTP 服务器与客户端都建模为可以组合起来的简单 Kotlin 函数。
-
Javalin 是用于 Kotlin 与 Java 的非常轻量级的 Web 框架,支持 WebSockets、HTTP2 与异步请求。
-
通过相应 Java 驱动程序进行持久化的可用选项包括直接 JDBC 访问、JPA 以及使用 NoSQL 数据库。 对于 JPA,kotlin-jpa 编译器插件使 Kotlin 编译的类适应框架的要求。
部署 Kotlin 服务器端应用程序
Kotlin 应用程序可以部署到支持 Java Web 应用程序的任何主机,包括 Amazon Web Services、 Google Cloud Platform 等。
要在 Heroku 上部署 Kotlin 应用程序,可以按照 Heroku 官方教程来做。
AWS Labs 提供了一个示例项目,展示了 Kotlin 编写 AWS Lambda 函数的使用。
谷歌云平台(Google Cloud Platform)提供了一系列将 Kotlin 应用程序部署到 GCP 的教程,包括 Ktor 与 App Engine 应用及 Spring 与 App engine 应用。此外, 还有一个交互式代码实验室(interactive code lab)用于部署 Kotlin Spring 应用程序。
Kotlin 用于服务器端的产品
Corda 是一个开源的分布式分类帐平台,由各大银行提供支持 ,完全由 Kotlin 构建。
JetBrains 账户,负责 JetBrains 整个许可证销售和验证过程的系统 100% 由 Kotlin 编写,自 2015 年生产运行以来,一直没有重大问题。
Kotlin 用于 Android 开发
自 2019 年 Google I/O 以来,Kotlin 就成为了 Android 移动开发的首选。
使用 Kotlin 进行 Android 开发,可以受益于:
- 代码更少、可读性更强。花更少的时间来编写代码与理解他人的代码。
- 成熟的语言与环境。自 2011 年创建以来,Kotlin 不仅通过语言而且通过强大的工具在整个生态系统中不断发展。 现在,它已无缝集成到 Android Studio 中, 并被许多公司积极用于开发 Android 应用程序。
- Android Jetpack 与其他库中的 Kotlin 支持。KTX 扩展 为现有的 Android 库添加了 Kotlin 语言特性,如协程、扩展函数、lambdas 与命名参数。
- 与 Java 的互操作性。可以在应用程序中将 Kotlin 与 Java 编程语言一起使用, 而无需将所有代码迁移到 Kotlin。
- 支持多平台开发。不仅可以使用 Kotlin 开发 Android,还可以开发 iOS、后端与 Web 应用程序。 享受在平台之间共享公共代码的好处。
- 代码安全。更少的代码与更好的可读性导致更少的错误。Kotlin 编译器检测这些剩余的错误,从而使代码安全。
- 易学易用。Kotlin 非常易于学习,尤其是对于 Java 开发人员而言。
- 大社区。Kotlin 得到了社区的大力支持与许多贡献,该社区在全世界范围内都在增长。 根据 Google 的说法,Play 商店前 1000 个应用中有 60% 以上使用 Kotlin。
许多初创公司与财富 500 强公司已经使用 Kotlin 开发了 Android 应用程序——详情请见面向 Kotlin 开发者的谷歌网站。
如果想开始使用 Kotlin 进行 Android 开发,请参阅在 Android 开发中开始使用 Kotlin。
如果是 Android 的新手,并且想学习使用 Kotlin 创建应用程序,请查看这门 Udacity 课程。
Kotlin 用于 JavaScript 开发
Kotlin/JS 提供了转换 Kotlin 代码、Kotlin 标准库的能力,并且兼容 JavaScript 的任何依赖项。Kotlin/JS 的当前实现以 ES5 为目标。
使用 Kotlin/JS 的推荐方法是通过 kotlin.js
与 kotlin.multiplatform
Gradle 插件。它们提供了一种集中且便捷的方式来设置与控制以 JavaScript 为目标的 Kotlin 项目。 这包括基本特性,例如控制应用程序的捆绑,直接从 npm 添加 JavaScript 依赖项等等。要获得可用选项的概述,请查看搭建 Kotlin/JS 项目文档。
Kotlin/JS IR 编译器
Kotlin/JS IR 编译器相对于旧版默认编译器进行了许多改进。 例如,通过消除死代码来减小生成的可执行文件的体积, 并提供了与 JavaScript 生态系统及其工具更加流畅的互操作性。
自 Kotlin 1.8.0 版起,旧编译器已弃用。
通过从 Kotlin 代码生成 TypeScript 声明文件(d.ts
),IR 编译器使创建混合 TypeScript 与 Kotlin 代码的“混合” 应用程序变得更加容易,并利用 Kotlin 多平台代码共享功能。
如需了解关于 Kotlin/JS IR 编译器中可用特性的更多信息,以及如何在项目中尝试使用它,请访问 Kotlin/JS IR 编译器文档页及其迁移指南。
Kotlin/JS 的使用场景
有很多使用 Kotlin/JS 的方式。这里列出了可以使用 Kotlin/JS 的场景的一个不完全的清单。
-
使用 Kotlin/JS 编写 Web 前端应用程序
- Kotlin/JS 允许以类型安全的方式 利用功能强大的浏览器与 Web API。创建、修改文档对象模型(DOM)中的元素并与之交互,使用 Kotlin 代码控制
canvas
或 WebGL 组件的呈现, 并享受现代浏览器所支持的更多特性的访问。 - 使用 JetBrains 提供的 kotlin-wrappers 用 Kotlin/JS 编写完整的,类型安全的 React 应用程序,它为 React 及其他流行 JavaScript 框架提供方便的抽象与深度集成。
kotlin-wrappers
还为许多类似技术(例如react-redux
、react-router
以及styled-components
)提供支持。 与 JavaScript 生态系统的互操作性意味着可以使用第三方 React 组件与组件库。 - 使用 Kotlin/JS 框架,充分利用 Kotlin 相关概念及其表现力与简洁性(例如 kvision 或 fritz2)。
- Kotlin/JS 允许以类型安全的方式 利用功能强大的浏览器与 Web API。创建、修改文档对象模型(DOM)中的元素并与之交互,使用 Kotlin 代码控制
-
使用 Kotlin/JS 编写服务器端与无服务器应用程序
- Kotlin/JS 提供的 Node.js 目标能够创建在服务器上运行或在无服务器基础架构上执行的应用程序。可以享受在 JavaScript 运行时中执行的所有优势,例如更快的启动与更少的内存占用。使用 kotlinx-nodejs, 可以直接从 Kotlin 代码中对 Node.js API 进行类型安全的访问。
-
使用 Kotlin 的多平台项目与其他 Kotlin 目标共享代码
- 使用
multiplatform
多平台 Gradle 插件时,也可以访问所有 Kotlin/JS 功能。 - 如果用 Kotlin 编写的后端,那么可以与用 Kotlin/JS 编写的前端共享公共代码,例如数据模型或逻辑验证,从而能够编写与维护全栈 Web 应用程序。
- 还可以在 Web 界面与移动应用之间共享业务逻辑(Android 与 iOS),并避免重复实现常见的功能,例如围绕 REST API 端点提供抽象,用户身份验证或者领域模型。
- 使用
-
创建用于 JavaScript 与 TypeScript 的库
- 也不必用 Kotlin/JS 编写整个应用程序——而是可以从 Kotlin 代码生成库, 这些库可以在 JavaScript 或 TypeScript 编写的任何代码库中作为模块使用,而与所使用的其他框架或技术无关。这种创建混合应用程序的方法可以利用个人与团队在 Web 开发方面已经具备的能力,同时减少重复的工作量、 使 Web 目标与应用程序的其他目标平台保持一致变得更加容易。
当然,这并不是如何充分利用 Kotlin/JS 的完整列表,而只是一些精选的使用场景。 请尝试不同的组合,并找出最适合项目的方案。
无论具体用例如何,Kotlin/JS 项目都可以使用兼容Kotlin 生态系统中的库, 以及第三方的JavaScript 与 TypeScript 生态系统中的库。如果要在 Kotlin 代码中使用后者, 可以提供自己的类型安全包装器、使用社区维护的包装器, 也可以让 Dukat 自动生成 Kotlin 声明。使用 Kotlin/JS 专有的动态类型可以放宽 Kotlin 的类型系统的约束,而跳过创建详细的库包装器,尽管这是以牺牲类型安全为代价。
Kotlin/JS 还与最常见的模块系统兼容:UMD、CommonJS 与 AMD。能够生产与使用模块意味着能够以结构化的方式与 JavaScript 生态系统进行交互。
Kotlin 原生
Kotlin/Native 是一种将 Kotlin 代码编译为无需虚拟机就可运行的原生二进制文件的技术。 Kotlin/Native 包含一个基于 LLVM 的 Kotlin 编译器后端以及 Kotlin 标准库的原生实现。
为什么选用 Kotlin/Native?
Kotlin/Native 的主要设计目标是让 Kotlin 可以为不希望或者不可能使用 虚拟机 的平台(例如嵌入式设备或者 iOS)编译。 它非常适合开发人员需要生成无需额外运行时或虚拟机的自包含程序的情况。
目标平台
Kotlin/Native 支持以下平台:
- macOS
- iOS、 tvOS、 watchOS
- Linux
- Windows(MinGW)
- Android NDK
如需编译苹果目标平台 macOS、 iOS、 tvOS 以及 watchOS,需要安装 Xcode 及其命令行工具。
参见所支持目标的完整列表。
互操作
Kotlin/Native 支持与不同操作系统的原生编程语言的双向互操作。 编译器可创建:
- 用于多个平台的可执行文件
- 用于 C/C++ 项目的静态库或动态库以及 C 语言头文件
- 用于Swift 与 Objective-C 项目的 Apple 框架
支持直接在 Kotlin/Native 中使用以下现有库的互操作:
- 静态或动态 C 语言库
- C 语言、 Swift 以及 Objective-C 框架
将编译后的 Kotlin 代码包含进用 C、 C++、 Swift、 Objective-C 以及其他语言编写的现有项目中会很容易。 直接在 Kotlin/Native 中使用现有原生代码、 静态或动态 C 语言库、 Swift/Objective-C 框架、 图形引擎以及任何其他原生内容也很容易。
Kotlin/Native 库有助于在多个项目之间共享 Kotlin 代码。 POSIX、 gzip、 OpenGL、 Metal、 Foundation 以及许多其他流行库与 Apple 框架都已预先导入并作为 Kotlin/Native 库包含在编译器包中。
在多个平台之间共享代码
多平台项目允许在多个平台之间共享公共的 Kotlin 代码,包括:Android、iOS、JVM、JavaScript 与原生。 多平台库为公共 Kotlin 代码提供了所需的 API,并且有助于在一处用 Kotlin 开发项目的共享部分, 并将其与一些或所有目标平台共享。
可以使用 Kotlin 移动端多平台通过 Android 与 iOS 之间共享代码创建多平台移动应用程序。
如何入门
教程与文档
刚接触 Kotlin?可以看看 Kotlin 入门页。
推荐文档:
- Kotlin 移动端多平台文档
- 多平台文档
- C 语言互操作
- Swift/Objective-C 互操作
推荐教程:
- Kotlin/Native 入门
- 创建第一个跨平台移动端应用程序
- C 语言 Kotlin/Native 之间的类型映射
- Kotlin/Native 开发动态库
- Kotlin/Native 开发 Apple 框架
Kotlin 用于数据科学
从构建数据流水线到生产机器学习模型, Kotlin 可能是处理数据的绝佳选择:
- Kotlin 简洁、易读且易于学习。
- 静态类型与空安全有助于创建可靠的、可维护的、易于故障排除的代码。
- 作为一种 JVM 语言,Kotlin 提供了出色的性能表现, 并具有充分利用久经考验的 Java 库的整个生态系统的能力。
交互式编辑器
Jupyter Notebook、 Datalore 与 Apache Zeppelin 等笔记本为数据可视化与探索性研究提供了方便的工具。 Kotlin 与这些工具集成在一起,可以帮助探索数据、与同事共享发现或建立数据科学和机器学习技能。
Jupyter Kotlin 内核
Jupyter Notebook 是一个开源 Web 应用程序, 它允许创建与共享包含代码、可视化与 Markdown 文本的文档(也称为“笔记本”)。 Kotlin-jupyter 是一个开源项目, 它为 Jupyter Notebook 带来了 Kotlin 支持。
查看 Kotlin 内核的 GitHub 仓库 以获取安装说明、文档与示例。
Kotlin Notebooks in Datalore
With Datalore, you can use Kotlin in the browser straight out of the box, no installation required. You can also collaborate on Kotlin notebooks in real time, get smart coding assistance when writing code, and share results as interactive or static reports. Check out a sample report.
Sign up and use Kotlin with a free Datalore Community account.
Zeppelin Kotlin 解释器
Apache Zeppelin 是一种流行的基于 Web 的交互式数据分析解决方案。 它为 Apache Spark 集群计算系统提供了强大的支持, 这对数据工程特别有用。 从版本 0.9.0 开始,Apache Zeppelin 内置了 Kotlin 解释器。
类库
Kotlin 社区创建的用于数据相关任务的类库生态系统正在迅速扩展。 以下是一些可能会有用的库:
Kotlin 库
-
Multik: multidimensional arrays in Kotlin. The library provides Kotlin-idiomatic, type- and dimension-safe API for mathematical operations over multidimensional arrays. Multik offers swappable JVM and native computational engines, and a combination of the two for optimal performance.
-
KotlinDL is a high-level Deep Learning API written in Kotlin and inspired by Keras. It offers simple APIs for training deep learning models from scratch, importing existing Keras models for inference, and leveraging transfer learning for tweaking existing pre-trained models to your tasks.
-
Kotlin DataFrame is a library for structured data processing. It aims to reconcile Kotlin's static typing with the dynamic nature of data by utilizing both the full power of the Kotlin language and the opportunities provided by intermittent code execution in Jupyter notebooks and REPLs.
-
Kotlin for Apache Spark adds a missing layer of compatibility between Kotlin and Apache Spark. It allows Kotlin developers to use familiar language features such as data classes, and lambda expressions as simple expressions in curly braces or method references.
-
kotlin-statistics 是一个为探索性统计与生产统计中提供扩展函数的库。它支持基本的数字列表/序列/数组函数(从
sum
到skewness
)、 切片操作符(诸如countBy
、simpleRegressionBy
)、分箱(binning)操作符、离散 PDF 采样、 朴素贝叶斯分类器、聚类、线性回归等等。 -
kmath is an experimental library that was intially inspired by NumPy but evolved to more flexible abstractions. It implements mathematical operations combined in algebraic structures over Kotlin types, defines APIs for linear structures, expressions, histograms, streaming operations, provides interchangeable wrappers over existing Java and Kotlin libraries including ND4J, Commons Math, Multik, and others.
-
krangl 是一个受 R 语言的 dplyr 与 Python 的 pandas 启发的库。这个库提供了采用函数式风格 API 进行数据操作的功能;它还包括过滤、转换、聚合与重塑表格数据的函数。
-
lets-plot 是一个用 Kotlin 编写的统计数据绘图库。 Lets-Plot 是多平台的,不仅可以用于 JVM,还可以用于 JS 与 Python。
-
kravis 是另一个用于表格数据可视化的库,其灵感来自于 R 的 ggplot。
-
londogard-nlp-toolkit is a library that provides utilities when working with natural language processing such as word/subword/sentence embeddings, word-frequencies, stopwords, stemming, and much more.
Java 库
因为 Kotlin 提供了与 Java 互操作的头等支持,所以也可以在用于数据科学的 Kotlin 代码中使用 Java 库。 以下是这些库的一些示例:
-
DeepLearning4J——一个 Java 深度学习库
-
ND4J——用于 JVM 的高效矩阵数学库
-
Dex——一个基于 Java 的数据可视化工具
-
Smile——一个全面的机器学习、自然语言处理、 线性代数、图、插值与可视化系统。除了 Java API,Smile 还提供了函数式的 Kotlin API 以及 Scala 与 Clojure API。
- Smile-NLP-kt——以 Kotlin 扩展函数与接口格式重写了 Smile 的自然语言处理部分的 Scala 隐式内容。
-
Apache Commons Math——一个 Java 通用数学、统计与机器学习库
-
NM Dev - a Java mathematical library that covers all of classical mathematics.
-
OptaPlanner——一个用于优化规划问题的求解器实用程序
-
Charts——一个正在开发中的科学 JavaFX 图表库
-
Apache OpenNLP - a machine learning based toolkit for the processing of natural language text
-
CoreNLP——一个自然语言处理工具包
-
Apache Mahout——一个回归、聚类与推荐的分布式框架
-
Weka——一组用于数据挖掘任务的机器学习算法
-
Tablesaw - a Java dataframe. It includes a visualization library based on Plot.ly
如果这个列表还不能满足需求,可以在 Thomas Nield 的 Kotlin Machine Learning Demos GitHub repository with showcases 中找到更多选项。
Kotlin 用于竞技程序设计
本教程适用于之前未使用 Kotlin 的竞技程序员,也适用于之前从未参与过任何竞技性程序设计活动的 Kotlin 开发人员。 本教程假定读者具有相应的编程技能。
竞技性程序设计是一项智力运动,参赛选手在严格的限制条件下编写程序精确地解决指定的算法问题。问题可以简单到任何软件开发人员都能解题、只需很少代码就能得到正确答案,也可以复杂到需要特殊的算法、数据结构知识以及大量实践。虽然 Kotlin 不是专为竞技性编程而设计的,但是它恰好适合这一领域,显著减少了程序员所需编写与阅读的样板代码量,这样几乎可以像动态脚本语言一样编写代码,同时又有静态类型语言的工具与性能支持。
关于如何搭建 Kotlin 开发环境,请参见 Kotlin/JVM 入门。在竞技程序设计中,通常会创建单个项目,而每个问题的答案写在单个源文件中。
简单示例:可达数问题
我们来看一个具体的示例。
Codeforces 第 555 轮第 3 次分赛已于 4 月 26 日举行,意味着它有适合任何开发者尝试的问题。 可以打开这个链接来阅读问题。 这组问题中最简单的是问题 A:可达数。 它要求实现问题陈述中所描述的简单算法。
我们会通过创建一个任意名称的 Kotlin 源文件来解这个问题。A.kt
就挺好。 首先,需要实现问题陈述中指定的函数,如下:
我们以这样的方式来表示函数 f(x):将 x 加 1,然后,如果得到的数至少以一个零结尾, 就去掉这个零。
Kotlin 是一门实用且不拘一格的语言,既支持命令式也支持函数式编程风格, 而不会将开发人员推向任何一种风格。可以按函数式风格实现函数 f
,使用像尾递归这样的 Kotlin 特性:
tailrec fun removeZeroes(x: Int): Int =
if (x % 10 == 0) removeZeroes(x / 10) else x
fun f(x: Int) = removeZeroes(x + 1)
也可以编写函数 f
的命令式实现,使用传统的 while 循环与可变变量(在 Kotlin 中用 var 表示):
fun f(x: Int): Int {
var cur = x + 1
while (cur % 10 == 0) cur /= 10
return cur
}
由于普遍使用类型推断,在 Kotlin 中很多地方的类型都是可选的,不过每个声明仍然具有编译期已知的明确定义的静态类型。
现在就只剩编写读取输入的主函数并实现问题陈述所要求算法的其余部分——计算在对标准输入所给出的初始数 n
重复应用函数 f
时所产生的不同整数的个数。
默认情况下,Kotlin 在 JVM 上运行,可以直接访问丰富且高效的集合库,其中包含通用的集合与数据结构,如动态大小的数组(ArrayList
)、 基于哈希的 map 与 set(HashMap
/HashSet
)、基于树的 map 与 set(TreeMap
/TreeSet
)。 使用整数哈希 set 来跟踪应用函数 f
时已达到的值, 该问题解法的一个简单命令式版本可以这样编写:
【Kotlin 1.6.0 及更高版本】
fun main() {
var n = readln().toInt() // 读取输入的整数
val reached = HashSet<Int>() // 可变的哈希 set
while (reached.add(n)) n = f(n) // 迭代函数 f
println(reached.size) // 输出答案
}
在竞技程序设计中无需处理输入格式错误的情况。 竞技程序设计中的输入格式向来都是精确指定的,并且实际输入不能偏离问题陈述中的输入规范。 That's why you can use Kotlin's readln() function. 它断言输入的字符串存在, 如不存在则抛出异常。 同样,如果输入不是整数,那么 String.toInt() 函数会抛出异常。
【早期版本】
fun main() {
var n = readLine()!!.toInt() // 读取输入的整数
val reached = HashSet<Int>() // 可变的哈希 set
while (reached.add(n)) n = f(n) // 迭代函数 f
println(reached.size) // 输出答案
}
请注意 readLine() 函数调用之后的空断言操作符 !!
的使用。 Kotlin 的 readLine()
函数定义为返回可空类型 String?
并且在输入结束时返回 null
,这样显式迫使开发人员处理缺少输入的情况。
在竞技程序设计中无需处理输入格式错误的情况。 在竞技程序设计中,输入格式总是精确指定并且实际输入不能偏离问题陈述中的输入规范。 这就是空断言操作符 !!
本质上所做的—— 它断言输入字符串存在否则抛出异常。 同理, String.toInt() 也是这样。
所有在线竞技程序设计活动都允许使用预编写代码,因此可以定义自己的面向竞技程序设计的工具函数库,以使实际解题代码更易于读写。然后,可以使用该代码作为解题模板。例如,可以定义以下辅助函数来读取竞技程序设计中的输入:
【Kotlin 1.6.0 及更高版本】
private fun readStr() = readln() // 一行字符串
private fun readInt() = readStr().toInt() // 单个整数
// 用于在解题中会用到的其他类型的类似声明等
【早期版本】
private fun readStr() = readLine()!! // 一行字符串
private fun readInt() = readStr().toInt() // 单个整数
// 用于在解题中会用到的其他类型的类似声明等
请注意这里使用了 private
(私有)可见修饰符。 虽然可见性修饰符的概念与竞技程序设计并无瓜葛, 但是它让你能够将基于相同模板的多个解题文件放在同一包中,而不会出现公有声明冲突的报错。
函数式操作示例:长数问题
对于更复杂的问题,Kotlin 丰富的集合函数式操作库就派上用场了, 可以大幅减少模板代码,并将代码写成从上到下、从左到右的流式数据转换流水线。例如问题 B:长数问题用一个简单的贪心算法实现,可以采用这种风格编写而无需任何可变变量:
【Kotlin 1.6.0 及更高版本】
fun main() {
// 读取输入
val n = readln().toInt()
val s = readln()
val fl = readln().split(" ").map { it.toInt() }
// 定义局部函数 f
fun f(c: Char) = '0' + fl[c - '1']
// 贪婪查找第一个与最后一个索引
val i = s.indexOfFirst { c -> f(c) > c }
.takeIf { it >= 0 } ?: s.length
val j = s.withIndex().indexOfFirst { (j, c) -> j > i && f(c) < c }
.takeIf { it >= 0 } ?: s.length
// 组合并写出答案
val ans =
s.substring(0, i) +
s.substring(i, j).map { c -> f(c) }.joinToString("") +
s.substring(j)
println(ans)
}
【早期版本】
fun main() {
// 读取输入
val n = readLine()!!.toInt()
val s = readLine()!!
val fl = readLine()!!.split(" ").map { it.toInt() }
// 定义局部函数 f
fun f(c: Char) = '0' + fl[c - '1']
// 贪婪查找第一个与最后一个索引
val i = s.indexOfFirst { c -> f(c) > c }
.takeIf { it >= 0 } ?: s.length
val j = s.withIndex().indexOfFirst { (j, c) -> j > i && f(c) < c }
.takeIf { it >= 0 } ?: s.length
// 组合并写出答案
val ans =
s.substring(0, i) +
s.substring(i, j).map { c -> f(c) }.joinToString("") +
s.substring(j)
println(ans)
}
在这段密集的代码中,除了集合转换之外,还可以看到像局部函数以及 elvis 操作符 ?:
这样灵便的 Kotlin 特性, 通过 elvis 操作符,可以用简洁易读的表达式如 .takeIf { it >= 0 } ?: s.length
来表达类似“如果是正数就取其值,否则取长度”的惯用法, 当然,也完全可以使用 Kotlin 创建额外的可变变量并以命令式风格表达相同的代码。
为了让这种竞技程序设计任务中读取输入更简洁, 可以使用以下输入读取辅助函数列表:
【Kotlin 1.6.0 及更高版本】
private fun readStr() = readln() // 一行字符串
private fun readInt() = readStr().toInt() // 单个整数
private fun readStrings() = readStr().split(" ") // 字符串列表
private fun readInts() = readStrings().map { it.toInt() } // 整数列表
【早期版本】
private fun readStr() = readLine()!! // 一行字符串
private fun readInt() = readStr().toInt() // 单个整数
private fun readStrings() = readStr().split(" ") // 字符串列表
private fun readInts() = readStrings().map { it.toInt() } // 整数列表
有了这些辅助函数,读取输入的代码部分变得更简单,一行行地严格遵循问题陈述中的输入规范:
// 读取输入
val n = readInt()
val s = readStr()
val fl = readInts()
请注意,在竞技程序设计中,习惯给变量取比通常在工业编程实践中更短的名称,因为代码只需编写一次,以后就不用支持了。 当然,这些名称通常仍然是助记手段——数组用 a
, 索引用 i
、j
,表格的行列号用 r
、c
,坐标用 x
、y
等。 输入数据的名称保持与问题陈述中所给出的名称相同也更容易。 当然,越复杂的问题就越需要更多的代码来解,这导致更长、具有自解释性的变量名与函数名。
更多提示与技巧
竞技程序设计通常有这样的输入:
输入的第一行包含两个整数 n
与 k
在 Kotlin 中,这一行可以通过使用对整型列表进行解构声明的下列语句简明地解析:
val (n, k) = readInts()
很多人习惯使用 JVM 的 java.util.Scanner
类来解析结构较少的输入格式。Kotlin 已设计成能与 JVM 库很好互操作,因此在 Kotlin 中使用它们会很自然。然而请注意,java.util.Scanner
极其慢。事实上,速度慢得以至用它解析 105 个或更多整数时,很可能不满足典型的 2 秒限制,而这是一个简单的 Kotlin split(" ").map { it.toInt() }
就能做到的。
在 Kotlin 中写输出通常很简单,调用 println(...) 以及使用 Kotlin 的字符串模板。然而,当输出包含大约 105 或更多行时,必须小心。调用这么多次 println
太慢了,因为 Kotlin 中的该输出会在每行后自动刷新写缓冲。 从数组或 list 中写多行的更快方式是使用 joinToString() 函数以 "\n"
作为分隔符,如下所示:
println(a.joinToString("\n")) // 数组/list 中的每个元素占一行
学习 Kotlin
Kotlin 很易学,尤其对于已经了解 Java 的人。 对于软件开发人员来说,关于 Kotlin 基本语法的简短介绍可以直接在以基本语法开始的网站参考部分中直接找到。
IDEA 已内置 Java-to-Kotlin 转换器。 熟悉 Java 的人可以用它来学习相应的 Kotlin 语法结构,但它并不完美,并且你仍然需要自己熟悉 Kotlin 并学习 Kotlin 惯用法。
学习 Kotlin 语法以及 Kotlin 标准库 API 的一个很好的资源是 Kotlin 心印。
参考文档:
关于本书 · Kotlin 官方文档 中文版
Kotlin开发Android-基础篇 - 知乎