-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathIteratorForCombination.kt
136 lines (121 loc) · 4.27 KB
/
IteratorForCombination.kt
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package questions
import _utils.UseCommentAsDocumentation
import utils.shouldBe
import java.lang.StringBuilder
/**
* Design the CombinationIterator class:
*
* * CombinationIterator(string characters, int combinationLength) Initializes the object with a string characters of sorted distinct lowercase English letters and a number combinationLength as arguments.
* * next() Returns the next combination of length combinationLength in lexicographical order.
* * hasNext() Returns true if and only if there exists a next combination.
*
* [Source](https://leetcode.com/problems/iterator-for-combination/)
*/
@UseCommentAsDocumentation
class CombinationIterator(characters: String, combinationLength: Int) {
private val cLength = combinationLength
private val chars = characters
// Hold indices of next return sequence
// for eg: for combinationLength=3 and characters=abcde i.e.
// trackIndex ranges from:
// 012, 013, 014, 023, 024, 034, 123, 124, 134, 234
// abc, abd, abe, acd, ace, ade, bcd, bce, bde, cde
// This pattern shows to generate next track index, you just have to make sure none of the digit crosses
// its right digit i.e trackIndex[i] + 1 < trackIndex[i + 1]
private var trackIndex: Array<Int>? = null
init {
findNextGenerator()
}
fun next(): String {
val sb = StringBuilder(cLength)
for (i in trackIndex!!) {
sb.append(chars[i])
}
return sb.toString().also {
findNextGenerator() // prepare next sequence
}
}
private fun findNextGenerator() {
if (trackIndex == null) { // for first time
trackIndex = Array(cLength) { it } // for combinationLength=3, it will be [0,1,2]
} else {
setNextValidTrackIndex(cLength - 1)
}
}
private fun setNextValidTrackIndex(index: Int) {
require(trackIndex != null)
trackIndex!!.let { track ->
if (index < 0) {
trackIndex = null // no more sequence possible
return
}
val rightDigitValue = track.getOrNull(index + 1) ?: chars.length
if (track[index] + 1 < rightDigitValue) { // to prevent collision like [0,3,3] make sure 1 can be safely added
track[index] = track[index] + 1
val current = track[index]
// next sequence after [0,3,4] is [1,2,3] so
var dist = 1
for (i in index + 1..track.lastIndex) {
track[i] = dist + current
dist++
}
} else {
setNextValidTrackIndex(index - 1)
}
}
}
fun hasNext(): Boolean {
return trackIndex != null // next sequence is already prepared so just check if not null
}
}
fun main() {
run {
val itr = CombinationIterator("abc", 2)
itr.next() shouldBe "ab"
itr.hasNext() shouldBe true
itr.next() shouldBe "ac"
itr.hasNext() shouldBe true
itr.next() shouldBe "bc"
itr.hasNext() shouldBe false
}
run {
val itr = CombinationIterator("abcd", 3)
itr.next() shouldBe "abc"
itr.hasNext() shouldBe true
itr.next() shouldBe "abd"
itr.hasNext() shouldBe true
itr.next() shouldBe "acd"
itr.hasNext() shouldBe true
itr.next() shouldBe "bcd"
itr.hasNext() shouldBe false
}
run {
CombinationIterator("chp", 1).apply {
hasNext() shouldBe true
next() shouldBe "c"
hasNext() shouldBe true
hasNext() shouldBe true
next() shouldBe "h"
next() shouldBe "p"
hasNext() shouldBe false
hasNext() shouldBe false
hasNext() shouldBe false
hasNext() shouldBe false
hasNext() shouldBe false
}
}
run {
CombinationIterator("ahijp", 2).apply {
hasNext() shouldBe true
next() shouldBe "ah"
next() shouldBe "ai"
hasNext() shouldBe true
next() shouldBe "aj"
hasNext() shouldBe true
next() shouldBe "ap"
hasNext() shouldBe true
next() shouldBe "hi"
next() shouldBe "hj"
}
}
}