How to merge lists of objects, based on time key - kotlin

Say I have multiple (MANY) Lists, each containing multiple Record objects that contain one long constructor parameter "time" (and a string parameter for debugging purposes).
Here is an example:
val list1 = listOf(Record(0, "A1"), Record(1, "A2"), Record(2, "A3"))
val list2 = listOf(Record(0, "B1"), Record(2, "B2"))
val list3 = listOf(Record(1, "C1"), Record(2, "C2"))
I want to combine the lists into one list, so that at any given time present in the original lists, the latest value from each list is present in the list.
Here is an example:
val output = listOf(Record(0, "A1+B1"), Record(1, "A2+B1+C1"), Record(2, "A3+B2+C2"))
Assume that Records can be added to create new Records containing the data of both.
(Technically, Records are a typealias for a key-value map containing data, but I thought that was out of scope for this question.)

Assuming that
data class Record(val time:Long , val param2:String)
Then you can do like this
val list1 = listOf(Record(0, "A1"), Record(1, "A2"), Record(2, "A3"))
val list2 = listOf(Record(0, "B1"), Record(2, "B2"))
val list3 = listOf(Record(1, "C1"), Record(2, "C2"))
val output = listOf(list1,list2,list3)
.flatten()
.groupBy { it.time }
.map { (key,value) ->
// you could define your own separator if needed
val newParam2 = value.joinToString(separator = "+") { it.param2 }
Record(key,newParam2)
}
println(output)
//[Record(0, "A1+B1"), Record(1, "A2+B1+C1"), Record(2, "A3+B2+C2")]

you can use 'associateByTo' inline method. Assuming that:
data class Record(val time: Int, var text: String)
then:
val list1 = listOf(Record(0, "A1"), Record(1, "A2"), Record(2, "A3"))
val list2 = listOf(Record(0, "B1"), Record(2, "B2"))
val list3 = listOf(Record(1, "C1"), Record(2, "C2"))
val map = hashMapOf<Int, Record>()
val output = listOf(list1, list2, list3).flatten().associateByTo(map,
{ it.time },
{
//if the record id exist in map, append time
map[it.time]?.apply { text += "+${it.text}" } ?: it
})
println(output.values)
output.values are what you want.

Related

How to find select the elements that are in one arraylist that are not in another arraylist in kotlin

I am attempting to find the difference of 2 arraylists in kotlin i.e the elements in the first arraylist that are not included in the second arraylist. The code below I believe would work for listOf but I need it to work for arraylists of strings structure instead with the same names i.e first, second and difference should all be arraylists of strings.
fun main() {
private var first = ArrayList<String>()
private var second = ArrayList<String>()
private var difference = ArrayList<String>()
first.add(“a”)
first.add(“b”)
first.add(“c”)
first.add(“d”)
first.add(“e”)
second.add(“a”)
second.add(“b”)
second.add(“c”)
val difference = first.minus(second)
println(difference) // [d, e]
}
You can use arrayListOf:
fun main() {
val first = arrayListOf("a", "b", "c", "d", "e")
println("first type: %s".format(first.javaClass.kotlin.qualifiedName))
val second = arrayListOf("a", "b", "c")
println("second type: %s".format(second.javaClass.kotlin.qualifiedName))
val difference = first.minus(second)
println(difference)
println("difference type: %s".format(difference.javaClass.kotlin.qualifiedName))
}
Or instead, add the elements to the ArrayList's one by one if desired:
fun main() {
val first = ArrayList<String>()
first.add("a")
first.add("b")
first.add("c")
first.add("d")
first.add("e")
println("first type: %s".format(first.javaClass.kotlin.qualifiedName))
val second = ArrayList<String>()
second.add("a")
second.add("b")
second.add("c")
println("second type: %s".format(second.javaClass.kotlin.qualifiedName))
val difference = first.minus(second)
println(difference)
println("difference type: %s".format(difference.javaClass.kotlin.qualifiedName))
}
Output:
first type: java.util.ArrayList
second type: java.util.ArrayList
[d, e]
difference type: java.util.ArrayList

I want to merge two lists of Mcqs and True false type questions in List of quiz type

