Kotlin: combine certain elements in list in a functional way - list
Recently I was asked what Kotlin stdlib functions I could recommend to handle a certain problem: combine certain meetings in a list that have the same start/end time.
Let's say a meeting is given by this data class:
data class Meeting(val startTime: Int, val endTime: Int)
fun main() {
val meetings = listOf(
Meeting(10, 11),
Meeting(12, 15), // this can be merged with
Meeting(15, 17) // this one
)
println(combine(meetings))
// should print: [Meeting(startTime=10, endTime=11), Meeting(startTime=12, endTime=17)]
}
fun combine(meetings: List<Meeting>): List<Meeting> {
// TODO: elegant, functional way to do this?
}
I already solved this problem using fold, but I didn't feel it was the right use for it (a simple forEach should have been enough):
fun combine(meetings : List<Meeting>) : List<Meeting> {
return meetings.fold(mutableListOf<Meeting>()) { combined: MutableList<Meeting>, meeting: Meeting ->
val lastMeeting = combined.lastOrNull()
when {
lastMeeting == null -> combined.add(meeting)
lastMeeting.endTime == meeting.startTime -> {
combined.remove(lastMeeting)
combined.add(Meeting(lastMeeting.startTime, meeting.endTime))
}
else -> combined.add(meeting)
}
combined
}.toList()
}
Also, another solution with forEach instead of fold:
fun combine(meetings: List<Meeting>): List<Meeting> {
val combined = mutableListOf<Meeting>()
meetings.forEachIndexed { index, meeting ->
val lastMeeting = combined.lastOrNull()
when {
lastMeeting == null -> combined.add(meeting)
lastMeeting.endTime == meeting.startTime ->
combined[combined.lastIndex] = Meeting(lastMeeting.startTime, meeting.endTime)
else -> combined.add(meeting)
}
}
return combined.toList()
}
However, I feel there must be a more elegant, functional way with less mutability to solve this. How would you approach this?
Oh, and before I forget: of course I have some unit tests for you to play around with! 😇
#Test
fun `empty meeting list returns empty list`() {
val meetings = emptyList<Meeting>()
assertEquals(emptyList<Meeting>(), combine(meetings))
}
#Test
fun `single meeting list returns the same`() {
val meetings = listOf(Meeting(9, 10))
assertEquals(meetings, combine(meetings))
}
#Test
fun `3 different meetings`() {
val meetings = listOf(Meeting(9, 10), Meeting(11, 12), Meeting(13, 14))
assertEquals(meetings, combine(meetings))
}
#Test
fun `2 meetings that can be merged`() {
val meetings = listOf(Meeting(9, 10), Meeting(10, 11))
assertEquals(listOf(Meeting(9, 11)), combine(meetings))
}
#Test
fun `3 meetings that can be merged`() {
val meetings = listOf(Meeting(9, 10), Meeting(10, 11), Meeting(11, 13))
assertEquals(listOf(Meeting(9, 13)), combine(meetings))
}
And here's a Kotlin Playground link to get started.
Thanks a lot for your help! 😊
Recursive and immutable.
fun combine(meetings: List<Meeting>): List<Meeting> {
return if (meetings.isEmpty()) meetings
else combineRecurse(emptyList(), meetings.first(), meetings.drop(1))
}
fun combineRecurse(tail: List<Meeting>, head: Meeting, remaining: List<Meeting>): List<Meeting> {
val next = remaining.firstOrNull()
return when {
next == null -> tail + head
next.startTime == head.endTime -> combineRecurse(tail, Meeting(head.startTime, next.endTime), remaining.drop(1))
else -> combineRecurse(tail + head, next, remaining.drop(1))
}
}
The recursive function takes 3 arguments:
tail: Processed meetings that cannot be combined anymore
head: The meeting we're currently working on and trying to extend as much as possible
remaining: Unprocessed meetings
I find the solution with fold most elegant, also it doesn't allocate any excess objects. However, I was able to simplify it:
fun combine(meetings : List<Meeting>) : List<Meeting> {
return meetings.fold(mutableListOf()) { combined: MutableList<Meeting>, meeting: Meeting ->
val prevMeeting = combined.lastOrNull()
if (prevMeeting == null || prevMeeting.endTime < meeting.startTime) {
combined.add(meeting)
} else {
combined[combined.lastIndex] = Meeting(prevMeeting.startTime, meeting.endTime)
}
combined
}
}
Note that this doesn't have to search through the list to remove the previous meeting. It just replaces the previous meeting with the combination of the meetings.
It does need one mutable list, because this solution should be efficient.
Here's a functional way. The idea is to get all the meeting endpoints in a list, then compare pairs of adjacent endTime and startTime and filter out those that are equal.
Then group the result into pairs and make the resulting list of meetings from them.
fun combine(meetings: List<Meeting>): List<Meeting> {
return meetings
.zipWithNext { current, next -> listOf(current.endTime, next.startTime) }
.filterNot { (end, start) -> end == start }
.flatten()
.let { listOf(meetings.first().startTime) + it + listOf(meetings.last().endTime) }
.chunked(2) { (start, end) -> Meeting(start, end) }
}
It works with non-empty lists of meetings; handling an empty one is a matter of an additional if (meetings.isEmpty()) return meetings check in the beginning.
I don't find it, however, more elegant because it requires significantly more object allocations for a big list of meetings. Turning meetings into a sequence with the .asSequence() function in the beginning of the operation chain might help a bit, but not that much.
Honestly, I believe this would be better handled on map creation/insertion rather than attempting to condense it later on. However, this seems to work while avoiding the use of fold and other functions that you seem to prefer not to use.
Also, depending on the size of the original meetings list, it might be worth creating a list of the extended meetings (opposite of stripped) and use that instead of meetings in findLastLinkedMeeting. Not sure if itd make much of difference though.
fun combine(): List<Meeting> {
val stripped = meetings.filter { meeting -> meetings.none { isContinuation(it, meeting) } }
return stripped.map { stripped ->
val fromMeeting = findLastLinkedMeeting(stripped)
if (fromMeeting == stripped) stripped else Meeting(stripped.startTime, fromMeeting.endTime)
}
}
private tailrec fun findLastLinkedMeeting(fromMeeting: Meeting): Meeting {
val nextMeeting = meetings.find { toMeeting -> isContinuation(fromMeeting, toMeeting) }
return if (nextMeeting != null) findLastLinkedMeeting(nextMeeting) else fromMeeting
}
private fun isContinuation(fromMeeting: Meeting, toMeeting: Meeting) =
fromMeeting.endTime == toMeeting.startTime
Using mutability inside a "functional" call is fair, as long as we don't expose it.
This is very similar to your first version, with a few arguably minor differences.
Aggregation function factored out.
Aggregration function is almost in single-expression form.
Interesting case of when is only a single expression.
fun combine(meetings: List<Meeting>): List<Meeting> {
fun add(ms: MutableList<Meeting>, m: Meeting) : MutableList<Meeting> {
ms.lastOrNull().let {
when {
it == null ->
ms.add(m)
it.endTime == m.startTime ->
ms[ms.lastIndex] = Meeting(it.startTime, m.endTime)
else ->
ms.add(m)
}
}
return ms
}
return meetings.fold(mutableListOf(), ::add)
}
Going one step further, we can use reduce instead of fold, at the expense of potentially introducing many short-lived lists (but never many at a time due to using a sequence; I'd hope the JIT would optimize that part) but adding the potential for parallelization:
fun combine(meetings: List<Meeting>): List<Meeting> {
fun add(ml: MutableList<Meeting>, mr: MutableList<Meeting>) : MutableList<Meeting> {
val leftLast = ml.lastOrNull()
val rightFirst = mr.firstOrNull()
when {
leftLast == null || rightFirst == null || leftLast.endTime != rightFirst.startTime ->
ml.addAll(mr)
else -> {
// assert(leftLast.endTime == rightFirst.startTime)
ml[ml.lastIndex] = Meeting(leftLast.startTime, rightFirst.endTime)
mr.removeAt(0)
ml.addAll(mr)
}
}
return ml
}
return meetings.asSequence().map { mutableListOf(it) }.reduce(::add)
}
Of course, the same principle can be applied to immutable lists:
fun combine(meetings: List<Meeting>): List<Meeting> {
fun add(ml: List<Meeting>, mr: List<Meeting>) : List<Meeting> {
val leftLast = ml.lastOrNull()
val rightFirst = mr.firstOrNull()
return when {
leftLast == null || rightFirst == null || leftLast.endTime != rightFirst.startTime ->
ml + mr
else -> {
// assert(leftLast.endTime == rightFirst.startTime)
ml.dropLast(1) + Meeting(leftLast.startTime, rightFirst.endTime) + mr.drop(1)
}
}
}
return meetings.asSequence().map { listOf(it) }.reduce(::add)
}
This is probably the most functional-style-ish variant, at the potential added cost of more object creations. For actual performance considerations we'd have to benchmark, of course.
Related
Memory error. A better way to count possibilities? 12 million is too many?
Maybe I should compile the list at different times so it doesn't load all at once? Everything worked until I added row 4 and 5, so I'm not sure it's a coding issue. Seems more like a noobie issue, not knowing what tools are my disposal. The overall goal is to generate a combination of numbers (0-100) in 5 rows. Without any duplicates. 1,2,3,4,5 to 1,2,3,4,6 ect ect until all combinations are completed. I've attached the code below :: class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val startButton = findViewById<Button>(R.id.startButton) val possibilities = findViewById<TextView>(R.id.possibilities) val rowOne = listOf("1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55","56","57","58","59","60","61","62","63","64","65","66","67","68","69","70") val rowTwo = listOf("2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55","56","57","58","59","60","61","62","63","64","65","66","67","68","69","70") val rowThree = listOf("3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55","56","57","58","59","60","61","62","63","64","65","66","67","68","69","70") val rowFour = listOf("4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55","56","57","58","59","60","61","62","63","64","65","66","67","68","69","70") val rowFive = listOf("5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55","56","57","58","59","60","61","62","63","64","65","66","67","68","69","70") val combinations = rowOne.flatMap { rowOne -> rowTwo.flatMap { rowTwo -> rowThree.flatMap { rowThree -> rowFour.flatMap { rowFour -> rowFive.map { rowFive -> "$rowFour $rowFive" } }.map { resultThree -> "$resultThree $rowThree" } }.map { resultTwo -> "$resultTwo $rowTwo" } }.map { resultOne -> "$resultOne $rowOne" } }.toList() startButton.setOnClickListener { possibilities.text = ("${(combinations).size} Possibilities")}
KotlinTest property based testing and generators
I have the following definition of a directed graph in Kotlin. (I'm still learning Kotlin so please forgive any shortcomings. Improvements and suggestions are always welcome.) My goal is to have a method, reverse, which maintains the vertices and loops but swaps the directions of the other edges. // We use an edge list because it makes it the easiest to swap. data class ReversibleDirectedGraph<T>(val vertices: Set<T>, val edgeList: List<Pair<T,T>>) { // This should be a self-inverting function. fun reverse(): ReversibleDirectedGraph<T> { // Make sure all vertices in edgeList are in vertices. val allVertices = edgeList.flatMap { it.toList() } require(allVertices.all { it in vertices }) { "Illegal graph specification" } // Swap the edges. val newEdgeList = edgeList.map { it.second to it.first } return ReversibleDirectedGraph(allVertices.toSet(), newEdgeList) } } fun main() { // Example test: works correctly. Double edge reversal results in original graph. val g = ReversibleDirectedGraph(setOf(0, 1, 2, 3), listOf(0 to 1, 2 to 1, 3 to 2, 3 to 0, 1 to 3)) println(g) val gr = g.reverse() println(gr) val grr = gr.reverse() println(grr) println(grr == g) } I'd like to use property-based testing to test this code using KotinTest, but I'm having trouble structuring it to properly produce random samples of undirected graphs. If I can achieve that point, I can reverse the edge direction twice and then ensure that the original graph is achieved. I'm familiar with Gen.list, Gen.choose, etc, but I can't seem to fit the pieces together to get the final product, i.e. the random undirecteed graph. I've gotten up to this, but this is clearly missing pieces, and I was hoping that someone might be able to assist. I suspect I could do it in Scala since I have more experience there, but I am determined to learn Kotlin. Ultimately, something along the lines of: class ReversibleDirectedGraphTest: StringSpec() { init { "reversibleDirectedGraphTest" { forAll { g: ReversibleDirectedGraph<Int> -> assertEqual(g.reverse().reverse() == g) } } } } } Any help / suggestions would be greatly appreciated. Thanks!
I ended up following the suggestion of #monkjack and creating my own Gen. I had to explicitly provide the Gen to the forAll, and a rare exception would come up with "bound must be greater than origin", but this works and the vast majority of test cases that are produced are valid and do not need to be intercepted by the try...catch. class GraphGen: Gen<ReversibleDirectedGraph<Int>> { override fun constants() = listOf( ReversibleDirectedGraph(emptySet(), emptySet()), ReversibleDirectedGraph(setOf(0), setOf(0 to 0)), ReversibleDirectedGraph(setOf(0, 1), emptySet()), ReversibleDirectedGraph(setOf(0, 1), setOf(0 to 1)) ) override fun random(): Sequence<ReversibleDirectedGraph<Int>> = generateSequence { val vertexGen = Gen.choose(0, 20) val vertices = Gen.set(vertexGen).random().first() val vertexList = vertices.toList() val edgeGen = Gen.set(Gen.pair(Gen.from(vertexList), Gen.from(vertexList))).random() // On rare occasions, this throws an exception with origin and bound, probably due // to the number of sets. In those cases, we use an emptySet instead as a placeholder. val edges = try { edgeGen.first() } catch (e: IllegalArgumentException) { null } ReversibleDirectedGraph(vertices, edges?: emptySet()) } } class ReversibleDirectedGraphTest: StringSpec() { init { "reversibleDirectedGraphTest" { forAll(GraphGen()) { g -> g.reverse().reverse() == g } } } }
Display a chunked items list in Java 8
With the following code: public class Main { public static void main(String[] args) { final List<Integer> items = IntStream.rangeClosed(0, 23).boxed().collect(Collectors.toList()); final String s = items .stream() .map(Object::toString) .collect(Collectors.joining(",")) .toString() .concat("."); System.out.println(s); } } I get: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23. What I would like to do, is to break the line every 10 items, in order to get: 0,1,2,3,4,5,6,7,8,9, 10,11,12,13,14,15,16,17,18,19, 20,21,22,23. I have try a lot of things after googling without any success ! Can you help me ? Thanks, Olivier.
If you're open to using a third-party library, the following will work using Eclipse Collections Collectors2.chunk(int). String s = IntStream.rangeClosed(0, 23) .boxed() .collect(Collectors2.chunk(10)) .collectWith(MutableList::makeString, ",") .makeString("", ",\n", "."); The result of Collectors2.chunk(10) will be a MutableList<MutableList<Integer>>. At this point I switch from the Streams APIs to using native Eclipse Collections APIs which are available directly on the collections. The method makeString is similar to Collectors.joining(). The method collectWith is like Stream.map() with the difference that a Function2 and an extra parameter are passed to the method. This allows a method reference to be used here instead of a lambda. The equivalent lambda would be list -> list.makeString(","). If you use just Eclipse Collections APIs, this problem can be simplified as follows: String s = Interval.zeroTo(23) .chunk(10) .collectWith(RichIterable::makeString, ",") .makeString("", ",\n", "."); Note: I am a committer for Eclipse Collections.
If all you want to do is process these ascending numbers, you can do it like String s = IntStream.rangeClosed(0, 23).boxed() .collect(Collectors.groupingBy(i -> i/10, LinkedHashMap::new, Collectors.mapping(Object::toString, Collectors.joining(",")))) .values().stream() .collect(Collectors.joining(",\n", "", ".")); This solution can be adapted to work on an arbitrary random access list as well, e.g. List<Integer> items = IntStream.rangeClosed(0, 23).boxed().collect(Collectors.toList()); String s = IntStream.range(0, items.size()).boxed() .collect(Collectors.groupingBy(i -> i/10, LinkedHashMap::new, Collectors.mapping(ix -> items.get(ix).toString(), Collectors.joining(",")))) .values().stream() .collect(Collectors.joining(",\n", "", ".")); However, there is no simple and elegant solution for arbitrary streams, a limitation which applies to all kind of tasks having a dependency to the element’s position.
Here is an adaptation of the already linked in the comments Collector: private static Collector<String, ?, String> partitioning(int size) { class Acc { int count = 0; List<List<String>> list = new ArrayList<>(); void add(String elem) { int index = count++ / size; if (index == list.size()) { list.add(new ArrayList<>()); } list.get(index).add(elem); } Acc merge(Acc right) { List<String> lastLeftList = list.get(list.size() - 1); List<String> firstRightList = right.list.get(0); int lastLeftSize = lastLeftList.size(); int firstRightSize = firstRightList.size(); // they are both size, simply addAll will work if (lastLeftSize + firstRightSize == 2 * size) { System.out.println("Perfect!"); list.addAll(right.list); return this; } // last and first from each chunk are merged "perfectly" if (lastLeftSize + firstRightSize == size) { System.out.println("Almost perfect"); int x = 0; while (x < firstRightSize) { lastLeftList.add(firstRightList.remove(x)); --firstRightSize; } right.list.remove(0); list.addAll(right.list); return this; } right.list.stream().flatMap(List::stream).forEach(this::add); return this; } public String finisher() { return list.stream().map(x -> x.stream().collect(Collectors.joining(","))) .collect(Collectors.collectingAndThen(Collectors.joining(",\n"), x -> x + ".")); } } return Collector.of(Acc::new, Acc::add, Acc::merge, Acc::finisher); } And usage would be: String result = IntStream.rangeClosed(0, 24) .mapToObj(String::valueOf) .collect(partitioning(10));
In Kotlin, how do you modify the contents of a list while iterating
I have a list: val someList = listOf(1, 20, 10, 55, 30, 22, 11, 0, 99) And I want to iterate it while modifying some of the values. I know I can do it with map but that makes a copy of the list. val copyOfList = someList.map { if (it <= 20) it + 20 else it } How do I do this without a copy? Note: this question is intentionally written and answered by the author (Self-Answered Questions), so that the idiomatic answers to commonly asked Kotlin topics are present in SO. Also to clarify some really old answers written for alphas of Kotlin that are not accurate for current-day Kotlin.
First, not all copying of a list is bad. Sometimes a copy can take advantage of CPU cache and be extremely fast, it depends on the list, size, and other factors. Second, for modifying a list "in-place" you need to use a type of list that is mutable. In your sample you use listOf which returns the List<T> interface, and that is read-only. You need to directly reference the class of a mutable list (i.e. ArrayList), or it is idiomatic Kotlin to use the helper functions arrayListOf or linkedListOf to create a MutableList<T> reference. Once you have that, you can iterate the list using the listIterator() which has a mutation method set(). // create a mutable list val someList = arrayListOf(1, 20, 10, 55, 30, 22, 11, 0, 99) // iterate it using a mutable iterator and modify values val iterate = someList.listIterator() while (iterate.hasNext()) { val oldValue = iterate.next() if (oldValue <= 20) iterate.set(oldValue + 20) } This will change the values in the list as iteration occurs and is efficient for all list types. To make this easier, create helpful extension functions that you can re-use (see below). Mutating using a simple extension function: You can write extension functions for Kotlin that do an in place mutable iteration for any MutableList implementation. These inline functions will perform as fast as any custom use of the iterator and is inlined for performance. Perfect for Android or anywhere. Here is a mapInPlace extension function (which keeps the naming typical for these type of functions such as map and mapTo): inline fun <T> MutableList<T>.mapInPlace(mutator: (T)->T) { val iterate = this.listIterator() while (iterate.hasNext()) { val oldValue = iterate.next() val newValue = mutator(oldValue) if (newValue !== oldValue) { iterate.set(newValue) } } } Example calling any variation of this extension function: val someList = arrayListOf(1, 20, 10, 55, 30, 22, 11, 0, 99) someList.mapInPlace { if (it <= 20) it + 20 else it } This is not generalized for all Collection<T>, because most iterators only have a remove() method, not set(). Extension functions for Arrays You can handle generic arrays with a similar method: inline fun <T> Array<T>.mapInPlace(mutator: (T)->T) { this.forEachIndexed { idx, value -> mutator(value).let { newValue -> if (newValue !== value) this[idx] = mutator(value) } } } And for each of the primitive arrays, use a variation of: inline fun BooleanArray.mapInPlace(mutator: (Boolean)->Boolean) { this.forEachIndexed { idx, value -> mutator(value).let { newValue -> if (newValue !== value) this[idx] = mutator(value) } } } About the Optimization using only Reference Equality The extension functions above optimize a little by not setting the value if it has not changed to a different instance, checking that using === or !== is Referential Equality. It isn't worth checking equals() or hashCode() because calling those has an unknown cost, and really the referential equality catches any intent to change the value. Unit Tests for Extension Functions Here are unit test cases showing the functions working, and also a small comparison to the stdlib function map() that makes a copy: class MapInPlaceTests { #Test fun testMutationIterationOfList() { val unhappy = setOf("Sad", "Angry") val startingList = listOf("Happy", "Sad", "Angry", "Love") val expectedResults = listOf("Happy", "Love", "Love", "Love") // modify existing list with custom extension function val mutableList = startingList.toArrayList() mutableList.mapInPlace { if (it in unhappy) "Love" else it } assertEquals(expectedResults, mutableList) } #Test fun testMutationIterationOfArrays() { val otherArray = arrayOf(true, false, false, false, true) otherArray.mapInPlace { true } assertEquals(arrayOf(true, true, true, true, true).toList(), otherArray.toList()) } #Test fun testMutationIterationOfPrimitiveArrays() { val primArray = booleanArrayOf(true, false, false, false, true) primArray.mapInPlace { true } assertEquals(booleanArrayOf(true, true, true, true, true).toList(), primArray.toList()) } #Test fun testMutationIterationOfListWithPrimitives() { val otherList = arrayListOf(true, false, false, false, true) otherList.mapInPlace { true } assertEquals(listOf(true, true, true, true, true), otherList) } }
Here's what I came up with, which is a similar approach to Jayson: inline fun <T> MutableList<T>.mutate(transform: (T) -> T): MutableList<T> { return mutateIndexed { _, t -> transform(t) } } inline fun <T> MutableList<T>.mutateIndexed(transform: (Int, T) -> T): MutableList<T> { val iterator = listIterator() var i = 0 while (iterator.hasNext()) { iterator.set(transform(i++, iterator.next())) } return this }
Here is a custom solution, with an example : val sorted: MutableList<Pair<Double, T>> = ... val groups: mutableListOf<List<T>>() = ... sorted.forEachMutable { it -> if (size + it.first <= maxSize) { size += it.first group += it.second this.remove() // Removing from iterable ! } } Here is code for "forEachMutable" : fun <T> MutableIterable<T>.forEachMutable(block: Removable<T>.(T) -> Unit): Unit { val iterator: MutableIterator<T> = iterator() val removable = Removable(iterator, block) while (iterator.hasNext()) { val item = iterator.next() removable.action(item) } } class Removable<T>( private val iterator: MutableIterator<T>, private val block: Removable<T>.(T) -> Unit) { fun remove() = iterator.remove() fun action(item: T) { block(item) } } Maxime
Without having to write any new extension methods - yes, the functional paradigms are awesome, but they do generally imply immutability. If you're mutating, you might consider making that implicit by going old school: val someList = mutableListOf(1, 20, 10, 55, 30, 22, 11, 0, 99) for(i in someList.indices) { val value = someList[i] someList[i] = if (value <= 20) value + 20 else value }
You can use list.forEach { item -> item.modify() } This will modify each item in list while iterating.
What's a graceful way of doing an "if none of the above"?
I'm sure you've been there. You want to say "if flib do this, if flob do that, if flab do diet, etc" where any number of them can be true, then at the end you want an "if you didn't do ANY of them". For example (the examples below are in Swift, as I've been playing with it, but I think the situation is the same in most languages): let thing = 101 var isInteresting = false if (thing % 3 == 0) { println("\"\(thing)\" is a multiple of three.") isInteresting = true } if (thing > 100) { println("\"\(thing)\" is greater than one hundred.") isInteresting = true } if (thing > 1000) { println("\"\(thing)\" is greater than one thousand.") isInteresting = true } if !isInteresting { println("\"\(thing)\" is boring.") } I find keeping track of a boolean to tell me whether I did anything or not kinda ungainly. The only other way I came up with was this: let thing = 101 let isAMultipleOfThree = (thing % 3 == 0) let isGreaterThan100 = (thing > 100) let isGreaterThan1000 = (thing > 1000) if isAMultipleOfThree { println("\"\(thing)\" is a multiple of three.") } if isGreaterThan100 { println("\"\(thing)\" is greater than one hundred.") } if isGreaterThan1000 { println("\"\(thing)\" is greater than one thousand.") } if !(isAMultipleOfThree || isGreaterThan100 || isGreaterThan1000 ) { println("\"\(thing)\" is boring.") } but if anything that's worse (if you add a new clause you need to remember to add it in three places. So my question is, is there a neat, succinct way of doing this? I'm dreaming of an imaginary switch-like statement: switchif { //Would have fallthrough where every case condition is checked case thing % 3 == 0: println("\"\(thing)\" is a multiple of three.") case thing >100 : println("\"\(thing)\" is greater than one hundred.") case thing > 1000: println("\"\(thing)\" is greater than one thousand.") none: //Unlike 'default' this would only occur if none of the above did println("\"\(thing)\" is boring.") }
It's a good question that does not have a perfect answer. However, here's one other idea in addition to those you suggest: Encapsulate the testing machinery in a procedure to allow the calling code at least to be a bit more streamlined. Specifically, for your example, the calling code can be this: if (! doInterestingStuff(101)) { println("\"\(thing)\" is boring."); } If testing is encapsulated into a procedure: public boolean doInterestingStuff(int thing) { var isInteresting = false if (thing % 3 == 0) { println("\"\(thing)\" is a multiple of three.") isInteresting = true } if (thing > 100) { println("\"\(thing)\" is greater than one hundred.") isInteresting = true } if (thing > 1000) { println("\"\(thing)\" is greater than one thousand.") isInteresting = true } return isInteresting }
I'm not sure how you'd do this in Swift, but since you didn't give a language tag I'll answer in C++. The key to this is that && is short circuiting, and the second part won't be evaluated when the first part is false. It's the same idea as your boolean flag, but it's a little more automated. struct Tracker { Tracker() : any(false) { } bool operator()() { any = true; return true; } bool any; }; int thing = 101; Tracker tracker; if (thing % 3 == 0 && tracker()) { printf("\"%d\" is a multiple of three.\n", thing); } if (thing > 100 && tracker()) { printf("\"%d\" is greater than one hundred.\n", thing); } if (thing > 1000 && tracker()) { printf("\"%d\" is greater than one thousand.\n", thing); } if (!tracker.any) { printf("\"%d\" is boring.\n", thing); } See it in action: http://ideone.com/6MQYY2
kjhughes' answer inspired me a little: Perhaps one could write a global function that accepts an indeterminate number of key-value pairs (or even just two element arrays), where the key is a comparison and the value is the statement to run if it's true. Then return false if none of them were run, otherwise true. Update: Tried it, it's horrible! //Function: func ifNone(ifNoneFunc:()->Void, tests: Bool...) { var oneTestPassed = false for test in tests { oneTestPassed |= test } if(!oneTestPassed) { ifNoneFunc() } } //Example: let thisThing = 7 ifNone( { println("\(thisThing) is boring") }, { if(thisThing % 10 == 0) { println("\"\(thisThing)\" is a multiple of 10") return true } else { return false } }(), { if(thisThing % 3 == 0) { println("\"\(thisThing)\" is a multiple of 3") return true } else { return false } }(), { if(thisThing > 1_000_000) { println("\"\(thisThing)\" is over a million!!") return true } else { return false } }() )