-
Notifications
You must be signed in to change notification settings - Fork 410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[step2] 문자열 계산기 #958
base: bperhaps
Are you sure you want to change the base?
[step2] 문자열 계산기 #958
Changes from all commits
2b2b066
8aa3b97
73e2d5c
f2ce01f
8f3e43e
1e06675
faa4ddd
eff2fec
ea45268
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package step1 | ||
|
||
data class Person( | ||
val name: String, | ||
val age: Int? = null, | ||
var nickname: String? = null | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package step2.calculator | ||
|
||
import step2.calculator.vo.CalculatorInput | ||
import step2.calculator.vo.Number | ||
import step2.calculator.vo.from | ||
|
||
class Calculator( | ||
private val inputs: Inputs | ||
) { | ||
fun calculate(): Number { | ||
var result = inputs.nextNumber; | ||
|
||
while(inputs.hasNextNumber && inputs.hasNextOperation) { | ||
val operation = inputs.nextOperation | ||
val operand = inputs.nextNumber | ||
|
||
result = operation.calculate(result, operand) | ||
} | ||
|
||
return result | ||
} | ||
|
||
companion object | ||
} | ||
|
||
fun Calculator.Companion.from(input: String): Calculator { | ||
return Calculator(Inputs.from(extractInputs(input))) | ||
} | ||
|
||
private fun extractInputs(input: String): List<CalculatorInput> { | ||
return input.split(" ").asSequence() | ||
.filter { it.isNotBlank() } | ||
.map { CalculatorInput.from(it) } | ||
.toList(); | ||
} | ||
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,104 @@ | ||||||||||||||
package step2.calculator | ||||||||||||||
|
||||||||||||||
import step2.calculator.vo.CalculatorInput | ||||||||||||||
import step2.calculator.vo.Number | ||||||||||||||
import step2.calculator.vo.Operation | ||||||||||||||
|
||||||||||||||
class Inputs( | ||||||||||||||
private val numbers: List<Number>, | ||||||||||||||
private val operations: List<Operation>, | ||||||||||||||
) { | ||||||||||||||
private var numbersPosition: Int = 0 | ||||||||||||||
private var operationsPosition: Int = 0 | ||||||||||||||
|
||||||||||||||
val hasNextNumber: Boolean | ||||||||||||||
get() { | ||||||||||||||
return numbers.size > numbersPosition | ||||||||||||||
} | ||||||||||||||
Comment on lines
+14
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
이렇게 하면 조금 더 깔끔해질 것 같아요! |
||||||||||||||
|
||||||||||||||
val nextNumber: Number | ||||||||||||||
get() { | ||||||||||||||
val result = numbers[numbersPosition] | ||||||||||||||
numbersPosition += 1 | ||||||||||||||
|
||||||||||||||
return result | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
val hasNextOperation: Boolean | ||||||||||||||
get() { | ||||||||||||||
return operations.size > operationsPosition | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
val nextOperation: Operation | ||||||||||||||
get() { | ||||||||||||||
val result = operations[operationsPosition] | ||||||||||||||
operationsPosition += 1 | ||||||||||||||
|
||||||||||||||
return result | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
companion object | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
@Suppress("UNCHECKED_CAST") | ||||||||||||||
fun Inputs.Companion.from(values: List<CalculatorInput>): Inputs { | ||||||||||||||
values.validateFormat() | ||||||||||||||
|
||||||||||||||
val groupBy = values.groupBy { it.getType() } | ||||||||||||||
|
||||||||||||||
return Inputs( | ||||||||||||||
groupBy[Number::class.java] as List<Number>, | ||||||||||||||
groupBy[Operation::class.java] as List<Operation> | ||||||||||||||
) | ||||||||||||||
} | ||||||||||||||
Comment on lines
+44
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분도 이 코멘트 처럼 companion object 부분을 수정해주세요 :) |
||||||||||||||
|
||||||||||||||
private fun List<CalculatorInput>.validateFormat() { | ||||||||||||||
validateNumberPosition() | ||||||||||||||
validateOperationOption() | ||||||||||||||
validateOperandCount() | ||||||||||||||
validateInputLength() | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
private fun List<CalculatorInput>.validateNumberPosition() { | ||||||||||||||
for (i: Int in 0 until this.size step (2)) { | ||||||||||||||
if (this[i] !is Number) { | ||||||||||||||
throw IllegalArgumentException("인풋 포멧 오류") | ||||||||||||||
} | ||||||||||||||
Comment on lines
+64
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. kotlin 의 https://seosh817.tistory.com/155 또한 어떤 에러인지 알 수 있도록 에러 메시지를 조금 더 상세하게 작성해주세요! |
||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
private fun List<CalculatorInput>.validateOperationOption() { | ||||||||||||||
for (i: Int in 1 until this.size step (2)) { | ||||||||||||||
if (this[i] !is Operation) { | ||||||||||||||
throw IllegalArgumentException("인풋 포멧 오류") | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
private fun List<CalculatorInput>.validateOperandCount() { | ||||||||||||||
val groupBy = this.groupingBy { | ||||||||||||||
it.getType() | ||||||||||||||
}.eachCount() | ||||||||||||||
|
||||||||||||||
if (groupBy[Number::class.java] != groupBy[Operation::class.java]?.plus(1)) { | ||||||||||||||
throw IllegalArgumentException("피연산자 오류") | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
private fun List<CalculatorInput>.validateInputLength() { | ||||||||||||||
if (this.size < 3) { | ||||||||||||||
throw IllegalArgumentException("인풋 포멧 오류") | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
private fun CalculatorInput.getType(): Class<out CalculatorInput> { | ||||||||||||||
if (this is Number) { | ||||||||||||||
return Number::class.java | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
if (this is Operation) { | ||||||||||||||
return Operation::class.java | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
throw IllegalArgumentException("잘못된 타입") | ||||||||||||||
} | ||||||||||||||
Comment on lines
+94
to
+104
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package step2.calculator.vo | ||
|
||
interface CalculatorInput { | ||
companion object | ||
} | ||
|
||
fun CalculatorInput.Companion.from(input: String): CalculatorInput { | ||
return when { | ||
input.isTypeOf(Number::class.java) -> Number(input.toInt()) | ||
input.isTypeOf(Operation::class.java) -> Operation.from(input) | ||
else -> throw IllegalArgumentException("알 수 없는 입력값.") | ||
} | ||
} | ||
Comment on lines
+7
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분도 변경할 수 있을 것 같습니다 :) |
||
|
||
|
||
fun String.isTypeOf(type: Class<out CalculatorInput>): Boolean { | ||
return when { | ||
type.isAssignableFrom(Number::class.java) -> { | ||
runCatching { | ||
Number(this.toInt()) | ||
}.isSuccess | ||
} | ||
|
||
type.isAssignableFrom(Operation::class.java) -> { | ||
kotlin.runCatching { | ||
Operation.from(this) | ||
}.isSuccess | ||
} | ||
|
||
else -> { | ||
false | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package step2.calculator.vo | ||
|
||
data class Number( | ||
val value: Int | ||
) : CalculatorInput |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package step2.calculator.vo | ||
|
||
import java.util.function.BiFunction | ||
|
||
enum class Operation( | ||
private val value: String, | ||
private val operation: BiFunction<Number, Number, Number> | ||
) : CalculatorInput { | ||
PLUS("+", { op1, op2 -> Number(op1.value + op2.value) }), | ||
MINUS("-", { op1, op2 -> Number(op1.value - op2.value) }), | ||
DIVIDE("/", { op1, op2 -> Number(op1.value / op2.value) }), | ||
MULTI("*", { op1, op2 -> Number(op1.value * op2.value) }); | ||
|
||
fun calculate(op1: Number, op2: Number): Number { | ||
return this.operation.apply(op1, op2) | ||
} | ||
|
||
companion object { | ||
fun from(input: String): Operation { | ||
return values().asSequence() | ||
.filter { it.value == input } | ||
.first() | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package step1 | ||
|
||
import io.kotest.matchers.shouldBe | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.Test | ||
import org.junit.jupiter.api.assertAll | ||
|
||
internal class PersonTest { | ||
|
||
@Test | ||
fun constructor() { | ||
val person = Person( | ||
name = "손민성", | ||
age = 30, | ||
nickname = "손너잘" | ||
) | ||
|
||
person.name shouldBe ("손민성") | ||
|
||
person.nickname = "너잘손" | ||
person.nickname shouldBe ("너잘손") | ||
} | ||
|
||
@Test | ||
fun `named arguments`() { | ||
val people = listOf( | ||
Person("손민성", 30, "손너잘"), | ||
Person("손민성", 30, nickname = "손너잘"), | ||
Person(name = "손민성", age = 30, nickname = "손너잘") | ||
) | ||
|
||
assertThat(people).allSatisfy { | ||
it.name shouldBe "손민성" | ||
it.age shouldBe 30 | ||
it.nickname shouldBe "손너잘" | ||
} | ||
} | ||
|
||
@Test | ||
fun `default arguments`() { | ||
val person = Person("손민성") | ||
|
||
assertAll( | ||
{ person.name shouldBe "손민성" }, | ||
{ person.age shouldBe null }, | ||
{ person.nickname shouldBe null } | ||
) | ||
} | ||
|
||
@Test | ||
fun `data classes`() { | ||
val person1 = Person("손민성", 30, "손너잘") | ||
val person2 = Person("손민성", 30, "손너잘") | ||
assertThat(person1).isEqualTo(person2) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package step2.calculator | ||
|
||
import io.kotest.assertions.assertSoftly | ||
import io.kotest.assertions.throwables.shouldNotThrowAny | ||
import io.kotest.assertions.throwables.shouldThrowAny | ||
import io.kotest.core.spec.style.StringSpec | ||
import io.kotest.matchers.shouldBe | ||
import step2.calculator.vo.Number | ||
|
||
class CalculatorTest : StringSpec({ | ||
"올바른 인풋에 대해서 오류 없이 계산기 생성이 가능하다." { | ||
shouldNotThrowAny { Calculator.from("3 + 2 * 4") } | ||
} | ||
|
||
"올바르지 않은 인풋에 대해서 오류 없이 계산기 생성이 불가능하다." { | ||
shouldThrowAny { Calculator.from("3+ 2 * 4") } | ||
} | ||
|
||
"계산결과가 출력된다." { | ||
assertSoftly { | ||
Calculator.from("3 + 2 * 4").calculate() shouldBe Number(20) | ||
Calculator.from("3 + 2 * 4 + 1").calculate() shouldBe Number(21) | ||
Calculator.from("3 + 2 * 4 / 5").calculate() shouldBe Number(4) | ||
Calculator.from("3 + 2 * 4 * 5").calculate() shouldBe Number(100) | ||
} | ||
|
||
} | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package step2.calculator | ||
|
||
import io.kotest.assertions.throwables.shouldNotThrowAny | ||
import io.kotest.assertions.throwables.shouldThrowAny | ||
import io.kotest.core.spec.style.StringSpec | ||
import step2.calculator.vo.Number | ||
import step2.calculator.vo.Operation | ||
|
||
class InputsTest : StringSpec({ | ||
"정확한 input 포멧으로 Inputs를 생성한다." { | ||
shouldNotThrowAny { | ||
Inputs.from( | ||
listOf( | ||
Number(1), | ||
Operation.from("+"), | ||
Number(2), | ||
Operation.from("-"), | ||
Number(3) | ||
) | ||
) | ||
} | ||
} | ||
|
||
"input 포멧이 부정확 하면 exception. (피연산자)" { | ||
shouldThrowAny { | ||
Inputs.from( | ||
listOf( | ||
Number(1), | ||
Operation.from("+"), | ||
Number(2), | ||
Operation.from("-") | ||
) | ||
) | ||
} | ||
} | ||
|
||
"input 포멧이 부정확 하면 exception.(길이)" { | ||
shouldThrowAny { | ||
Inputs.from( | ||
listOf( | ||
Number(1), | ||
Operation.from("+") | ||
) | ||
) | ||
} | ||
} | ||
|
||
"input 포멧이 부정확 하면 exception.(순서)" { | ||
shouldThrowAny { | ||
Inputs.from( | ||
listOf( | ||
Number(1), | ||
Operation.from("+"), | ||
Operation.from("-") | ||
) | ||
) | ||
} | ||
} | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://pearlluck.tistory.com/722
companion object는 이렇게 사용하시면 될 것 같습니다 :)