From ac1d5da01b391b0f8ae37754bbbda59bfacec4c7 Mon Sep 17 00:00:00 2001 From: monoid Date: Sat, 28 Jun 2025 19:15:18 +0900 Subject: [PATCH] refactor: more kotlin style --- lib/src/main/kotlin/org/example/RegexItem.kt | 127 +++++++++++-------- 1 file changed, 71 insertions(+), 56 deletions(-) diff --git a/lib/src/main/kotlin/org/example/RegexItem.kt b/lib/src/main/kotlin/org/example/RegexItem.kt index ea80001..827ff86 100644 --- a/lib/src/main/kotlin/org/example/RegexItem.kt +++ b/lib/src/main/kotlin/org/example/RegexItem.kt @@ -30,7 +30,7 @@ class MatchResult(val available: AvailableState) { // 재귀 하향 분석기. interface RegexItem { override fun toString(): String - fun findMatch(item: String): AvailableState + fun findMatch(str: String): AvailableState } fun RegexItem.match(item: String): MatchResult { @@ -40,8 +40,8 @@ fun RegexItem.match(item: String): MatchResult { class AndThenItem(val left: RegexItem, val right: RegexItem) : RegexItem { override fun toString(): String = "${left}${right}" - override fun findMatch(item: String): AvailableState { - val leftMatch = left.findMatch(item) + override fun findMatch(str: String): AvailableState { + val leftMatch = left.findMatch(str) if (leftMatch.isEmpty) { return AvailableState() // If left match fails, return empty sequence } @@ -67,67 +67,83 @@ class AndThenItem(val left: RegexItem, val right: RegexItem) : RegexItem { class CharItem(val value: String) : RegexItem { override fun toString(): String = value - override fun findMatch(item: String): AvailableState { - return if (item.isNotEmpty() && item[0].toString() == value) { - // If the first character matches, return a successful match - AvailableState(sequenceOf(State(value, item.substring(1)))) - } else { - // If it doesn't match, return an empty sequence - AvailableState() + override fun findMatch(str: String): AvailableState { + return when { + // 첫번째 문자가 value와 일치하는지 확인 + str.isNotEmpty() && str[0].toString() == value -> { + AvailableState(sequenceOf(State(value, str.substring(1)))) + } + else -> AvailableState() } } } +fun matchMany( + str: String, + item: RegexItem, +): Sequence { + // 욕심쟁이 매칭을 위한 헬퍼 함수 + return item.findMatch(str).seq.flatMap { state -> + if (state.remaining.isEmpty()) { + sequenceOf(state) // If remaining is empty, return the matched state + } else { + // Otherwise, continue matching with the remaining string + matchMany(state.remaining, item).map { nextState -> + State(state.matched + nextState.matched, nextState.remaining) + } + sequenceOf(state) // Include the current state as well + } + } +} + +// fun matchMany(str: String, item: RegexItem): Sequence = sequence { +// val stack = mutableListOf(item.findMatch(str).seq) +// while (stack.isNotEmpty()) { +// val current = stack.removeAt(stack.lastIndex) +// for (state in current) { +// yield(state) // Yield the current state +// if (state.remaining.isNotEmpty()) { +// // If there is remaining string, continue matching +// stack.add(item.findMatch(state.remaining).seq.map { nextState -> +// State(state.matched + nextState.matched, nextState.remaining) +// }) +// } +// } +// } +// } + class PlusItem(val item: RegexItem) : RegexItem { override fun toString(): String = "${item}+" - override fun findMatch(item: String): AvailableState { - // 욕심쟁이 매칭해야 함. 그래서 가능한 한 많이 매칭해야 함. - val results = mutableListOf() - var remaining = item - - while (true) { - val matchResult = this.item.findMatch(remaining) - if (matchResult.isEmpty) { - break // No more matches possible - } - matchResult.forEach { state -> results.add(State(state.matched, state.remaining)) } - remaining = matchResult.first().remaining // Update remaining string - } - - return AvailableState(results.reversed().asSequence()) // Return all successful matches + override fun findMatch(str: String): AvailableState { + return AvailableState(matchMany(str, item)) } } class StarItem(val item: RegexItem) : RegexItem { override fun toString(): String = "${item}*" - override fun findMatch(item: String): AvailableState { - // 욕심쟁이 매칭해야 함. 그래서 가능한 한 많이 매칭해야 함. - val results = mutableListOf(State("", item)) // Start with an empty match - var remaining = item - while (true) { - val matchResult = this.item.findMatch(remaining) - if (matchResult.isEmpty) { - break // No more matches possible - } - matchResult.forEach { state -> results.add(State(state.matched, state.remaining)) } - remaining = matchResult.first().remaining // Update remaining string - if (remaining.isEmpty()) { - break // If remaining string is empty, stop matching - } + override fun findMatch(str: String): AvailableState { + // *는 0개 이상의 매칭을 의미하므로, 먼저 시도해보고 실패하면 빈 시퀀스를 반환 + val matchResult = this.item.findMatch(str) + if (matchResult.isEmpty) { + // If the item does not match, return an empty sequence + return AvailableState(sequenceOf(State("", str))) } - // 반드시 reverse해서 가장 긴 매칭부터 시작해야 함. - return AvailableState(results.reversed().asSequence()) // Return all successful matches + // If it matches, return the successful match and continue matching with the remaining string + return AvailableState( + matchResult.flatMap { state -> + sequenceOf(state) + matchMany(state.remaining, this.item) + } + ) } } class QuestionItem(val item: RegexItem) : RegexItem { override fun toString(): String = "${item}?" - override fun findMatch(item: String): AvailableState { + override fun findMatch(str: String): AvailableState { // ?는 0개 또는 1개 매칭을 의미하므로, 먼저 시도해보고 실패하면 빈 시퀀스를 반환 - val matchResult = this.item.findMatch(item) + val matchResult = this.item.findMatch(str) if (matchResult.isEmpty) { // If the item does not match, return an empty sequence - return AvailableState(sequenceOf(State("", item))) + return AvailableState(sequenceOf(State("", str))) } // If it matches, return the successful match return AvailableState(matchResult.map { State(it.matched, it.remaining) }) @@ -136,25 +152,24 @@ class QuestionItem(val item: RegexItem) : RegexItem { class DotItem : RegexItem { override fun toString(): String = "." - override fun findMatch(item: String): AvailableState { - // .은 임의의 한 문자와 매칭되므로, 첫 문자가 존재하면 매칭 성공 - return if (item.isNotEmpty()) { - AvailableState(sequenceOf(State(item[0].toString(), item.substring(1)))) - } else { - AvailableState() // 빈 문자열에 대해서는 매칭 실패 - } - } + override fun findMatch(str: String): AvailableState = + // .은 임의의 한 문자와 매칭되므로, 첫 문자가 존재하면 매칭 성공 + when { + str.isNotEmpty() -> + AvailableState(sequenceOf(State(str[0].toString(), str.substring(1)))) + else -> AvailableState() // 빈 문자열에 대해서는 매칭 실패 + } } class AlternationItem(val left: RegexItem, val right: RegexItem) : RegexItem { override fun toString(): String = "(${left}|${right})" - override fun findMatch(item: String): AvailableState { + override fun findMatch(str: String): AvailableState { // Alternation은 왼쪽 또는 오른쪽 항목 중 하나와 매칭되므로, 각각 시도해보고 성공하는 경우를 반환 - val leftMatch = left.findMatch(item) - val rightMatch = right.findMatch(item) + val leftMatch = left.findMatch(str) + val rightMatch = right.findMatch(str) return AvailableState( - (leftMatch + rightMatch).asSequence() // 두 매칭 결과를 합쳐서 반환 + (leftMatch + rightMatch) // 두 매칭 결과를 합쳐서 반환 ) } }