3

Hey fellow programmers,

I recently jumped from my normal python programming to Kotlin programming in Android Studio.

In python I have generated some dictionaries that contain strings as keys and tuples as values. These tuples theirselves then contain an int, two strings and again two ints. Imagine this simplified dict:

dict = {"key" : (1,"firstString","secondString",2,0),
        "secondKey":(0,"firstString","secondString",4,1)}

Now comes the scary part: I would like to import my dicts into normal variables/ vals in Kotlin.

Therefore I put all four dicts I have in a text file in the res folder. The difficulty for me is both declaring the val of the appropriate data structure and then actually loading the key-value-pairs step by step into the val. All Kotlin tutorials seem to come to an end when diving this deep into their data structures.

Am I right in guessing I should have a hashmap for the dicts themselves and then a(n array) list for the tuples? Got even stuck when trying to declare that complex structure:

val importedDict: HashMap<String,Array<Integer,String,String,Integer>> = hashMapOf<String,Array<Int,String,String,Int>()

as there just seemed to be no valid data structure of multiple types in Kotlin.

I'm thankful for any hint you can give.

Pavneet_Singh
  • 36,884
  • 5
  • 53
  • 68
Malte Ge
  • 153
  • 3
  • 12

2 Answers2

2

I believe the easiest way to represent your tuples is to declare a data class, like so:

data class TupleData(val id: Int, 
                     val firstString: String, 
                     val secondString: String, 
                     val otherNumber: Int, 
                     val anotherNumber: Int)

And then declare your data structure as HashMap<String, TupleData>. Note that you have to think about nullability of the types (String or String?) and if they have immutable references (val or var) since you didn't specify them in your question.

Also, to make your life easier, I believe you could use Moshi with a custom adapter to parse your string as if it was a JSON object directly into your data structures. (but keep in mind this other answer).

You cannot declare an Array as Array<Integer, String, String, Integer> because it takes only one generic parameter named T in the kotlin documentation. If you want to specify a tuple you either have to use Pair or Triple and if there's more data than that in the tuple, declare a data class


EDIT: After trying to make a working example with your data, I noticed that it's not properly formatted for JSON parsing, so you'll probably have to parse them by hand.

If you still want to look into how to serialize into JSON (maybe using pickle from Python), this is how I implemented the adapter:

class TupleDataAdapter {
    @ToJson
    fun toJson(tupleData: TupleData) : String {
        val (id, first, second, other, another) = tupleData;
        return "($id, $first, $second, $other, $another)" 
    }

    @FromJson
    fun fromJson(tuple: String) : TupleData {
        if (tuple[0] != '(' || tuple.last() != ')') {
            throw JsonDataException("Wrong format for tuple data: missing '(' or ')'")
        }

        val trimmed = tuple.trimStart('(').trimEnd(')')
        val fields = trimmed.split(',')
        if (fields.size != 5) {
            throw JsonDataException("Wrong format for tuple data: wrong number of fields in tuple")
        }
        return TupleData(fields[0].toInt(), fields[1], fields[2], fields[3].toInt(), fields[4].toInt()) 
    }
}

fun main(args: Array<String>) {

    val dict = File("./dict.txt").readText()

    println(dict)

    val moshi = Moshi.Builder()
        .add(TupleDataAdapter())
        .build()

    val type = Types.newParameterizedType(Map::class.java, String::class.java, TupleData::class.java)   

    val mapAdapter : JsonAdapter<Map<String, TupleData>> = moshi.adapter(type)
    val decoded : Map<String, TupleData>? = mapAdapter.fromJson(dict)

    println(decoded)
}

This implementation worked correctly when I changed the text as such:

{"key" : "(1,firstString,secondString,2,0)","secondKey" : "(0,firstString,secondString,4,1)"}

Note the difference in the quotation marks.

Eduardo macedo
  • 762
  • 1
  • 4
  • 16
  • 1
    Thank you for the elaborated solution! I actually got it working in a much simpler way but appreciate your efforts :) – Malte Ge Feb 17 '20 at 02:13
1

Array can only contain one type of data so multiple types cannot be declared instead, use Array<Any> where Any is super class of every object so use

  var map : Map<String, Array<Any>> = mapOf("key" to arrayOf(1,"firstString","secondString",2,0))

You can declare the array if you want to add values like

  var map2 : HashMap<String, Array<Any>> = HashMap<String, Array<Any>>()
  map2.put("key" , arrayOf(1,"firstString","secondString",2,0))
  println(map2["key"]?.get(2)) // "secondString"
Pavneet_Singh
  • 36,884
  • 5
  • 53
  • 68
  • Great, this seems to work, thank you :) Do you possibly even know how to import this in a loop? In Python I'd know this right away but as a Kotlin newbee I'm still trying. I guess there is even smarter ways in Kotlin than starting for loops in for loops like in ``for (dict in elements.txt){ for (key in dict){for (value in dict[key]){map2.put(key... `` right? – Malte Ge Feb 03 '20 at 17:33
  • your welcome and you can use `for (dict in elements.txt){ for((key, array) in dict) {// use key or array} }`. **Note: Arrays are immutable, you cannot add more element once size is defined, if you want to then use `MutableList`**. it's totally depends how you are reading your txt file though can eliminate the whole process with Gson or mochi parsing libs to create map objects – Pavneet_Singh Feb 03 '20 at 17:43
  • Thank you, this works perfectly! I of course had the full dictionary I wanted to make into a map so I unconventionally used MSWord to transform my dict into the style Kotlin wants maps to be. I fear I need to bother you with a further question as googling for something special in Kotlin just doesn't help: How can I get a random element from the map then? I tried importing Java's Random() and then `val randomElementNumber = Random().nextInt(elementeAusBauch.size) + 1` and `println(elementeAusBauch[randomElementNumber])` but then the type inference fails. Thanks in advance! – Malte Ge Feb 08 '20 at 17:04
  • @MalteGe you can get the [set of keys](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/keys.html) then use it's size to generate random value and [use a simple logic like this to get the key from set](https://stackoverflow.com/a/124693/4936904) – Pavneet_Singh Feb 09 '20 at 17:19
  • Thanks again, got this working - although Kotlin really tested me :D – Malte Ge Feb 17 '20 at 02:12
  • 1
    I am glad that I could help, keep it up and happy coding! – Pavneet_Singh Feb 17 '20 at 10:13