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 }
}
}
}
Related
How to efficiently implement below c++ function in rust? The data structure must be tree based (BTree, RBTree, etc).
Given a sorted map m, a key target, and a value val.
Find the lower_bound entry (the first key >= target). return DEFAULT if no such entry.
If the value of the found entry <= val and it has previous entry, return value of previous entry.
If the value of the found entry > val and it has next entry, return value of the next entry.
Otherwise, return the found value.
template<class K, class V>
V find_neighbor(const std::map<K, V>& m, const K& target, const V& val) {
auto it = m.lower_bound(target);
if( it == m.end() ) return V{}; // DEFAULT value.
if( it->second <= val && it != m.begin() )
return (--it)->value; // return previous value
if( it->second > val && it != (--m.end()) )
return (++it)->value; // return next value
return it->second; // return target value
}
Thats what I've got.
Create trait FindNeighbor that adds the function find_neighbor to all BTreeMaps
I'm quite confused what the algorithm does, though, tbh. But it should (tm) behave identical to the C++ version.
If you use this in an actual project though, for the love of god, please write unit tests for it. 😄
use std::{borrow::Borrow, collections::BTreeMap};
trait FindNeighbor<K, V> {
type Output;
fn find_neighbor(&self, target: K, val: V) -> Self::Output;
}
impl<K, V, KI, VI> FindNeighbor<K, V> for BTreeMap<KI, VI>
where
K: Borrow<KI>,
V: Borrow<VI>,
KI: Ord,
VI: Default + PartialOrd + Clone,
{
type Output = VI;
fn find_neighbor(&self, target: K, val: V) -> VI {
let val: &VI = val.borrow();
let target: &KI = target.borrow();
let mut it = self.range(target..);
match it.next() {
None => VI::default(),
Some((_, it_value)) => {
if it_value <= val {
match self.range(..target).rev().next() {
Some((_, prev_val)) => prev_val.clone(),
None => it_value.clone(),
}
} else {
match it.next() {
Some((_, next_val)) => next_val.clone(),
None => it_value.clone(),
}
}
}
}
}
}
fn main() {
let map = BTreeMap::from([(1, 5), (2, 3), (3, 8)]);
println!("{:?}", map.find_neighbor(3, 10));
}
3
Note a couple of differences between C++ and Rust:
Note that there are trait annotations on the generic parameters. Generic functions work a little different than C++ templates. All the capabilities that get used inside of a generic method have to be annotated as trait capabilities. The advantage is that generics are then guaranteed to work with every type they take, no random compiler errors can occur any more. (C++ templates are more like duck-typing, while Rust generics are strongly typed)
We implement a trait that adds new functionality to an external struct. That is something that also doesn't exist in C++, and tbh I really like this mechanic in Rust.
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.
I'm running into a problem when trying to use the Z3 optimizer to solve graph partitioning problems. Specifically, the code bellow will fail to produce a satisfying model:
namespace z3 {
expr ite(context& con, expr cond, expr then_, expr else_) {
return to_expr(con, Z3_mk_ite(con, cond, then_, else_));;
}
}
bool smtPart(void) {
// Graph setup
vector<int32_t> nodes = {{ 4, 2, 1, 1 }};
vector<tuple<node_pos_t, node_pos_t, int32_t>> edges;
GraphType graph(nodes, edges);
// Z3 setup
z3::context con;
z3::optimize opt(con);
string n_str = "n", sub_p_str = "_p";
// Re-usable constants
z3::expr zero = con.int_val(0);
// Create the sort representing the different partitions.
const char* part_sort_names[2] = { "P0", "P1" };
z3::func_decl_vector part_consts(con), part_preds(con);
z3::sort part_sort =
con.enumeration_sort("PartID",
2,
part_sort_names,
part_consts,
part_preds);
// Create the constants that represent partition choices.
vector<z3::expr> part_vars;
part_vars.reserve(graph.numNodes());
z3::expr p0_acc = zero,
p1_acc = zero;
typename GraphType::NodeData total_weight = typename GraphType::NodeData();
for (const auto& node : graph.nodes()) {
total_weight += node.data;
ostringstream name;
name << n_str << node.id << sub_p_str;
z3::expr nchoice = con.constant(name.str().c_str(), part_sort);
part_vars.push_back(nchoice);
p0_acc = p0_acc + z3::ite(con,
nchoice == part_consts[0](),
con.int_val(node.data),
zero);
p1_acc = p1_acc + z3::ite(con,
nchoice == part_consts[1](),
con.int_val(node.data),
zero);
}
z3::expr imbalance = con.int_const("imbalance");
opt.add(imbalance ==
z3::ite(con,
p0_acc > p1_acc,
p0_acc - p1_acc,
p1_acc - p0_acc));
z3::expr imbalance_limit = con.real_val(total_weight, 100);
opt.add(imbalance <= imbalance_limit);
z3::expr edge_cut = zero;
for(const auto& edge : graph.edges()) {
edge_cut = edge_cut +
z3::ite(con,
(part_vars[edge.node0().pos()] ==
part_vars[edge.node1().pos()]),
zero,
con.int_val(edge.data));
}
opt.minimize(edge_cut);
opt.minimize(imbalance);
z3::check_result opt_result = opt.check();
if (opt_result == z3::check_result::sat) {
auto mod = opt.get_model();
size_t node_id = 0;
for (z3::expr& npv : part_vars) {
cout << "Node " << node_id++ << ": " << mod.eval(npv) << endl;
}
return true;
} else if (opt_result == z3::check_result::unsat) {
cerr << "Constraints are unsatisfiable." << endl;
return false;
} else {
cerr << "Result is unknown." << endl;
return false;
}
}
If I remove the minimize commands and use a solver instead of an optimize it will find a satisfying model with 0 imbalance. I can also get an optimize to find a satisfying model if I either:
Remove the constraint imbalance <= imbalance_limit or
Make the imbalance limit reducible to an integer. In this example the total weight is 8. If the imbalance limit is set to 8/1, 8/2, 8/4, or 8/8 the optimizer will find satisfying models.
I have tried to_real(imbalance) <= imbalance_limit to no avail. I also considered the possibility that Z3 is using the wrong logic (one that doesn't include theories for real numbers) but I haven't found a way to set that using the C/C++ API.
If anyone could tell me why the optimizer fails in the presence of the real valued constraint or could suggest improvements to my encoding it would be much appreciated. Thanks in advance.
Could you reproduce the result by using opt.to_string() to dump the state (just before the check())? This would create a string formatted in SMT-LIB2 with optimization commands. It is then easier to exchange benchmarks. You should see that it reports unsat with the optimization commands and sat if you comment out the optimization commands.
If you are able to produce a bug, then post an issue on GitHub.com/z3prover/z3.git with a repro.
If not, you can use Z3_open_log before you create the z3 context and record a rerunnable log file. It is possible (but not as easy) to dig into unsoundness bugs that way.
It turns out that this was a bug in Z3. I created an Issue on GitHub and they have since responded with a patch. I'm compiling and testing the fix now, but I expect it to work.
Edit: Yup, that patch fixed the issue for the command line tool and the C++ API.
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.
I am having difficulties with my state machine. I use a function that returns the new state based on input parameters oldState and two input parameters.
In this function I have a lot of nested switch cases. I'd rather use a 2x2 transition matrix but have no idea how to use it. I did make a transition table from the state diagram with sates and inputs.
But how exaclty do I use the 2 dim. array transition_table[3][4]?
You stated you currently have something like this:
StateType transition (StateType old, InputType one, InputType two) {
//... nested switch statements
return new_state;
}
So, it seems what you need is a 3-dimensional array:
#define MAX_STATES 12
#define MAX_INPUT_VAL 2
StateType transitionTable[MAX_STATES][MAX_INPUT_VAL][MAX_INPUT_VAL] = {
{ { StateA, StateB },
{ StateC, StateD } },
{ { StateE, StateF },
{ StateG, StateH } },
{ { StateI, StateJ },
{ StateK, StateL } },
//...
};
Then you would transition like this:
new_state = transitionTable[StateIndex(old)][one][two];
So, assuming that StateIndex(StateC) returns 2, then:
old = StateC;
new_state = transitionTable[StateIndex(old)][1][0];
assert(new_state == StateK);
would result in new_state holding StateK.
Given a matrix like this:
state1_input1 state1_input2 state1_input3
state2_input1 state2_input2 state2_input3
state3_input1 state3_input2 state3_input3
When you are in state n and receive input m, you look at row n, column m to find out the new state. Assuming you have 3 possible states and 4 possible inputs, all you need to do is:
state = transition_table[state][input]
Based on your description, you don't need a 2-dimentional array, 1 dimension is fine. It should be made this way:
void foo()
{
int States[2] = {1,2};
int currentState = 1;///initial state, let's say
int oldState;///prev. state
while(true)
{
if(currentState == 1 && *add any other condition that you need*)
{
<...>do something<...>
oldState = currentState;//saving the old state, in case you need it.
currentState = states[currentState]; //changing the state
}
else if( currentState == 2 && *add any other condition that you need*)
{
<...>some other code<...>
}
}
So you have an array of states. You then calculate the index of that array based on your input parameters (you said you use the old state and something else for it). After that you simply get the new state from the array by that index.
My explanation is a bit messy, so leave a comment if you need a clarification of some part.