The data class of Mcqs look like this:
data class Mcqss(
var answer: String,
val mcqs: String,
val option1: String,
val option2: String,
val option3: String,
val option4: String,
var topicId: String,
var sequence: String,
)
True false data class:
data class tf(
val answer: String,
val question: String,
val topicId: String,
val sequence: String,
)
Quiz data class:
data class quiz(
var topicId: String,
var sequence: String,
var mcq_question:String,
var trf_question:String
)
Function to combine two lists:
fun <T, U> combine(first: ArrayList<Mcqss>, second: ArrayList<tf>): MutableList<Any> {
val list: MutableList<Any> = first.map { i -> i }.toMutableList()
list.addAll(second.map { i -> i })
return list
}
But when I execute this line it gives me a class cast exception:
val joined: ArrayList<quiz> = combine<Any,Any>(mcqlist, tfs) as ArrayList<quiz>
for (item in joined) {
item.sequence
}
Any suggestions please.
Please try next code:
fun combine(first: ArrayList<Mcqss>, second: ArrayList<Tf>): ArrayList<Quiz> {
// I assume the sizes of `first` and `second` lists are the same
require(first.size == second.size)
val result = mutableListOf<Quiz>()
for (i in 0 until first.size) {
val quiz = Quiz(first[i].topicId, first[i].sequence, ...)
result.add(quiz)
}
return ArrayList(result)
}
val joined: ArrayList<Quiz> = combine(mcqlist, tfs)
I would recommend to name classes starting with a capital letter, e.g. quiz->Quiz.
val mcqlist: List<Mcqss> = ...
val tfs: List<TrueFalse> = ...
val joined = mcqlist + tfs
for (item in joined) {
if (item is Mcqss) {
println(item.option1)
} else if (item is TrueFalse) {
println(item.question)
}
}

Kotlin: mutable map of mutable list won't update the list

(Kotlin newbie here) I have a text file with rows that look like these:
1-1-1
1-1-2
1-1-3
2-1-1
2-1-2
etc.
I have to transform these data to a map where the key is the first 2 elements and the value is a list of the third elements that that match the key. For example, the above records will transform into this JSON:
1-1: [1, 2, 3]
2-1: [1, 2]
etc.
I'm unable to increment the list. Here's a simplified version, I get stuck on the "else":
fun main () {
val l1 = mutableListOf("1-1-1", "1-1-2", "1-1-3", "2-1-1", "2-1-2")
val m = mutableMapOf<String, List<Int>>()
for (e in l1) {
val c = e.split("-")
val key = "${c[0]}-${c[1]}"
if (m[key] == null) m[key] = listOf(c[2].toInt())
else println("How do I append to the list?")
}
println(m)
}
Output:
{1-1=[1], 2-1=[1]}
But I want:
{1-1=[1, 2, 3], 2-1=[1, 2]}
Thank you (comments about idiomatic form are welcome!)
If we continue to follow your strategy, what you need is for the value type to be a MutableList. Then you can add to the existing MutableList when there's already an existing list for that key:
fun main() {
val l1 = mutableListOf("1-1-1", "1-1-2", "1-1-3", "2-1-1", "2-1-2")
val m = mutableMapOf<String, MutableList<Int>>()
for (e in l1) {
val c = e.split("-")
val key = "${c[0]}-${c[1]}"
if (m[key] == null) m[key] = mutableListOf(c[2].toInt())
else m[key]!!.add(c[2].toInt())
}
println(m)
}
This can be more natural using getOrPut(). It returns the existing MutableList or creates one and puts it in the map if it's missing. Then we don't have to deal with if/else, and can simply add the new item to the list.
fun main() {
val l1 = mutableListOf("1-1-1", "1-1-2", "1-1-3", "2-1-1", "2-1-2")
val m = mutableMapOf<String, MutableList<Int>>()
for (e in l1) {
val c = e.split("-")
val key = "${c[0]}-${c[1]}"
m.getOrPut(key, ::mutableListOf).add(c[2].toInt())
}
println(m)
}
But we can use the map and groupBy functions to create it more simply:
val m = l1.map { it.split("-") }
.groupBy(
{ "${it[0]}-${it[1]}" }, // keys
{ it[2].toInt() } // values
)
You can achieve your desired output with a single call to groupBy of the Kotlin standard library.
val input = listOf("1-1-1", "1-1-2", "1-1-3", "2-1-1", "2-1-2")
val result = input.groupBy(
{ it.substringBeforeLast("-") }, // extract key from item
{ it.substringAfterLast("-").toInt() } // extract value from item
)
The first lambda function extracts the key to group by of every list item. The second lambda function provides the value to use for each list item.
You can also do it by first mapping your values to Pairs and then group them as follows:
fun main(args: Array<String>) {
val input = listOf("1-1-1", "1-1-2", "1-1-3", "2-1-1", "2-1-2")
val result = input.map {
val values = it.split("-")
"${values[0]}-${values[1]}" to values[2]
}.groupBy ({ it.first }) { it.second }
println(result)
}

How to compare two lists item by item in Kotlin

