I have a List and I basically want to zip it, but only every second entry.
What I mean is: I want my List [a,1,b,2] to become [(a,1),(b,2)],
I currently use zipWith.
But it does not give me the expected result, it gives me [(a,1),(1,b),(b,2)].
Am I being completely stupid right now, or is there no other solution than just ignore every second tuple? (e.g. by adding a filter afterwards) Is there no operator for that?
The chunked function in Kotlin 1.2 does exactly what you need:
val list = listOf("a", 1, "b", 2)
val newList = list.chunked(2) // returns listOf(listOf("a", 1), listOf("b", 2))
Related
So, I am implementing a POM parser. I've got to the point where I can get all dependencies, but some artifacts may be duplicate with different versions. Like gradle, I want it to just fetch the greater version among those. But in kotlin, I can only see examples of removing duplicates using distinctBy function which does not accept a condition. I wanna filter it by only removing the duplicate entry which has the lowest version and keeping the greater one. How can I do so?
The entries in my list is as follows
data class Artifact(val groupId: String, val artifactId: String, val version: String)
edit: For example, let the list be like
Artifact("com.squareup.okio", "okio-jvm", "3.2.0")
Artifact("org.jetbrains.kotlin", "kotlin-stdlib-jdk8", "1.6.20")
Artifact("org.jetbrains.kotlin", "kotlin-stdlib-common", "1.5.20")
Artifact("org.jetbrains.kotlin", "kotlin-stdlib-common", "1.6.21")
Artifact("org.jetbrains", "annotations", "13.0")
And the output is expected to be like
Artifact("com.squareup.okio", "okio-jvm", "3.2.0")
Artifact("org.jetbrains.kotlin", "kotlin-stdlib-jdk8", "1.6.20")
Artifact("org.jetbrains.kotlin", "kotlin-stdlib-common", "1.6.21")
Artifact("org.jetbrains", "annotations", "13.0")
i.e, to remove the version that is the lowest among the duplicates
The main part of this is fairly straightforward: you can use groupBy to group the artifacts by group ID and artifact ID, and then maxBy* to select the latest version from each group.
A more tricky part is comparing version strings… But luckily, the Java stdlib⁑ has a class for this!
So you can simply:
import java.lang.module.ModuleDescriptor.Version
data class Artifact(val groupId: String, val artifactId: String, val version: String)
fun main() {
// Some simple sample data:
val deps = listOf(Artifact("gp", "art", "9.2"), Artifact("gp", "art", "9.10"), Artifact("gp", "art", "10.0"), Artifact("gp2", "art2", "1.0"))
// Find the latest version of each dependency:
val latestDeps = deps.groupBy{ it.groupId to it.artifactId }.values
.map{ it.maxBy{ Version.parse(it.version) }}
// And display the result:
println(latestDeps)
}
(That creates a set. Since the items will be unique, but have no inherent ordering, it would make sense to call toSet() on the result… but I leave that to you!)
(* That's deprecated now in favour of maxByOrNull(), but I'm ignoring that for simplicity, as the groups here will never be empty.)
(⁑ If you're not running on Kotlin/JVM, then you'll have to write your own version-number-comparing function. I've written one; it's a bit fiddly, but not too hard. — I think you have to split the string on dots and then compare the individual numbers numerically, pairwise, though it's complicated if you have to handle non-numeric parts such as trailing a or -SNAPSHOT or patch numbers… If you need to do that, it would probably be best as a separate question.)
I have data in the following format
ArrayList<Map.Entry<String,ByteString>>
[
{"a":[a-bytestring]},
{"b":[b-bytestring]},
{"a:model":[amodel-bytestring]},
{"b:model":[bmodel-bytestring]},
]
I am looking for a clean way to transform this data into the format (List<Map.Entry<ByteString,ByteString>>) where the key is the value of a and value is the value of a:model.
Desired output
List<Map.Entry<ByteString,ByteString>>
[
{[a-bytestring]:[amodel-bytestring]},
{[b-bytestring]:[bmodel-bytestring]}
]
I assume this will involve the use of filters or other map operations but am not familiar enough with Kotlin yet to know this
It's not possible to give an exact, tested answer without access to the ByteString class — but I don't think that's needed for an outline, as we don't need to manipulate byte strings, just pass them around. So here I'm going to substitute Int; it should be clear and avoid any dependencies, but still work in the same way.
I'm also going to use a more obvious input structure, which is simply a map:
val input = mapOf("a" to 1,
"b" to 2,
"a:model" to 11,
"b:model" to 12)
As I understand it, what we want is to link each key without :model with the corresponding one with :model, and return a map of their corresponding values.
That can be done like this:
val output = input.filterKeys{ !it.endsWith(":model") }
.map{ it.value to input["${it.key}:model"] }.toMap()
println(output) // Prints {1=11, 2=12}
The first line filters out all the entries whose keys end with :model, leaving only those without. Then the second creates a map from their values to the input values for the corresponding :model keys. (Unfortunately, there's no good general way to create one map directly from another; here map() creates a list of pairs, and then toMap() creates a map from that.)
I think if you replace Int with ByteString (or indeed any other type!), it should do what you ask.
The only thing to be aware of is that the output is a Map<Int, Int?> — i.e. the values are nullable. That's because there's no guarantee that each input key has a corresponding :model key; if it doesn't, the result will have a null value. If you want to omit those, you could call filterValues{ it != null } on the result.
However, if there's an ‘orphan’ :model key in the input, it will be ignored.
i've got a function, into which i want to be able to pass a list of lists, like in this artificial example:
sub print_lists(#input) {
.say for #input
}
my #list_of_two_lists = ((1, 2), (3, 4));
print_lists(#list_of_two_lists);
this gives the following output, as expected:
(1, 2)
(3, 4)
but if i do this:
my #list_of_one_list = ((1, 2));
print_lists(#list_of_one_list);
i get this output:
1
2
i.e. it flattens the list containing one list of two elements, into a single list of two elements.
what am i doing wrong? what do i need to do if i want to be able to pass in a list containing a single list?
This behavior is a consequence of two Raku features, both of which are worth knowing.
The first is the Single Argument Rule. It's important enough to be worth reading the docs on, but the key takeaway is that when you pass a single list (as you do in with #list_of_one_list) constructs like for will iterate over each item in the list rather than over the list as a single item. In this case, that means iterating over the two items in the list, 1, and 2.
At this point, you might be thinking "but #list_of_one_list didn't have two items in it – it had one item: the list (1, 2)". But that's because we haven't gotten to the second point to understand: In Raku ( and ) are not what makes something a list. Instead, using the , operator is what constructs a list. This can take a tad bit of getting used to, but it's what allows Raku to treat parentheses as optional in many places that other languages require them.
To see this second point in action, I suggest you check out how .raku prints out your #list_of_lists. Compare:
my #list_of_one_list = ((1, 2));
say #list_of_one_list; # OUTPUT: «[1, 2]»
my #list-of-one-list = (1, 2),;
say #list-of-one-list; # OUTPUT: «[(1, 2)]»
And that's all the info you need to answer your question: simply add a , when you build your list of one list.
I hope that helped :)
Thanks to #RedBassett for this Ressource (Kotlin problem solving): https://kotlinlang.org/docs/tutorials/koans.html
I'm aware this question exists here:
Creating a 4 digit Random Number using java with no repetition in digits
but I'm new to Kotlin and would like to explore the direct Kotlin features.
So as the title suggests, I'm trying to find a Kotlin specific way to nicely solve generate a 4 digit number (after that it's easy to make it adaptable for length x) without repeating digits.
This is my current working solution and would like to make it more Kotlin. Would be very grateful for some input.
fun createFourDigitNumber(): Int {
var fourDigitNumber = ""
val rangeList = {(0..9).random()}
while(fourDigitNumber.length < 4)
{
val num = rangeList().toString()
if (!fourDigitNumber.contains(num)) fourDigitNumber +=num
}
return fourDigitNumber.toInt()
}
So the range you define (0..9) is actually already a sequence of numbers. Instead of iterating and repeatedly generating a new random, you can just use a subset of that sequence. In fact, this is the accepted answer's solution to the question you linked. Here are some pointers if you want to implement it yourself to get the practice:
The first for loop in that solution is unnecessary in Kotlin because of the range. 0..9 does the same thing, you're on the right track there.
In Kotlin you can call .shuffled() directly on the range without needing to call Collections.shuffle() with an argument like they do.
You can avoid another loop if you create a string from the whole range and then return a substring.
If you want to look at my solution (with input from others in the comments), it is in a spoiler here:
fun getUniqueNumber(length: Int) = (0..9).shuffled().take(length).joinToString('')
(Note that this doesn't gracefully handle a length above 10, but that's up to you to figure out how to implement. It is up to you to use subList() and then toString(), or toString() and then substring(), the output should be the same.)
How can I remove specific elements from a list(=multi-valued-attribute) using a map? For example, let's say I want to filter out all b's in a given list:
<["a", "b", "c", "b"]: {<table.(it)>}; separator=",">
table ::= ["b":, default: key]
The desired outcome would be "a,c" but the actual result is "a,,c,"
The thing is that the map successfully turn b's into nulls, but then they're wrapped in an anonymous template {} and become non-null values. So they won't go away with strip() function, either.
So the question is, would it be possible to filter a list using a map by slightly modifying the code above?
update
I've found a workaround:
filter(it) ::= "<if(it)><it><endif>"
<["a", "b", "c", "b"]: {<table.(it)>}: filter(); separator=",">
This gives the result I wanted: a,c
Might not want to filter in your template, but nonetheless, could be a bug.
Ok, i checked it out. That gives empty not null so it thinks it's an item. ST treats false conditionals same way: empty not null. I think you need to filter in model.