Skip to content

Commit fccf8ef

Browse files
committed
wip
1 parent ea3af80 commit fccf8ef

1 file changed

Lines changed: 126 additions & 23 deletions

File tree

app/src/main/java/org/akanework/gramophone/logic/utils/flows/IncrementalFlows.kt

Lines changed: 126 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,91 +2,194 @@ package org.akanework.gramophone.logic.utils.flows
22

33
import kotlinx.coroutines.flow.Flow
44
import kotlinx.coroutines.flow.flow
5+
import kotlin.math.abs
6+
import kotlin.math.min
57

68
sealed 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+
52128
inline 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

Comments
 (0)