Kotlin: Find the first line of a file that matches a regex, and return captured values - kotlin

i want get text from a file by using regEx and want save the file with a new name (using the results of the regEx-Find).
My Problem is that i cant get/return the correct genearated (in this example xyz maur) out of the function readFileLineByLineUsingForEachLine(fileName: String) the new newFileName which was generated (sucessfully as expected) in the function.
Line 1 of Source:
start {"Name":"xyz","Civ":"maur","Team":0}
My Prototype:
fun main() {
val f = "./commands.txt";
var newFileName = readFileLineByLineUsingForEachLine(f)
print(newFileName.)
val source = Paths.get(f)
val target = Paths.get("/home/x/snap/0ad/199/.local/share/0ad/replays/0.0.24/2021-03-14_0016/" + newFileName)
// try {
// val move = Files.move(
// source,
// target
// )
// } catch (e: IOException) {
// e.printStackTrace()
// }
};
fun readFileLineByLineUsingForEachLine(fileName: String) // https://www.baeldung.com/kotlin/read-file
= File(fileName).forEachLine lit#{
// "Name":"Cleisthenes"
val regexString = """
"Name":(?<Name>"\w+").*?"Civ":(?<Civ>"\w+").*?"Team":0
""".trim()
var regex = Regex(regexString)
var matched = regex.find(it)?.groupValues
val Name = matched?.get(1)
val Civ = matched?.get(2)
if (Name != null)
println(Name)
if (Civ != null)
println(Civ)
val newFileName = "$Name $Civ"
return#lit
}

Because you want to stop processing as soon as you find a match, I don't think forEachLine is the best choice. Instead you can use useLines, and combine it with first to stop processing once you get a match:
val regex = Regex(""""Name":(?<Name>"\w+").*?"Civ":(?<Civ>"\w+").*?"Team":0""")
fun readFileLineByLineUsingForEachLine(fileName: String) =
File(fileName).useLines { lines ->
val (name, civ) = lines
.map { regex.find(it) }
.filterNotNull()
.first()
.destructured
"$name $civ"
}
For the example you provided, this returns the string "xyz" "maur".

that's just a very little modification of the correct, helpful answer from Adam here https://stackoverflow.com/a/66654710/2891692
fun readFileLineByLineUsingForEachLine2(fileName: String) =
File(fileName).useLines { lines ->
val (name, civ) = lines
.map {
val regexString = """
"Name":(?<Name>"\w+").*?"Civ":(?<Civ>"\w+").*?"Team":0
""".trim()
var regex = Regex(regexString)
regex.find(it)
}
.filterNotNull()
.first()
.destructured
"$name $civ"
}

Related

How to use dynamic string substitution in Kotlin?

