@@ -2,91 +2,194 @@ package org.akanework.gramophone.logic.utils.flows
22
33import kotlinx.coroutines.flow.Flow
44import kotlinx.coroutines.flow.flow
5+ import kotlin.math.abs
6+ import kotlin.math.min
57
68sealed class IncrementalCommand <T >(val after : List <T >) {
79 class Begin <T >(after : List <T >) : IncrementalCommand<T>(after)
810 class Insert <T >(val pos : Int , val count : Int , after : List <T >) : IncrementalCommand<T>(after)
911 class Remove <T >(val pos : Int , val count : Int , after : List <T >) : IncrementalCommand<T>(after)
10- // class Move<T>(val pos: Int, val count: Int, val outPos: Int, after: List<T>) : IncrementalCommand<T>(after)
12+ class Move <T >(val pos : Int , val count : Int , val outPos : Int , after : List <T >) : IncrementalCommand<T>(after)
1113 class Update <T >(val pos : Int , val count : Int , after : List <T >) : IncrementalCommand<T>(after)
1214}
1315
14- inline fun <T , R > Flow<IncrementalCommand<T>>.mapIncremental (
15- crossinline predicate : (T ) -> R
16+ inline fun <T , R > Flow<IncrementalCommand<T>>.flatMapIncremental (
17+ crossinline predicate : (T ) -> List < R >
1618): Flow <IncrementalCommand <R >> = flow {
17- var last: List <R >? = null
19+ var last: List <List <R >>? = null
20+ var lastFlat: List <R >? = null
1821 collect { command ->
19- var new: List <R >
22+ var new: List <List <R >>
23+ var newFlat: List <R >? = null
2024 when {
2125 command is IncrementalCommand .Begin || last == null -> {
2226 new = command.after.map(predicate)
23- emit(IncrementalCommand .Begin (new))
27+ newFlat = new.flatMap { it }
28+ emit(IncrementalCommand .Begin (newFlat))
2429 }
2530 command is IncrementalCommand .Insert -> {
2631 new = ArrayList (last!! )
32+ var totalSize = 0
2733 for (i in command.pos.. < command.pos+ command.count) {
28- new.add(i, predicate(command.after[i]))
34+ val item = predicate(command.after[i])
35+ totalSize + = item.size
36+ new.add(i, item)
37+ }
38+ if (totalSize > 0 ) {
39+ var totalStart = 0
40+ for (i in 0 .. < command.pos) {
41+ totalStart + = new[i].size
42+ }
43+ newFlat = new.flatMap { it }
44+ emit(IncrementalCommand .Insert (totalSize, totalStart, newFlat))
45+ }
46+ }
47+ command is IncrementalCommand .Move -> {
48+ new = ArrayList (last!! )
49+ var totalSize = 0
50+ for (i in command.pos.. < command.pos+ command.count) {
51+ totalSize + = new.removeAt(i).size
52+ }
53+ for (i in command.outPos.. < command.outPos+ command.count) {
54+ new.add(i, last!! [i - command.outPos + command.pos])
55+ }
56+ if (totalSize > 0 ) {
57+ var totalStart = 0
58+ for (i in 0 .. < command.pos) {
59+ totalStart + = last!! [i].size
60+ }
61+ var totalOutStart = 0
62+ for (i in 0 .. < command.outPos) {
63+ totalOutStart + = new[i].size
64+ }
65+ newFlat = new.flatMap { it }
66+ emit(IncrementalCommand .Move (totalStart, totalSize, totalOutStart, newFlat))
2967 }
30- emit(IncrementalCommand .Insert (command.pos, command.count, new))
3168 }
3269 command is IncrementalCommand .Remove -> {
3370 new = ArrayList (last!! )
71+ var totalSize = 0
3472 for (i in command.pos.. < command.pos+ command.count) {
35- new.removeAt(i)
73+ totalSize + = new.removeAt(i).size
74+ }
75+ if (totalSize > 0 ) {
76+ var totalStart = 0
77+ for (i in 0 .. < command.pos) {
78+ totalStart + = new[i].size
79+ }
80+ newFlat = new.flatMap { it }
81+ emit(IncrementalCommand .Remove (totalSize, totalStart, newFlat))
3682 }
37- emit(IncrementalCommand .Remove (command.pos, command.count, new))
3883 }
3984 command is IncrementalCommand .Update -> {
4085 new = ArrayList (last!! )
86+ var removed = 0
87+ var added = 0
4188 for (i in command.pos.. < command.pos+ command.count) {
42- new[i] = predicate(command.after[i])
89+ removed + = new[i].size
90+ val item = predicate(command.after[i])
91+ added + = item.size
92+ new[i] = item
93+ }
94+ if (removed != 0 || added != 0 ) {
95+ var baseStart = 0
96+ for (i in 0 .. < command.pos) {
97+ baseStart + = new[i].size
98+ }
99+ val baseSize = min(added, removed)
100+ val offsetStart = baseStart + baseSize
101+ var offsetCount = abs(added - removed)
102+ newFlat = new.flatMap { it }
103+ if (removed > added) {
104+ val dummy = ArrayList (lastFlat!! )
105+ for (i in offsetStart.. < offsetStart+ offsetCount) {
106+ dummy.removeAt(i)
107+ }
108+ emit(IncrementalCommand .Remove (offsetStart, offsetCount, dummy))
109+ } else if (removed < added) {
110+ val dummy = ArrayList (lastFlat!! )
111+ for (i in offsetStart.. < offsetStart+ offsetCount) {
112+ dummy.add(i, newFlat[i])
113+ }
114+ emit(IncrementalCommand .Insert (offsetStart, offsetCount, dummy))
115+ }
116+ if (removed != 0 && added != 0 ) {
117+ emit(IncrementalCommand .Update (baseStart, baseSize, newFlat))
118+ }
43119 }
44- emit(IncrementalCommand .Update (command.pos, command.count, new))
45120 }
46121 else -> throw IllegalArgumentException (" code bug, IncrementalCommand case exhausted" )
47122 }
48123 last = new
124+ lastFlat = newFlat ? : lastFlat
49125 }
50126}
51- /*
127+
52128inline fun <T > Flow<IncrementalCommand<T>>.filterIncremental (
129+ crossinline predicate : (T ) -> Boolean
130+ ): Flow <IncrementalCommand <T >> = flatMapIncremental {
131+ if (predicate(it)) listOf (it) else emptyList()
132+ }
133+
134+ /*
135+ Hand-"optimized" version of:
136+ inline fun <T, R> Flow<IncrementalCommand<T>>.mapIncremental(
137+ crossinline predicate: (T) -> R
138+ ): Flow<IncrementalCommand<R>> = flatMapIncremental {
139+ listOf(predicate(it))
140+ }
141+ */
142+ inline fun <T , R > Flow<IncrementalCommand<T>>.mapIncremental (
53143 crossinline predicate : (T ) -> R
54- ): Flow<IncrementalCommand<U >> = flow {
144+ ): Flow <IncrementalCommand <R >> = flow {
55145 var last: List <R >? = null
56146 collect { command ->
57147 var new: List <R >
58148 when {
59149 command is IncrementalCommand .Begin || last == null -> {
60150 new = command.after.map(predicate)
61- val out = command.after.filterIndexed { i, _ -> new[i] }
62- emit(IncrementalCommand.Begin(out))
151+ emit(IncrementalCommand .Begin (new))
63152 }
64153 command is IncrementalCommand .Insert -> {
65154 new = ArrayList (last!! )
66155 for (i in command.pos.. < command.pos+ command.count) {
67156 new.add(i, predicate(command.after[i]))
68157 }
69- val out = postProcess(command.after, new)
70- emit(IncrementalCommand.Insert(command.pos, command.count, out))
158+ emit(IncrementalCommand .Insert (command.pos, command.count, new))
159+ }
160+ command is IncrementalCommand .Move -> {
161+ new = ArrayList (last!! )
162+ for (i in command.pos.. < command.pos+ command.count) {
163+ new.removeAt(i)
164+ }
165+ for (i in command.outPos.. < command.outPos+ command.count) {
166+ new.add(i, last!! [i - command.outPos + command.pos])
167+ }
168+ emit(IncrementalCommand .Move (command.pos, command.count, command.outPos, new))
71169 }
72170 command is IncrementalCommand .Remove -> {
73171 new = ArrayList (last!! )
74172 for (i in command.pos.. < command.pos+ command.count) {
75173 new.removeAt(i)
76174 }
77- val out = postProcess(command.after, new)
78- emit(IncrementalCommand.Remove(command.pos, command.count, out))
175+ emit(IncrementalCommand .Remove (command.pos, command.count, new))
79176 }
80177 command is IncrementalCommand .Update -> {
81178 new = ArrayList (last!! )
82179 for (i in command.pos.. < command.pos+ command.count) {
83180 new[i] = predicate(command.after[i])
84181 }
85- val out = postProcess(command.after, new)
86- emit(IncrementalCommand.Update(command.pos, command.count, out))
182+ emit(IncrementalCommand .Update (command.pos, command.count, new))
87183 }
88184 else -> throw IllegalArgumentException (" code bug, IncrementalCommand case exhausted" )
89185 }
90186 last = new
91187 }
92- }*/
188+ }
189+
190+ // TODO some sort of "groupByIncremental" that takes a key, and produces incremental list of keys with
191+ // incremental list of their members? (if sublists aren't incremental we can't compose to big increments)
192+
193+ // TODO something that combines said groups back to one "Album" or "Artist"? or should we give up nice objects and
194+ // expose new flows from library surface to consumers for these subgroups? maybe we don't need to compose
195+ // increments if we do that?
0 commit comments