how can i compare "List A" with "List B", and if "list A", has "n" elements that match with "list B" create a "C list" from the "list A" with boolean attached as a new field to every item so if the item was in "list B" it is true else false.
heres a detailed example of what im trying to do:
data class ListAelement(
val fieldA: Double = 2.0,
//this is the field that i have to check with listB
val id: String = "12345",
val somefield: String,
val someotherfield: String
)
data class ListBelement(
//list b only has id
val id: String = "12345"
)
data class ListCelement(
val fieldA: Double = 2.0,
val id: String = "12345",
val somefield: String,
val someotherfield: String,
//this should be true if it is in ListB
val isElementInA: Boolean
)
fun isEqual(listA: List<ListAelement>, listB: List<ListBelement>): List<ListCelement> {
return emptyList()
}
fun main() {
val listA = listOf<ListAelement>(
ListAelement(1.2, "12345"),
ListAelement(1.2, "12343"),
ListAelement(1.2, "1234566"),
ListAelement(1.2, "11233")
)
val listB = listOf<ListBelement>(ListBelement("12345"), ListBelement("123123"))
//new list
val listC = isEqual(listA, listB)
}
Tried following
val listASize = listA.size
val listBSize = listB.size
var counter = 0
while (counter < listASize) {
var id = listA[counter].id
listB.forEach {
if (it.id == id) {
println("matched item $it")
}
}
counter++
}
the accepted response works as it should, heres what i was trying to achieve:
"list A" is an Api response, "list B" is a local database, and list C is to display it and show a check if the item is in the local database
Essentially what you have is a search problem.
I have slightly modified your implementation to make it more accurate for reference:
fun isEqual(listA: List<ListAelement>, listB: List<ListBelement>): List<ListCelement> {
val listC: MutableList<ListCelement> = mutableListOf()
// you don't need a while loop or a counter variable as you need to look at every item in list A
listA.forEach { listAItem ->
var found = false
// search for each item in list A in list B
// this is a linear search
listB.forEach { listBItem ->
if (listBItem.id == listAItem.id) {
// println("matched item $it")
found = true // cache the result that it has been found rather than printing out
}
}
// create your list C with the ListCelement instance that holds the result of whether it was found in List B or not
listC.add(
ListCelement(id=listAItem.id, isElementInA=found)
)
}
return listC
}
You current implementation is of time complexity=O(N * M) (where N and M are the sizes of lists A and B) because for each item in list A, you have to iterate through list B while performing a linear search for it.
This can be optimized through the use of a binary search. Binary search does however require that the search space is sorted. In your case list B being the search space, it needs to be sorted on the id field since that's what you identify elements of list A that are in list B on.
It has a time complexity=O(log N) (where N is the size of the search space) because you take advantage of the order in your search space to discard half the elements at each attempt until you either find your search item or reduce your search space to one item which is not your search item in which case you exit the search.
Below is an implementation using binary search:
/**
* We declare a package-level function main which returns Unit and takes
* an Array of strings as a parameter. Note that semicolons are optional.
*/
data class ListAelement(
val fieldA: Double = 2.0,
// this is the field that i have to check with listB
val id: String,
val someField: String,
val someOtherField: String
)
data class ListBelement(
// list b only has id
val id: String
)
data class ListCelement(
val fieldA: Double = 2.0,
val id: String,
val someField: String,
val someOtherField: String,
// this should be true if it is in ListB
val isElementInA: Boolean
)
fun isEqual(listA: List<ListAelement>, listB: List<ListBelement>): List<ListCelement> {
val sortedListB = listB.sortedWith(compareBy({ it.id }))
val listC: MutableList<ListCelement> = mutableListOf()
listA.forEach { listAItem ->
listC.add(
ListCelement(
id=listAItem.id,
someField=listAItem.someField,
someOtherField=listAItem.someOtherField,
isElementInA=binarySearchListB(sortedListB, listAItem) // linear search replaced with this binary search
)
)
}
return listC
}
// the kotlin collections package ships with a binarySearch() function https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/binary-search.html
// I have opted to implement a custom one so that I can have a Boolean return type
fun binarySearchListB(searchSpace:List<ListBelement>, searchItem: ListAelement, leftIndex:Int = 0, rightIndex: Int = searchSpace.size - 1): Boolean {
val midIndex: Int = (leftIndex + rightIndex) / 2
val midItem: ListBelement = searchSpace[midIndex]
// exit condition if item does not exist
if (leftIndex == rightIndex && searchItem.id != searchSpace[leftIndex].id) {
return false
}
if (midItem.id == searchItem.id) {
// found
return true
} else if(searchItem.id < midItem.id) {
// go to the left
return binarySearchListB(searchSpace, searchItem, leftIndex, midIndex - 1)
} else if(searchItem.id > midItem.id) {
// go to the right
return binarySearchListB(searchSpace, searchItem, midIndex + 1, rightIndex)
}
return false
}
fun main(args: Array<String>) {
val listA = listOf<ListAelement>(
ListAelement(1.2, "12345", "someField", "someOtherField"),
ListAelement(1.2, "12343", "someField", "someOtherField"),
ListAelement(1.2, "1234566", "someField", "someOtherField"),
ListAelement(1.2, "11233", "someField", "someOtherField")
)
val listB = listOf<ListBelement>(ListBelement("1234566"), ListBelement("12345"), ListBelement("123123"))
// new list
val listC = isEqual(listA, listB)
println(listC)
}
Here is the output:
[ListCelement(fieldA=2.0, id=12345, someField=someField, someOtherField=someOtherField, isElementInA=true), ListCelement(fieldA=2.0, id=12343, someField=someField, someOtherField=someOtherField, isElementInA=false), ListCelement(fieldA=2.0, id=1234566, someField=someField, someOtherField=someOtherField, isElementInA=true), ListCelement(fieldA=2.0, id=11233, someField=someField, someOtherField=someOtherField, isElementInA=false)]
UPDATE:
Here's an update that converts list B into a HashSet of strings containing ListBelement ids to yield constant time lookups (i.e O(1)) as suggested in the comments:
/**
* We declare a package-level function main which returns Unit and takes
* an Array of strings as a parameter. Note that semicolons are optional.
*/
data class ListAelement(
val fieldA: Double = 2.0,
// this is the field that i have to check with listB
val id: String,
val someField: String,
val someOtherField: String
)
data class ListBelement(
// list b only has id
val id: String
)
data class ListCelement(
val fieldA: Double = 2.0,
val id: String,
val someField: String,
val someOtherField: String,
// this should be true if it is in ListB
val isElementInA: Boolean
)
fun isEqual(listA: List<ListAelement>, listB: List<ListBelement>): List<ListCelement> {
// construct a HashSet of string ids from listB
// there could be a more idiomatic way to do it
// more about HashSet here -> https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-hash-set/
val listBHashSet: HashSet<String> = hashSetOf()
listB.forEach { listBItem ->
listBHashSet.add(listBItem.id)
}
val listC: MutableList<ListCelement> = mutableListOf()
listA.forEach { listAItem ->
listC.add(
ListCelement(
id=listAItem.id,
someField=listAItem.someField,
someOtherField=listAItem.someOtherField,
isElementInA=listBHashSet.contains(listAItem.id) // linear search replaced with this hashset lookup search
)
)
}
return listC
}
fun main(args: Array<String>) {
val listA = listOf<ListAelement>(
ListAelement(1.2, "12345", "someField", "someOtherField"),
ListAelement(1.2, "12343", "someField", "someOtherField"),
ListAelement(1.2, "1234566", "someField", "someOtherField"),
ListAelement(1.2, "11233", "someField", "someOtherField")
)
val listB = listOf<ListBelement>(ListBelement("1234566"), ListBelement("12345"), ListBelement("123123"))
// new list
val listC = isEqual(listA, listB)
println(listC)
}
The output:
[ListCelement(fieldA=2.0, id=12345, someField=someField, someOtherField=someOtherField, isElementInA=true), ListCelement(fieldA=2.0, id=12343, someField=someField, someOtherField=someOtherField, isElementInA=false), ListCelement(fieldA=2.0, id=1234566, someField=someField, someOtherField=someOtherField, isElementInA=true), ListCelement(fieldA=2.0, id=11233, someField=someField, someOtherField=someOtherField, isElementInA=false)]

reference a local variable from string

Is there a way that I could represent a local variable name by passing a string to represent the variable name?
for example something like this
val arr1 = arrayOf(1,2,3,"Apple")
val arr2: Array<Int> = Array(6,{i -> i * 2})
val arr3: Array<Int> = Array<Int>(6,{i -> i+1})
val arr4 = arrayOf<Int>(1,2,3)
for (i in 1..4){
val arrResult = java.util.Arrays.deepToString("arr${i}")
println(arrResult)
}
Such question usually means that you want to use dictionaries. In this case an array suffices:
fun main() {
val arr0 = arrayOf(1,2,3,"Apple")
val arr1: Array<Int> = Array(6,{i -> i * 2})
val arr2: Array<Int> = Array<Int>(6,{i -> i+1})
val arr3 = arrayOf<Int>(1,2,3)
val arrs = arrayOf(arr0, arr1, arr2, arr3)
for (i in 0..3) {
val arrResult = java.util.Arrays.deepToString(arrs[i])
println(arrResult)
}
}