问题
I want to combine two different length lists. For example;
val list1 = listOf(1,2,3,4,5)
val list2 = listOf("a","b","c")
I want to result like this
(1,"a",2,"b",3,"c",4,5)
Is there any suggestion?
回答1:
You may use the .zip
function for that
list1.zip(list2){ a,b -> listOf(a,b)}.flatten()
The only problem is that it will only process elements, with both sets, so if (like in the example) let's have different size - it will not work
The alternative could be to add specific markers and filter them or to just use iterators for that. I found an elegant solution with sequence{..}
function
val result = sequence {
val first = list1.iterator()
val second = list2.iterator()
while (first.hasNext() && second.hasNext()) {
yield(first.next())
yield(second.next())
}
yieldAll(first)
yieldAll(second)
}.toList()
回答2:
- If the elements from the source lists can occur in any order in the resulting list, then
>>> list1 + list2
res12: kotlin.collections.List<kotlin.Any> = [1, 2, 3, 4, 5, a, b, c]
- If the elements from the source lists should alternate in the resulting list and list1 is longer than list2, then
>>> list1.zip(list2).flatMap { listOf(it.first, it.second) } + list1.drop(list2.size)
res16: kotlin.collections.List<kotlin.Any> = [1, a, 2, b, 3, c, 4, 5]
回答3:
You could do it like this:
val mergedList = with(setOf(list1, list2).sortedByDescending { it.count() }) {
first().mapIndexed { index, e ->
listOfNotNull(e, last().getOrNull(index))
}
}.flatten()
First, you put both lists in a Set
, then you sort it (descending) by the number of elements yielding a list of lists.
The first list has the most elements will be used for iteration.
Using mapIndexed
you can use the index
to access the corresponding element in the second list. If there is none, null
is returned and it will be filtered out by listOfNotNull
. In the end you flatten the resulting list of lists and you get the desired result:
[1, a, 2, b, 3, c, 4, 5]
回答4:
I think Eugenes answer already contains all you need to know to combine two lists (be it zip
or combining all elements).
In case you want to combine an arbitrary number of lists, one item per alternating list, you may also be interested in the following approach:
fun combine(vararg lists: List<*>) : List<Any> = mutableListOf<Any>().also {
combine(it, lists.map(List<*>::iterator))
}
private tailrec fun combine(targetList: MutableList<Any>, iterators: List<Iterator<*>>) {
iterators.asSequence()
.filter(Iterator<*>::hasNext)
.mapNotNull(Iterator<*>::next)
.forEach { targetList += it }
if (iterators.asSequence().any(Iterator<*>::hasNext))
combine(targetList, iterators)
}
Calling it then looks as follows and leads to the value seen in the comment:
combine(list1, list2) // List containing: 1, "a", 2, "b", 3, "c", 4, 5
combine(list1, list2, listOf("hello", "world")) // 1, "a", "hello", 2, "b", "world", 3, "c", 4, 5
A simplified approach to the second part of Eugenes answer could be implemented using following code; that, of course, isn't lazy anymore as you get a list back ;-) (but maybe you even translated it directly to a list, so you can also use this approach):
fun List<Any>.combine(other: List<Any>) : List<Any> = mutableListOf<Any>().also {
val first = iterator()
val second = other.iterator()
while (first.hasNext() || second.hasNext()) {
if (first.hasNext()) it.add(first.next())
if (second.hasNext()) it.add(second.next())
}
}
Calling it would work as follows:
list1.combine(list2)
回答5:
Here's my take:
fun <T> zip(vararg iterables: Iterable<T>): List<T> = iterables
.map { it.iterator() }
.toList()
.let { iterators ->
mutableListOf<T>()
.also { list ->
while (
iterators.any {if (it.hasNext()) list.add(it.next()) else false }
) { }
}
}
回答6:
Your lists are of inconvertible types (Ints and Strings) so you have to have MutableList<Any>
so that you can add both types:
val allItems = mutableListOf<Any>(1,2,3,4,5)
val list2 = listOf("a","b","c")
allItems.addAll(list2)
来源:https://stackoverflow.com/questions/55404428/how-to-combine-two-different-length-lists-in-kotlin