Is there a neat way of joining two different lists of different types where they share the same attribute that they need to be matched on (like i id field) and then return a list of matching pairs?
y
Youssef Shoaib [MOD]
01/19/2024, 7:38 AM
associateBy
on one of them, then a
map
on the other, pairing it with the matching value from the first one
s
simon.vergauwen
01/19/2024, 10:40 AM
+1 to what @Youssef Shoaib [MOD] said, a code example:
Copy code
inline fun <A, B, C, Key> Iterable<A>.zipBy(
other: Iterable<B>,
keySelectorA: (A) -> Key,
keySelectorB: (B) -> Key,
combine: (A, B) -> C
): List<C> {
val firstByKey = associateBy(keySelector)
val secondByKey = other.associateBy(keySelector)
return (firstByKey.keys intersect secondByKey.keys)
.map { key -> firstByKey.getValue(it) to secondByKey.getValue(it) }
}
•
intersect
creates a
Set
of all common
Key
•
getValue
returns the value for
Key
, (and throws NoSuchKeyElementException but that is impossible in this case)
• keySelector maps
A
and
B
to
Key
, in your case
id field
• combine allows to transform common value to be transformed in your case
::Pair
.
simon.vergauwen
01/19/2024, 10:42 AM
A more optimised implementation might be possible, but not sure if it's worth implementing.
y
Youssef Shoaib [MOD]
01/19/2024, 10:45 AM
Yeah I was thinking of only doing
associateBy
on one and iterate over the other with
mapNotNull
, but I think it's likely an unnecessary optimizatuon
🤔 1
s
simon.vergauwen
01/19/2024, 10:54 AM
Nope, that's definitely a lot better @Youssef Shoaib [MOD] 😄 Code is not more complex IMO.
Copy code
public inline fun <A, B, C, Key> Iterable<A>.zipBy(
other: Iterable<B>,
keySelectorA: (A) -> Key,
keySelectorB: (B) -> Key,
combine: (A, B) -> C
): List<C> {
val aByKey = associateBy(keySelectorA)
return other.mapNotNull { b ->
val key = keySelectorB(b)
val aOrNull = aByKey[key]
aOrNull?.let { a -> combine(a, b) }
}
}