I'm looking for a Kotlin way to do a dynamic values substitution into a string.
It is clear how to implement it, just want to check if there is something similar in standard library.
Could you help me to find a function which given template and data map returns a resulting string with all template keys replaced with their values?
fun format(template: String, data: Map<String, Any>): String { /* magic */ }
format("${a} ${b} ${a}", mapOf("a" to "Home", "b" to "Sweet)) // -> "Home Sweet Home"
fun format(template: String, data: Map<String, String>): String {
var retval = template
data.forEach { dataEntry ->
retval = retval.replace("\${" + dataEntry.key + "}", dataEntry.value)
}
return retval
}
// The $ signs in the template string need to be escaped to prevent
// string interpolation
format("\${a} \${b} \${a}", mapOf("a" to "Home", "b" to "Sweet"))
Not shorter than lukas.j's answer, just different (using Regex):
val regex = "\\\$\\{([a-z])}".toRegex()
fun format(template: String, data: Map<String, String>) =
regex.findAll(template).fold(template) { result, matchResult ->
val (match, key) = matchResult.groupValues
result.replace(match, data[key] ?: match)
}
I did not find any thing standard to solve the problem.
So here is a balanced (readability/performance/extensibility) solution also handling cases when some substitutions are undefined in dataMap.
makeString("\${a} # \${b} # \${c}", mapOf("a" to 123, "c" to "xyz")) // => "123 # ??? # xyz"
--
object Substitutions {
private val pattern = Pattern.compile("\\$\\{([^}]+)\\}")
fun makeString(
template: String,
dataMap: Map<String, Any?>,
undefinedStub: String = "???"
): String {
val replacer = createReplacer(dataMap, undefinedStub)
val messageParts = splitWithDelimiters(template, pattern, replacer)
return messageParts.joinToString("")
}
private fun createReplacer(dataMap: Map<String, Any?>, stub: String): (Matcher) -> String {
return { m ->
val key = m.group(1)
(dataMap[key] ?: stub).toString()
}
}
private fun splitWithDelimiters(
text: String,
pattern: Pattern,
matchTransform: (Matcher) -> String
): List<String> {
var lastMatch = 0
val items = mutableListOf<String>()
val m = pattern.matcher(text)
while (m.find()) {
items.add(text.substring(lastMatch, m.start()))
items.add(matchTransform(m))
lastMatch = m.end()
}
items.add(text.substring(lastMatch))
return items
}
}

Kotlin Creating List<List<Map<String, String>>>

I am trying to return List<List<Map<String, String>>> from a function in kotlin. I'm new to kotlin.
Edit1
Here's how I am attempting to to this
val a = mutableListOf(mutableListOf(mutableMapOf<String, String>()))
The problem with the above variable is, I am unable to figure out how to insert data into this variable. I tried with this:
val a = mutableListOf(mutableListOf(mutableMapOf<String, String>()))
val b = mutableListOf(mutableMapOf<String, String>())
val c = mutableMapOf<String, String>()
c.put("c", "n")
b.add(c)
a.add(b)
This is giving me:
[[{}], [{}, {c=n}]]
What I want is [[{c=n}]]
Can someone tell me how I can insert data into it?
The end goal I am trying to achieve is to store data in the form of List<List<Map<String, String>>>
EDIT 2
The function for which I am trying to write this dat structure:
fun processReport(file: Scanner): MutableList<List<Map<String, String>>> {
val result = mutableListOf<List<Map<String, String>>>()
val columnNames = file.nextLine().split(",")
while (file.hasNext()) {
val record = mutableListOf<Map<String, String>>()
val rowValues = file.nextLine()
.replace(",(?=[^\"]*\"[^\"]*(?:\"[^\"]*\"[^\"]*)*$)".toRegex(), "")
.split(",")
for (i in rowValues.indices) {
record.add(mapOf(columnNames[i] to rowValues[i]))
print(columnNames[i] + " : " + rowValues[i] + " ")
}
result.add(record)
}
return result
}
You don't need to use mutable data structures. You can define it like this:
fun main() {
val a = listOf(listOf(mapOf("c" to "n")))
println(a)
}
Output:
[[{c=n}]]
If you wanted to use mutable data structures and add the data later, you could do it like this:
fun main() {
val map = mutableMapOf<String, String>()
val innerList = mutableListOf<Map<String, String>>()
val outerList = mutableListOf<List<Map<String, String>>>()
map["c"] = "n"
innerList.add(map)
outerList.add(innerList)
println(outerList)
}
The output is the same, although the lists and maps are mutable.
In response to the 2nd edit. Ah, you're parsing a CSV. You shouldn't try to do that yourself, but you should use a library. Here's an example using Apache Commons CSV
fun processReport(file: File): List<List<Map<String, String>>> {
val parser = CSVParser.parse(file, Charset.defaultCharset(), CSVFormat.DEFAULT.withHeader())
return parser.records.map {
it.toMap().entries.map { (k, v) -> mapOf(k to v) }
}
}
For the following CSV:
foo,bar,baz
a,b,c
1,2,3
It produces:
[[{foo=a}, {bar=b}, {baz=c}], [{foo=1}, {bar=2}, {baz=3}]]
Note that you can simplify it further if you're happy returning a list of maps:
fun processReport(file: File): List<Map<String, String>> {
val parser = CSVParser.parse(file, Charset.defaultCharset(), CSVFormat.DEFAULT.withHeader())
return parser.records.map { it.toMap() }
}
Output:
[{foo=a, bar=b, baz=c}, {foo=1, bar=2, baz=3}]
I'm using Charset.defaultCharset() here, but you should change it to whatever character set the CSV is in.

how to replace duplicated string using re{n,} in Kotlin?

I want change "aaa" or "aa..." to "." using Regex(re{2,})
Below is my code
var answer = "aaa"
var re = Regex("re{2,}a") // Regex("are{2,}")
answer = re.replace(answer,".")
println(answer)
Regex("re{2,}a") and Regex("are{2,}")
Both println aaa
How can I replace duplicated string using re{n,} ??
fun main(args: Array<String>) {
var tests = arrayOf("a","aa","aaa","aaaa")
val re = Regex("a(a+)")
tests.forEach {t->
val result = re.replace(t,".")
println(result)
}
}
output:
a
.
.
.
Generic regex to replace any duplicates (not only duplicate a symbols) is (?<symbol>.)\k<symbol>+
You may define an extension function for convenient usage:
private val duplicateRegex = "(?<symbol>.)\\k<symbol>+".toRegex()
fun String.replaceDuplicatesWith(replacement: String): String = replace(duplicateRegex, replacement)
Usage:
println("a".replaceDuplicatesWith(".")) //a
println("aaa".replaceDuplicatesWith(".")) //.
println("aa...".replaceDuplicatesWith(".")) //..
If you want duplicates to be iteratively replaced (like "aa..." -> ".." -> ".") you'll need an auxilary recursive method:
tailrec fun String.iterativelyReplaceDuplicatesWith(replacement: String): String {
val result = this.replaceDuplicatesWith(replacement)
return if (result == this) result else result.iterativelyReplaceDuplicatesWith(replacement)
}
Usage:
println("a".iterativelyReplaceDuplicatesWith(".")) //a
println("aaa".iterativelyReplaceDuplicatesWith(".")) //.
println("aa...".iterativelyReplaceDuplicatesWith(".")) //.

kotlin how to parse text into a list of data class

I have a kotlin data class described as:
data class Credentials(val p: String, val id: String, val key: String, val pass: String, val out: String)
I am trying to read from 2 text files located in a directory, and put them into this data class.
How the data looks:
config file
[user1]
out = specialk
id = mike
[user2]
out = specialk
id = mike
[user3]
out = specialk
id = mike
credentials file
[user1]
key = qwer1
pass = 3452
[user2]
key = qwer3
pass = 345232
[user3]
key = qwer5
pass = 3452gfd
Setting it up:
val homepath = System.getProperty("user.home")
val config = "$homepath/foobar"
val cred= "$homepath/credbar"
val configStream: InputStream = File(config).inputStream()
val credStream: InputStream = File(cred).inputStream()
This next part is something I am unsure of. What I think is that I should be reading each stream and putting it into a list of data class grouped by the user. However, I'm not sure how that should be accomplished.
configStream.bufferedReader().forEachLine {
// put to data class here.
}
I can't write single comment cause i am new at Stackowerflow. Sorry about that. If this file holds your app backup you can take backup as JSON file. This is much easier than this.
Firstly you need to add Gson dependency in your project
implementation 'com.google.code.gson:gson:2.8.6'
Secondly you need to two parser object that is for make parse to data class to JSON and JSON to data class.
You need to declare your export and import type as below
val exportType = object : TypeToken<List<Credentials>>() {}.type
And after that for convert your data to JSON String you can use this
private fun List<Credentials>.toJson() = Gson().toJson(this, exportType)
this code returns String.
and if you want to convert JSON to String you can use this code
private fun fromJson(str: String): List<Credentials> {
return try {
Gson().fromJson(str, exportType)
} catch (e: Exception) {
Log.e("From Json Exception", "$e")
emptyList()
}
}
this code returns list of your data class.
I hope this can help you. I did not ask is this you want to do because my Stacowerflow account is new.
Basically I would try to convert your streams into two strings
val configInputFileString = convertStreamToString(configStream)
val credentialsInputFileString = convertStreamToString(credStream)
You can easily convert a stream by using this function:
fun convertStreamToString(stream: InputStream?): String? {
val reader = BufferedReader(InputStreamReader(stream))
val sb = StringBuilder()
var line: String? = null
while (reader.readLine().also { line = it } != null) {
sb.append(line).append("\n")
}
reader.close()
return sb.toString()
}
Once you have the two streams, I would define to different data classes to get the elements of the streams:
data class UserConfig(val placeholder: String, val out: String, val id: String)
data class UserCredentials(val placeholder: String, val key: String, val pass: String)
for each stream you need to get each row, splitting by new line character \n and clearing unuseful parts:
private fun getUserConfigs(elements: List<String>): ArrayList<UserConfig> {
val configs = arrayListOf<UserConfig>()
for (element in elements) {
val splittedConfig = element
.replace("out = ", "")
.replace("id = ", "")
.split("\n")
val config = UserConfig(splittedConfig[0], splittedConfig[1], splittedConfig[2])
configs.add(config)
}
return configs
}
private fun getUserCredentials(elements: List<String>): ArrayList<UserCredentials> {
val credentials = arrayListOf<UserCredentials>()
for (element in elements) {
val splittedCredentials = element
.replace("key = ", "")
.replace("pass = ", "")
.split("\n")
val config = UserCredentials(splittedCredentials[0], splittedCredentials[1], splittedCredentials[2])
credentials.add(config)
}
return credentials
}
Now you can map userConfig and credentialConfigs in a usersMap
val userConfigs = getUserConfigs(configInputFileString!!.split("\n\n"))
val credentialConfigs = getUserCredentials(credentialsInputFileString!!.split("\n\n"))
val usersMap = userConfigs.map { userConfig ->
userConfig to credentialConfigs.find { it.placeholder == userConfig.placeholder }
}
Finally you can transform the usersMap to match your data class by doing:
val credentials = usersMap.map { Credentials(it.first.placeholder, it.first.id, it.second!!.key, it.second!!.pass, it.first.out) }
As long as your files seem to be of Windows INI format you can use third-party library Ini4J to parse them.
Parse both files and merge two Inis into list of Credentials
val configs = Ini(File(config))
val credentials = Ini(File(cred))
val result: List<Credentials> = configs.keySet().map { user ->
Credentials(p = user, // or whatever 'p' is
id = configs.get(user, "id")!!,
key = credentials.get(user, "key")!!,
pass = credentials.get(user, "pass")!!,
out = configs.get(user, "out")!!)
}

Parking lot project error: when using a scanner. NoSuchElementException

I'll appreciate all your help.
I've been working on a course project where I have to make a parking lot that registers cars. When I use it in my IDE it works fine but when I run it through the platforms tests, in the first one, there's no problem but when the second iteration reaches the "when (val command = scanner.next())" in the createOrder fun, it crashes with the error:
java.lang.AssertionError: Exception in test #1
Probably your program run out of input (Scanner tried to read more than expected).
java.util.NoSuchElementException
at java.util.Scanner.throwFor(Scanner.java:862)
at java.util.Scanner.next(Scanner.java:1371)
at parking.ParkingLot.createOrder(Main.kt:39)
at parking.ParkingLot.start(Main.kt:31)
at parking.MainKt.main(Main.kt:6)
at parking.MainKt.main(Main.kt)
Please find below the output of your program during this failed test.
Note that the '>' character indicates the beginning of the input line.
---
> park KA-01-HH-1234 White
White car parked in spot 1.
the idea is that the test inputs many cars but it crashes when trying to do the second input
this is my code (sorry if my code is messy, I'm still learning)
import java.util.*
fun main() {
ParkingLot.start()
}
class Car(val regNumber: String = "", val color: String = "") {
}
class Order(val command: String) {
lateinit var regNum: String
lateinit var color: String
lateinit var spot: String
lateinit var status: String
}
object ParkingLot {
val spaces: Array<Pair<String?, Car?>> = Array(20) { Pair(null, null) }
const val occupied = "occupied"
const val park = "park"
const val leave = "leave"
const val exit = "exit"
fun start() {
val scanner = Scanner(System.`in`)
do {
val order = createOrder(scanner)
interaction(order, scanner)
} while (order.command != exit)
}
fun createOrder(scanner: Scanner): Order {
when (val command = scanner.next()) {
park -> {
val parkOrder = Order(command)
parkOrder.regNum = scanner.next()
parkOrder.color = scanner.next()
parkOrder.status = "valid"
return parkOrder
}
leave -> {
val retrieveOrder = Order(command)
retrieveOrder.spot = scanner.next()
retrieveOrder.status = "valid"
return retrieveOrder
}
exit -> {
val exitOrder = Order(command)
exitOrder.status = "valid"
return exitOrder
}
else -> {
val incorrectOrder = Order(command)
incorrectOrder.status = "invalid"
return incorrectOrder
}
}
}
fun interaction(order: Order, scanner: Scanner) {
if (order.command == park) {
// val toParkCar = Car(order.regNum, order.color)
park(Car(order.regNum, order.color))
}
if (order.command == leave) {
leave(order)
}
if (order.command == exit) return
//TODO update the error msg to include exit command
if (order.status == "invalid") println("\"${order.command}\" isn't a valid , either use \"park\" or \"leave\"")
// scanner.close()
}
fun park(car: Car) {
for ((index, item) in spaces.withIndex()) {
if (item.first == null) {
spaces[index] = Pair(occupied, car)
println("${car.color} car parked in spot ${index + 1}.")
return
}
}
println("Sorry, the parking lot is full.")
}
fun leave(order: Order) {
if (spaces[order.spot.toInt() - 1].first == occupied) {
spaces[order.spot.toInt() - 1] = Pair(null, null)
println("Spot ${order.spot} is free.")
} else {
println("There is no car in spot ${order.spot}.")
}
}
}
Ok so I noticed this is a problem for the JetBrains plugin. I don't know why but the solution was taking the scanner out of the function and directly in the main loop.