Kotlin загрузить коллекцию класса из файла? - PullRequest
0 голосов
/ 11 июля 2019

В C ++ я могу загрузить коллекцию класса из файла, используя оператор друга >> перегрузка:

friend std::istream& operator>>(std::istream& is_a, College& college_a) {
    return is_a >> college_a.id_,
        is_a.get(),
        std::getline(is_a,college_a.name_);
}

std::set<College> colleges {};
std::copy(std::istream_iterator<College> {std::cin},
    std::istream_iterator<College> {},
    std::inserter(colleges, colleges.begin())); 

из файла, подобного этому:

0707 Rowan Technical College
0980 University of Saskatchewan
1058 Belmont University
1072 Belmont Technical College

Как это можно сделать с Kotlin?

1 Ответ

0 голосов
/ 11 июля 2019

В 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>()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...