В Kotlin / Java есть много способов для анализа файла и разделения строк.
В следующем примере Regex используется для разделения строки на id и имя колледжа:
data class College(val id: String, val name: String)
val colleges = File("input.txt").useLines { lines ->
val regex = Regex("(\\d+) (.+)")
lines.mapNotNull { line ->
regex.matchEntire(line)?.let {
College(it.groupValues[1], it.groupValues[2])
}
}.toList()
}
Если вам нужно прочитать из стандартного ввода, замените третью строку на:
val colleges = InputStreamReader(System.`in`).useLines { lines ->
Обновление: Скрыть подробности
В Java / Kotlin нет общего способа разбора текстовых файлов на объект. Для файлов XML и Json существует несколько стандартных API, и вы можете зарегистрировать Serializer / Deserializer для каждого типа.
Если вам нужна такая абстракция для простых строчно-ориентированных текстовых файлов, вы должны создать ее самостоятельно. Следующие примеры могут дать вам представление:
fun <T : Any> File.parseLines(lineParser: (String) -> T?): List<T> =
useLines { it.mapNotNull(lineParser).toList() }
Функция расширяет класс File
методом parseLines
.
Реализация парсера строк для класса College
будет выглядеть следующим образом:
val collegeLineParser: (String) -> College? = { line ->
val regex = Regex("(\\d+) (.+)")
regex.matchEntire(line)?.let {
College(it.groupValues[1], it.groupValues[2])
}
}
Или, если вы хотите кешировать RegEx
:
val collegeLineParserCachedRegex = object : (String) -> College? {
val regex = Regex("(\\d+) (.+)")
override fun invoke(line: String): College? =
regex.matchEntire(line)?.let {
College(it.groupValues[1], it.groupValues[2])
}
}
Теперь вы можете вызывать метод parseLines
для File
:
val colleges = File("input.txt").parseLines(collegeLineParser)
Обновление: реестр для анализаторов
Если вы не хотите указывать lineParser
для каждого звонка, вы можете создать реестр:
object LineParserRegistry {
val parsers = ConcurrentHashMap<KClass<*>, (String) -> Any?>()
inline fun <reified T> register(noinline parser : (String) -> T?) {
parsers[T::class] = parser
}
inline fun <reified T> get(): (String) -> T? {
// force companion initializer
Class.forName(T::class.java.name)
return parsers[T::class] as (String) -> T??
}
}
inline fun <reified T : Any> File.parseLines(): List<T> =
useLines { it.mapNotNull(LineParserRegistry.get<T>()).toList() }
Если вы хотите зарегистрировать парсеры автоматически, вам нужно добавить сопутствующий объект методом init
:
data class College(val id: String, val name: String) {
companion object {
init {
val collegeLineParser: (String) -> College? = { line ->
val regex = Regex("(\\d+) (.+)")
regex.matchEntire(line)?.let {
College(it.groupValues[1], it.groupValues[2])
}
}
LineParserRegistry.register(collegeLineParser)
}
}
}
Этот метод init
вызывается при первом использовании класса College
. Но отражение не в счет, поэтому нам пришлось добавить Class.forName(T::class.java.name)
в метод get
реестра. Этот вызов вызывает инициализацию объекта-компаньона.
Теперь вы можете вызывать парсер без всяких приготовлений:
val colleges = File("input.txt").parseLines<College>()