[kotlin] ๋๋ ค๋๋ ค ๋๋ฆผํ~ ๋ฃฐ๋ ๊ฒ์ ๊ตฌํํ๊ธฐ
๐ ์๋ก
๋ฃฐ๋ ๊ฒ์์ ํ ๋ฒ์ฏค ํด๋ณธ ์ ์ด ์์ ๊ฒ์ด๋ค. ๋น๊ธ๋น๊ธ ๋์๊ฐ๋ ์ํ์๋ ๊ตฌ์ญ๋ณ๋ก ์ํ์ด ๊ฑธ๋ ค์๊ณ ๋์๊ฐ๋ ์ํ์ด ์๋๋ฅผ ์์ ๋์ฏค ๋ฉ์ถ๋ ๊ณณ์ ๋น์ฒจ ๋ง๋๊ฐ ๊ฐ๋ฆฌํค๋ ์ํ์ ์ป๊ฒ ๋๋ค.
์ด๋ฐ ๋ฃฐ๋ ๊ฒ์์ ์ฝ๋๋ก ๊ตฌํํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผํ ๊น? ํ์๋ ๋ฐฑ์๋ ๊ฐ๋ฐ์์ด๊ธฐ์ UI๋ฅผ ๊ทธ๋ฆฌ๋ ๊ฒ๋ณด๋จ ์ด๋ค ์๋ฆฌ์ ์ํด ์ํ์ด ๋น์ฒจ๋๋์ง ์ด์ ์ ๋๊ฒ ๋๋ค. ๋ง์ฝ ์ํ์ด ์ฌ์ฏ ๊ฐ์ ๊ตฌ์ญ์ผ๋ก ๋๋์ด ์๋ค๋ฉด ๊ฐ๊ฐ 1/6์ ๋น์ฒจํ๋ฅ ์ ๊ฐ์ง๋ค๊ณ ์๊ฐํ ์ ์๋ค. ํ์ง๋ง ์ธ์์ผ์ด ์๋ ์๊ฐ๋๋ก ๋๋๊ฐ์? ์ข์ ์ํ์ ํญ์ ๋น๋๊ฐ๊ณ ์๋์ ์ผ๋ก ๊ฐ์ด์น๊ฐ ๋จ์ด์ง๋ ์ํ๋ง ์์ฃผ ๊ฑธ๋ฆฌ๋ ๊ฒ ์ฐจ๊ฐ์ด ์๋ณธ์ฃผ์์ ํ์ค์ด๋ค like ์ค๋ฃจ์นด์ค. ํ๋ก๊ทธ๋จ์ผ๋ก ๊ตฌํ๋ ๋ฃฐ๋ ๊ฒ์์ ๊ฐ ๊ตฌ์ญ์ ๋น์ฒจํ๋ฅ ์ ์ธํ ํ ์ ์๊ธฐ์ ๊ฐ์ด์น๊ฐ ๋์ ์ํ๋ณด๋จ ๊ทธ๋ ์ง ์์ ์ํ์ด ๋ ๋น๋ฒํ ๋น์ฒจ๋ ์ ์๋ค.
์ด๋ฒ ํฌ์คํ ์์๋ ๊ฐ๊ฐ์ ๊ตฌ์ญ(์ํ)์ด ์๋ก ๋ค๋ฅธ ๋น์ฒจํ๋ฅ ์ ๊ฐ์ง๋ ๋ฃฐ๋ ์ ์ฝ๋๋ก ๊ตฌํํ๋๋ฐ ์์๋ฅผ ๋๋ค. ์ถ๊ฐ์ ์ผ๋ก ๊ฐ๊ฐ์ ๊ตฌ์ญ์ ์๋ก ๋ค๋ฅธ ์๋์ ์ง๋๋ค. ์์ผ๋ก๋ ์ด ์๋์ `์ฌ๊ณ `๋ผ ์นญํ๊ฒ ๋ค. ์๋ฅผ๋ค์ด ๋ฃฐ๋ ์ด ์ฌ์ฏ ๊ฐ์ ๊ตฌ์ญ์ผ๋ก ๊ตฌ์ฑ๋๋ค๋ฉด ์ฌ์ฏ ๊ฐ์ ๊ตฌ์ญ์ ๊ฐ๊ธฐ ๋ค๋ฅธ ์ฌ๊ณ ์ ๋น์ฒจํ๋ฅ ์ ๋ณด์ ํ ์ ์ด๋ค. ๋๋ฌธ์ ๋น์ฒจํ๋ฅ ์ด ๋์ ๊ตฌ์ญ์ ์์ดํ ์ด ๋จผ์ ์์ง๋๊ณ , ๋น์ฒจํ๋ฅ ์ด ๋ฎ์ ๊ตฌ์ญ์ ์์ดํ ์ ๋น๊ต์ ๋ฆ๊ฒ ์ฌ๊ณ ๊ฐ ์์ง ๋ ๊ฒ์ด๋ค.
๐ ์๊ตฌ์ฌํญ ๋ฐ ๊ณ ๋ ค์ฌํญ
์์ดํ (score) | ํ๋ฅ | ์ฌ๊ณ |
10 | 0.5 | 40 |
50 | 0.3 | 30 |
100 | 0.1 | 16 |
500 | 0.05 | 8 |
800 | 0.03 | 4 |
1000 | 0.02 | 2 |
๋ฃฐ๋ ๊ฒ์์ ์๊ตฌ์ฌํญ์ ์๋์ ๊ฐ๋ค
- ๋น์ฒจ๋ ์ ์๋ ์์ดํ ์ ์๋์ ์ด 100๊ฐ๋ก ํ์ ํ๋ค.
- ๊ฐ ์์ดํ ์ ๋น์ฒจ ํ๋ฅ ์ ์์ ํ(table)์ ๊ฐ๋ค.
- ์ฆ ๋ฃฐ๋ ์ ์คํ ํ์ ์ญ์ 100๋ฒ์ผ๋ก ์ ํ๋๋ฉฐ 100๋ฒ์ ํ๋ ์ด ์ดํ์๋ ๋ฃฐ๋ ์ ๋จ์ ์์ดํ ์ฌ๊ณ ๊ฐ ์์ด์ผํ๋ค.
- ๊ฐ๊ฐ์ ์ฌ๊ณ ๋ ๋์์ฑ ์ด์๋ก๋ถํฐ ์์ ๋ก์์ผ ํ๋ค.
- ์ ์ฒด ์์ดํ ์ ๋น์ฒจ ํ๋ฅ ์ ํฉ์ฐํ๋ฉด 1์ด ๋๋ค.
- ํน์ ์์ดํ ์ ์๋์ด ๋ชจ๋ ์์ง๋๋ฉด ํด๋น ์์ดํ ์ ๋น์ฒจํ๋ฅ ์ ์ฌ๊ณ ๊ฐ ์์ง๋์ง ์์ ๋๋จธ์ง ์์ดํ ์ ๋ถ๋ฐฐํ๋ค.
ex) 10์ ์์ดํ ์ฌ๊ณ ๋ชจ๋ ์๋ -> 0.5 / 5(๋จ์ ์์ดํ ๊ฐ์) = 0.1 โก๏ธ ๋จ์ ์์ดํ ์ ํ๋ฅ ์ ๊ฐ๊ฐ ํฉ์ฐ (์๋ ๋ํ ์ฐธ์กฐ)
์ ์(score) | ํ๋ฅ | ์ฌ๊ณ |
50 | 0.3 + 0.1 = 0.4 | 30 |
100 | 0.1 + 0.1 = 0.2 | 16 |
500 | 0.05 + 0.1 = 0.15 | 8 |
800 | 0.03 + 0.1 = 0.13 | 4 |
1000 | 0.02 + 0.1 = 0.12 | 2 |
๐ ๊ตฌํํ๊ธฐ
๊ธฐ์ ์คํ
- Kotlin 1.8
- Junit 5.10
- ์ด๋ฒ ํฌ์คํ ์์ ๋ณ๋์ ์ธ๋ถ DB ์ฐ๊ฒฐ์์ด ๋ฉ๋ชจ๋ฆฌ ์์์๋ง ๋ฃฐ๋ ํด๋์ค๋ฅผ ๊ด๋ฆฌํ๋ค.
์ฐ์ ๋ฃฐ๋ ๋๋ฆผํ์ ๊ตฌ์ฑํ๋ ๊ฐ๊ฐ์ ๊ตฌ์ญ์ ์ฝ๋๋ก ๊ตฌํํด์ผ ํ๋ค.
์ ์๊ตฌ์ฌํญ์ ๋ฐ๋ฅด๋ฉด ๋ฃฐ๋ ์ ๊ฐ ๊ตฌ์ญ์ 1) ์ ์(score)
2) ๋น์ฒจ ํ๋ฅ (probability)
3) ์ฌ๊ณ (stocks)
์ด 3๊ฐ์ง ์์๋ฅผ ๊ฐ๋๋ค. ์ด๋ฅผ ์ฝํ๋ฆฐ ์ฝ๋๋ก ํํํ๋ฉด ์๋์ ๊ฐ๋ค.
๊ฐ ๊ตฌ์ญ์ ์ฝ๋๋ก ๋ํ๋ด๊ธฐ - Sector.kt
class Sector(val score: Int, val probability: BigDecimal, var stocks: AtomicInteger) {
fun decreaseStock() {
if (stocks.get() > 0) {
stocks.set(stocks.get() - 1)
}
}
}
ํด๋น ํด๋์ค๋ 3๊ฐ์ ํ๋์ decreaseStock() ๋ฉ์๋๋ฅผ ๊ฐ๋๋ค. ๋ฃฐ๋ ์ ํ๋ฒ ๋๋ฆด ๋๋ง๋ค ์ต์ ํ๋์ ์์ดํ ์ ๋น์ฒจ๋๊ธฐ์ ์ฌ๊ณ ์ ๊ฐฏ์๋ ํ๋์ฉ ์ฐจ๊ฐ๋์ผ ํ๋ค. stocks ํ๋๋ AtomicInteger ๋ก ์ ์ํ๋ค. ์ฒ์์ Int ํ์ ์ผ๋ก ์ ์ธํ์ง๋ง ๋์์ฑ์ ์ง์ผ์ผ ํ๋ ์๊ตฌ์ฌํญ์ด ์๋ค. ๋ฉํฐ์ค๋ ๋ ํ๊ฒฝ์์ stocks ๋ฅผ ์ฐจ๊ฐํ๋ค๋ฉด stocks ํ๋๋ ๊ฒฝ์์ํ๊ฐ ๋์ด ๋ฐ์ดํฐ ์ ํฉ์ฑ์ด ๋ง์ง ์๋ ์ด์๊ฐ ๋ฐ์ํ ์ ์๋ค. ์ด๋ฅผ ๋ฐฉ์งํ๊ณ ์ stocks ์ ํ์ ์ AtomicInteger ๋ก ๋ณ๊ฒฝํ๋ค.
๊ฐ๊ฐ์ Sector ๋ฅผ Collection ์ผ๋ก ๋ฌถ์ด ํ๋์ ๋ฃฐ๋ ์ ๋ง๋ค ์ ์๋ค. ํ์๋ List collection ์ ์ฌ์ฉํ์ฌ ์ต์ด์ ๋ฃฐ๋ ๊ฒ์์ ์คํํ๋ ์ํ๋ฅผ ๋ง๋ค์๋ค. (ํ๊ธ๋ช ๋ฉ์๋๋ ์ดํด๋ฅผ ๋๊ธฐ ์ํด.. ใ ใ )
internal class RouletteUnitTest {
private fun ๋ฃฐ๋ _์ด๊ธฐํ(): List<Sector> {
return listOf(
Sector(10, BigDecimal.valueOf(0.5), AtomicInteger(40)),
Sector(50, BigDecimal.valueOf(0.3), AtomicInteger(30)),
Sector(100, BigDecimal.valueOf(0.1), AtomicInteger(16)),
Sector(500, BigDecimal.valueOf(0.05), AtomicInteger(8)),
Sector(800, BigDecimal.valueOf(0.03), AtomicInteger(4)),
Sector(1000, BigDecimal.valueOf(0.02), AtomicInteger(2))
)
}
}
๐ ํ๋ ์ด ๊ณผ์ ๊ตฌํํ๊ธฐ
์๊ตฌ์ฌํญ์ ๋ค์ ์ดํด๋ณด์. ๊น๋ค๋ก์ด ๋ถ๋ถ์ด ํ๋ ์๋ค.
ํน์ ์์ดํ ์ ์ฌ๊ณ ๊ฐ ๋ชจ๋ ์์ง๋๋ฉด ํด๋น ์์ดํ ์ ๋น์ฒจ ํ๋ฅ ์ ๋ค๋ฅธ ๊ณณ์ผ๋ก ์ฌ๋ถ๋ฐฐ ํด์ผํ๋ค. ์ ์ฒด ๋ฃฐ๋ ์ ๋น์ฒจ ํ๋ฅ ์ 1๋ก ์ ์งํ๊ธฐ ์ํด์๋ค.
์ ์ผ์ด์ค๋ณด๋ค ๋ ๊ฐ๋จํ ์๋ฅผ ๋ค์ด๋ณด์. ์ด 3๊ฐ์ sector ๊ฐ ์๊ณ ๊ฐ๊ฐ์ ๋น์ฒจ ํ๋ฅ ์ 20%, 30%, 50% ๋ก ๊ฐ์ ํ๋ค. ์ด๋ฅผ ๊ฐ๋จํ ์ ์๋ก ํํํ๋ฉด ์๋์ ์ด๋ฏธ์ง์ ๊ฐ๋ค.
0 ์์ 9 ๊น์ง(์ด 10๊ฐ์ ์ ์) ๋๋คํ ์ ์๋ฅผ ํ๋ ๋ฝ์ ๋, ๋น์ฒจ๋ ์ ์๊ฐ ์ํ ๋ฒ์์ ์์ดํ ์ ๋ฐ๊ฒ ๋๋ ๊ฒ์ด๋ค. ์ด๋ฏธ์ง ์ ๋น์ฒจํ๋ฅ ์ด ๋์ ์์ดํ ์์๋๋ก ์์ญ์ด ๋์ ๊ฒ์ ํ์ธํ ์ ์๋ค. ์ด๋ฌํ ์์ญ์ด range ๋ก ๊ฐ์ฃผ๋๋ฉฐ ๋๋ค์ผ๋ก ๋ฝ์ ์๊ฐ ์ด๋ค range ์ ์ํ๋์ง์ ๋ฐ๋ผ ๋น์ฒจ ์์ดํ ์ด ๋ฌ๋ผ์ง๋ค.
์ด๋ฌํ ์๋ฆฌ๋ฅผ ๋ฃฐ๋ ๊ฒ์์ ์ ์ฉํด๋ณด์. ๋ฃฐ๋ ์ ์ด 6๊ฐ์ ์์ดํ ์ด ์์ผ๋ฏ๋ก ์ด 6๊ฐ์ ๋ฒ์(range)์ด ์๊ธฐ๋ ์ ์ด๋ค. ๋ฃฐ๋ ์ ๋์ ํ๋ฅ ๊ตฌ๊ฐ์ ๋ํ๋ด๋ฉด ๋ค์๊ณผ ๊ฐ์ ํํ์ผ ๊ฒ์ด๋ค.
๐ Item 10: 0 < x <= 0.5
๐ Item 50: 0.5 < x <= 0.8
๐ Item 100: 0.8 < x <= 0.9
๐ Item 500: 0.9 < x <= 0.95
๐ Item 800: 0.95 < x <= 0.98
๐ Item 1000: 0.98 < x <= 1
0 ์์ 1 ์ฌ์ด์ ๋๋คํ ์๋ฅผ ๊ตฌํ๊ณ , ํด๋น ์ซ์(ex, 0.32)๊ฐ ์ํ๋ ๋ฒ์์ ๋ฐ๋ผ ์์ดํ ์ด ๋น์ฒจ๋๋ ๊ฒ์ด๋ค. ์ด ๊ฒฝ์ฐ์๋ 0 < x <= 0.5 ์ ์ํ๋ฏ๋ก item 10 ์ ์ป๊ฒ ๋๋ค. 0 ์์ 1 ์ฌ์ด์ ๋๋คํ ์ซ์๋ Random ๋ฉ์๋๋ก ๊ฐ๋จํ ๊ตฌํ ์ ์๋ค.
๋๋ค ๊ฐ์ด ์ด๋ ๋ฒ์์ ์ํ๋์ง์ ๋ฐ๋ผ ๋น์ฒจ ์์ดํ ์ด ๋ฌ๋ผ์ง๋ค.
์ ์ด๋ฏธ์ง์ฒ๋ผ ๊ฐ ์์ดํ ๋ณ ๋์ ๋น์ฒจํ๋ฅ ์ ์ ์ฅํ๊ธฐ ์ํด 1) ์์ดํ ์ ๋น์ฒจํ๋ฅ ์ ์ฐจ๋ก๋ก ๋์ ํ๊ณ 2) ๋์ ๋ ๋น์ฒจํ๋ฅ ์ key๋ก ํ์ฌ ๊ฐ ์์ดํ ์ ํ ๋นํด์ผ ํ๋ค. ํ์๋ TreeMap ์๋ฃ๊ตฌ์กฐ๋ฅผ ์ด์ฉํด ์์ดํ ์ ๋น์ฒจํ๋ฅ ์ ์ฐจ๋ก๋ก ๋์ ํ๋ค. (TreeMap ์ ์ ํํ ์ด์ ๋ ์๋์์ ๋ค๋ฃจ๊ฒ ์ต๋๋ค.)
class RouletteGame(val sectorList: List<Sector>) {
private fun getRouletteProbability(): TreeMap<BigDecimal, Sector> {
var cumulativeProbability = BigDecimal.ZERO // (a)
val probabilityMap = TreeMap<BigDecimal, Sector>() // (b)
this.sectorList
.forEach { sector ->
cumulativeProbability = cumulativeProbability.add(sector.probability) // (c)
probabilityMap[cumulativeProbability] = sector // (d)
}
return probabilityMap
}
}
(a) ์ต์ด์ ๋์ ํ๋ฅ ๊ฐ์ 0 ์ด์ด์ผ ํ๋ค. ๊ฐ ์์ดํ ์ ๋น์ฒจ ํ๋ฅ ์ ์ด ๊ฐ์ ์ฐจ๋ก๋ก ๋์ ํ๊ฒ ๋๋ค.
(b) ๊ฐ ๋์ ํ๋ฅ ์ ์ ์ฅํ ์๋ฃ๊ตฌ์กฐ๋ฅผ TreeMap ํ์ ์ผ๋ก ์ ์ธํ์ฌ key ๋ ๋์ ํ๋ฅ ์, value ๋ Sector ์ธ์คํด์ค๋ฅผ ์ ์ฅํ๋ค.
(c) sectorList ๋ฅผ ์ํํ๋ฉด์ ๊ฐ Sector ์ ๋น์ฒจํ๋ฅ ์ cumlativeProbability ์ ์ฐจ๋ก๋ก ๋์ ํ๋ค.
(d) ๋์ ๋ ๋น์ฒจํ๋ฅ ์ probabilityMap ์ key ๋ก ์ ์ฅํ๋ฉฐ value ์ Sector ๊ฐ์ฒด๋ฅผ ์ ์ฅํ๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก ํด๋น ๋ฉ์๋๋ probabilityMap ๋ฅผ ๋ฐํํ์ฌ ์์ดํ ์ ๋น์ฒจํ๋ฅ ์ ์ฐจ๋ก๋ก ๋์ ํ๊ณ ์ด๋ฅผ key๋ก ๊ฐ์ง๋ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
๐ TreeMap ์ฌ์ฉ ์ด์ ์ ๊ตฌํ
์ ์ฅํ๋ Key์ ํน๋ณํ ์์๊ฐ ์๋ HashMap๊ณผ ๋ฌ๋ฆฌ TreeMap์ ์ด๋ค ๊ท์น์ ๋ฐ๋ผ ํค๋ฅผ ์ ๋ ฌํ์ฌ ์ ์ฅํ ์ ์๋ค. TreeMap ์ ์ด์ง ํ์ ํธ๋ฆฌ ํํ๋ฅผ ํ์ฉํ๊ธฐ ๋๋ฌธ์ ์ ๋ ฌ๋ key ์์๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ์ ์๋ค. TreeMap ์ ํค ๊ฐ์ ๋ฐ๋ผ ์ค๋ฆ์ฐจ์์ผ๋ก ์๋ ์ ๋ ฌ๋๊ธฐ ๋๋ฌธ์ (์๋ฅผ ๋ค์ด) 0.1, 0.36, 0.05, 0.52 ๋ฅผ key๋ก ์ ์ฅํ๋ฉด 0.05, 0.1, 0.36, 0.52 ์์๋ก ์๋ ์ ๋ ฌ ์ ์ฅ๋๋ค. TreeMap ์ ์ด๋ฌํ ํน์ง์ ํ์ฉํ์ฌ ๊ฐ Sector ์ ๋น์ฒจํ๋ฅ ์ ์ ์ฅํ๋ฉด ์ ๋ ฌ๋ ์ํ๋ฅผ ์ ์งํ ์ ์๋ค.
๋ํ TreeMap ์ ๋ด๋ถ ์ฝ๋๋ฅผ ์ดํด๋ณด๋ฉด NavigableMap ์ธํฐํ์ด์ค๋ฅผ ์์ํ ๊ตฌํ์ฒด์์ ์ ์ ์๋๋ฐ, NavigableMap ์ higherEntry()
๋ฉ์๋๋ฅผ ์ด์ฉํ์ฌ ์๊ตฌ์ฌํญ์ ๊ตฌํํ ์ ์๋ค.
higherEntry() : ์ธ์๋ก ๋ฐ์ key ๋ณด๋ค ํฐ key ์ค์์ ๊ฐ์ฅ ์์ key ๋ฅผ ๋ฐํํ๋ค.
higherEntry()๋ NavigableMap ์ธํฐํ์ด์ค์ ์ ์ธ๋ ์ถ์ ๋ฉ์๋๋ก TreeMap์์ ์ค๋ฒ๋ผ์ด๋ฉ ๋ผ ์๋ค.
higherEntry() ๋ฉ์๋๋ ์ฃผ์ด์ง key ๋ณด๋ค ํฐ ๋ค์(next) key ์ value ๋ฅผ ๋ฐํํ๋ค. ์ด ๋ฉ์๋๋ ํน์ key ์ ๋ค์ ํญ๋ชฉ์ ์ฐพ๋ ๋ฐ ์ฌ์ฉ๋๋ฉฐ ๋ง์ฝ ์ฃผ์ด์ง key ๋ณด๋ค ํฐ ๋ค์ key ๋ฅผ ๊ฐ์ง ํญ๋ชฉ์ด ์์ผ๋ฉด null์ ๋ฐํํ๋ค. ์๋ฅผ ๋ค์ด TreeMap ์ ์ ์ฅ๋ key ์ค ๊ฐ์ฅ ํฐ ๊ฐ์ด 1์ผ ๋ higherEntry(2) ๋ฅผ ํธ์ถํ๋ฉด 2 ๋ณด๋ค ํฐ key ๋ฅผ ๊ฐ์ง ํญ๋ชฉ์ด ์๊ธฐ ๋๋ฌธ์ null ์ ๋ฐํํ๋ค.
class RouletteGame(val sectorList: List<Sector>) {
private fun getResult(sectorMap: TreeMap<BigDecimal, Sector>): Sector {
val random = Random()
val randomValue = BigDecimal.valueOf(random.nextDouble())
return sectorMap.higherEntry(randomValue).value
}
private fun getRouletteProbability(): TreeMap<BigDecimal, Sector> {
var cumulativeProbability = BigDecimal.ZERO
val probabilityMap = TreeMap<BigDecimal, Sector>()
this.sectorList
.forEach { sector ->
cumulativeProbability = cumulativeProbability.add(sector.probability)
probabilityMap[cumulativeProbability] = sector
}
return probabilityMap
}
}
๋น์ฒจ ๊ฒฐ๊ณผ๋ฅผ ์ป๊ธฐ ์ํด getResult() ๋ฉ์๋๋ฅผ ์ ์ธํ๋ค.
ํด๋น ๋ฉ์๋๋ getRouletteProbability() ๋ฅผ ํตํด ์์ฑ๋ TreeMap ์ ์ธ์๋ก ๋ฐ๊ณ , ๋ด๋ถ์์ Random ์ซ์๋ฅผ ๋ง๋ค์ด higherEntry() ๋ฉ์๋์ ์ธ์๋ก ์ฌ์ฉํ๋ค. higherEntry(randomValue)๋ TreeMap์ key ์ค randomValue๋ณด๋ค ํฌ๋ฉด์ ๋์์ ๊ฐ์ฅ ๊ทผ์ ํ key ๋ฅผ ์ ํํด ๋ฐํํ๋ค. ์๋ฅผ๋ค์ด randomValue ๊ฐ์ด 0.55 ์ผ ๋ 0.55 ๋ณด๋ค ํฌ๋ฉด์ ๊ฐ์ฅ ๊ทผ์ ํ ๊ฐ์ธ 0.8์ key ๋ก ๊ฐ๋ value ๋ฅผ ๋ฐํํ๋ค. ๋๋ค ๊ฐ์ Random ํด๋์ค์ nextDouble ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ค. ์ฝํ๋ฆฐ ๊ณต์ ๋ฌธ์์ ์ํ๋ฉด nextDouble() ๋ฉ์๋๋ 0 <= x < 1 ๋ฒ์์ Double ํ ๊ฐ์ ๋ฐํํ๋ค.
TreeMap ์ key ๊ฐ ์ค๋ฆ์ฐจ์์ผ๋ก ์ ์ฅ๋๋ ํน์ง์ ํ์ฉํด ํ๋ฅ map ์ ๊ตฌํํ ์ ์์๋ค.
๐ ์ฌ๊ณ ์ฐจ๊ฐ์ ๊ณ ๋ คํ ํ๋ฅ ์ฐ์ฐ
์๊ตฌ์ฌํญ์์ ์ฌ๊ณ ๋ถ๋ถ์ ๋ค์ ํ์ธํด๋ณด์. ์ด ์ฌ๊ณ ๋ 100๊ฐ๋ก ๊ฐ ์์ดํ ์ ์ฌ๊ณ ๊ฐ ๋ชจ๋ ์์ง๋๋ฉด ํด๋น ์์ดํ ์ ๋น์ฒจ ํ๋ฅ ์ ๋ค๋ฅธ ์์ดํ ์ ๋ถ๋ฐฐํด์ผ ํ๋ค. ๋ง์ฝ ๋น์ฒจํ๋ฅ ์ด 0.5์ธ ์์ดํ 10 ์ด ๋ชจ๋ ์์ง๋๋ฉด ๋จ์ ์์ดํ (5๊ฐ์ง ์ข ๋ฅ)์ ํ๋ฅ ์ ๊ฐ๊ฐ ๋ถ๋ฐฐํด์ผํ๋ค. ์ด ๊ฒฝ์ฐ์๋ ๋จ์ ์์ดํ ์ ๊ฐฏ์๊ฐ 5๊ฐ์ด๋ฏ๋ก ๋ถ๋ฐฐํด์ผ ํ ํ๋ฅ ๊ฐ์ 0.5 / 5 = 0.1 ์ด ๋๋ค. ์ด๋ฅผ ๊ณ ๋ คํ์ฌ ํ๋ฅ map ์ ๋ค์ ๊ตฌํํด๋ณด์.
class RouletteGame(val sectorList: List<Sector>) {
private val soldOuts = ConcurrentHashMap<Int, Roulette>()
private fun getCurrentProbability(): BigDecimal? {
val sumSoldOutProbability = soldOuts.values
.stream()
.map { obj: Roulette -> obj.probability }
.reduce(BigDecimal.ZERO)
{ obj: BigDecimal, agent: BigDecimal? -> obj.add(agent) }
val availableSize = rouletteList.size - soldOuts.size
return sumSoldOutProbability.divide(BigDecimal.valueOf(availableSize.toLong()), 3, RoundingMode.HALF_UP)
}
...
}
getCurrentProbability() ๋ ์ฌ๊ณ ๊ฐ ์์ง๋ ์์ดํ ์ ํ๋ฅ ์ ๋ค๋ฅธ ์์ดํ ์๊ฒ ์ฌ๋ถ๋ฐฐ ํ๋ ๋ฉ์๋๋ค.
์ด ๋ soldOuts ๋ผ๋ ์ธ์คํด์ค ๋ณ์๋ฅผ ํ์ฉํ์ฌ ํ์ฌ ๋ฃฐ๋ ๊ฒ์์์ ์ฌ๊ณ ๊ฐ ์์ง๋ ์์ดํ ์ ์ ์ฅํ๋ค. ํด๋น ๋ณ์๋ ConcurrentHashMap<Int, Roulette> ํ์ ์ผ๋ก ์๋ ์์๋ ์์ดํ ์ ์ ์ฅํ๋ค.
์ด์ ์์์ ์ดํด๋ณธ getRouletteProbability() ๋ฉ์๋์ getCurrentProbability() ๋ฉ์๋๋ฅผ ๋ น์ฌ๋ณด์. remainProbabiliy ๋ ์์ง ์ฌ๊ณ ๊ฐ ๋จ์์๋ ์์ดํ ์๊ฒ ๋ถ๋ฐฐํด์ค ํ๋ฅ ๊ฐ์ด๋ค. ์์ง ์ฌ๊ณ ๊ฐ ๋จ์์๋ ์์ดํ ์ ๊ตฌํ๊ธฐ ์ํด filter() ๋ฅผ ์ถ๊ฐ(a)ํ๊ณ , ํํฐ๋ง ๋ ์ฌ๊ณ ์ ์์ดํ ์ remainProbability ๋ฅผ ๋ํด์ค๋ค(b).
class RouletteGame(val rouletteList: List<Roulette>) {
private val soldOuts = ConcurrentHashMap<Int, Roulette>()
private val random = Random()
...
private fun getRouletteProbability(): TreeMap<BigDecimal, Roulette> {
val remainProbability = getCurrentProbability()
var cumulativeProbability = BigDecimal.ZERO
val probabilityMap = TreeMap<BigDecimal, Roulette>()
this.rouletteList
.filter { r -> r.stocks.get() != 0 } // (a)
.forEach { r ->
val add = r.probability.add(remainProbability) // (b)
cumulativeProbability = cumulativeProbability.add(add)
probabilityMap[cumulativeProbability] = r
}
return probabilityMap
}
private fun getCurrentProbability(): BigDecimal? {
val sumSoldOutProbability = soldOuts.values
.stream()
.map { obj: Roulette -> obj.probability }
.reduce(BigDecimal.ZERO)
{ obj: BigDecimal, agent: BigDecimal? -> obj.add(agent) }
val availableSize = rouletteList.size - soldOuts.size
return sumSoldOutProbability.divide(BigDecimal.valueOf(availableSize.toLong()), 3, RoundingMode.HALF_UP)
}
...
}
๐ play() ๋ฉ์๋
์์์ ์ธ๊ธํ ๋ชจ๋ ๋ด์ฉ์ ์คํํ๋ ๊ฒ์ด play() ๋ฉ์๋๋ก, ํด๋ผ์ด์ธํธ๊ฐ ๋ฃฐ๋ ์ ํ๋ฒ ๋๋ฆด ๋ ํธ์ถ๋๋ค.
getResult() ๋ฉ์๋์ ๊ฒฐ๊ณผ๋ก ๋์จ roulette ๊ฐ์ ๋น์ฒจ๋ ์์ดํ ์ ํด๋น๋๋ฉฐ, ์ด๋ฅผ decreaseStock() ๋ฉ์๋์ ์ธ์๋ก ํธ์ถํ์ฌ ํด๋น ์์ดํ ์ ์ฌ๊ณ ๋ฅผ ์ฐจ๊ฐํ๋ค. ์ด ๋ ํด๋น ์์ดํ (roulette) ์ stocks ๊ฐ 0์ด๋ฉด soldOut ์ผ๋ก ํ๋จํ์ฌ soldOuts concurrentHashMap ์ ์ถ๊ฐํ๋ค.
class RouletteGame(val rouletteList: List<Roulette>) {
private val soldOuts = ConcurrentHashMap<Int, Roulette>()
private val random = Random()
fun play(): Roulette {
checkPlayAvailable()
val rouletteProbability = getRouletteProbability()
val roulette = getResult(rouletteProbability)
decreaseStock(roulette)
return roulette
}
private fun checkPlayAvailable() {
if (this.rouletteList.size == this.soldOuts.size) {
throw IllegalStateException()
}
}
private fun getResult(rouletteMap: TreeMap<BigDecimal, Roulette>): Roulette {
val randomValue = BigDecimal.valueOf(random.nextDouble())
return rouletteMap.higherEntry(randomValue).value
}
private fun decreaseStock(roulette: Roulette) {
roulette.decreaseStock()
if (roulette.stocks.get() == 0) {
soldOuts[roulette.score] = roulette
}
}
...
}
๐ ๊ฒฐ๋ก
์ด๋ ๊ฒ ๋๋ ค๋๋ ค ๋๋ฆผํ์ ์ฝ๋๋ก ๊ตฌํํ๋ค. ์์ง๋ ์์ดํ ์ ํ๋ฅ ์ ๋ค๋ฅธ ์์ดํ ์ผ๋ก ๋ถ๋ฐฐํ๋ ์๊ตฌ์ฌํญ์ด ๊น๋ค๋กญ๊ธด ํ์ง๋ง TreeMap ์๋ฃํ์ ์ฌ์ฉํ์ฌ ํด๊ฒฐํ ์ ์์๋ค. ๋ฉํฐ์ค๋ ๋๊ฐ ๋์์ ์ ๊ทผํ ์ ์๋ soldOuts ๋ณ์๋ฅผ concurrentHashMap ํ์ ์ผ๋ก ์ ์ธํ ๋๋ถ์ ๋์์ฑ ๊ด๋ จ ๋ฌธ์ ๋ ํด๊ฒฐํ ์ ์์๋ค. ๋์์ฑ ๋ฌธ์ ์ ๊ดํ ์ฝ๋๋ ํ ์คํธ ์ฝ๋๋ก ๋์ฒดํ๋ค.
ํ ์คํธ์ฝ๋๋ ์๋์์ ํ์ธํ ์ ์๋ค.
๐ ํ ์คํธ ์ฝ๋
internal class RouletteUnitTest {
private val initialStock = 100
private fun ๋ฃฐ๋ _์ด๊ธฐํ(): List<Roulette> {
return listOf(
Roulette(10, BigDecimal.valueOf(0.5), AtomicInteger(40)),
Roulette(50, BigDecimal.valueOf(0.3), AtomicInteger(30)),
Roulette(100, BigDecimal.valueOf(0.1), AtomicInteger(16)),
Roulette(500, BigDecimal.valueOf(0.05), AtomicInteger(8)),
Roulette(800, BigDecimal.valueOf(0.03), AtomicInteger(4)),
Roulette(1000, BigDecimal.valueOf(0.02), AtomicInteger(2))
)
}
@Test
@DisplayName("๋ฃฐ๋ ๊ฒ์ ํ๋ ์ด ํ
์คํธ")
fun playTest() {
val initialRoulette = ๋ฃฐ๋ _์ด๊ธฐํ()
val rouletteGame = RouletteGame(initialRoulette)
for (i in 0 until initialStock) {
val play = rouletteGame.play()
if (play.stocks.get() == 0) {
println(i.toString() + "๋ฒ์งธ play: " + play.score + "์ ์์ดํ
์์ง")
}
}
val rouletteList = rouletteGame.rouletteList
for (roulette in rouletteList) {
Assertions.assertThat(roulette.stocks.get()).isEqualTo(0)
}
}
@Test
@DisplayName("ํ๋ ์ด ๋ง๋ค ์ฌ๊ณ ๋ 1์ฉ ์ฐจ๊ฐ๋๋ค")
fun stockCountTest() {
val initialRoulette = ๋ฃฐ๋ _์ด๊ธฐํ()
val rouletteGame = RouletteGame(initialRoulette)
val totalStock = rouletteGame.currentTotalStock()
Assertions.assertThat(totalStock).isEqualTo(initialStock)
for (i in 1..initialStock) {
rouletteGame.play()
val currentStock = rouletteGame.currentTotalStock()
Assertions.assertThat(currentStock).isEqualTo(initialStock - i)
}
}
@Test
@DisplayName("์ฌ๊ณ ์์๋ ์์ธ๋ฅผ ๋์ง๋ค")
fun overPlayTest() {
val initialRoulette = ๋ฃฐ๋ _์ด๊ธฐํ()
val rouletteGame = RouletteGame(initialRoulette)
for (i in 1..100) {
rouletteGame.play()
}
Assertions.assertThatThrownBy { rouletteGame.play() }
}
@Test
@DisplayName("์ค๋ ๋ 30๊ฐ ๋์์ฑ ํ
์คํธ - Future")
@Throws(
InterruptedException::class,
ExecutionException::class
)
fun multiThreadTest() {
val initialRoulette = ๋ฃฐ๋ _์ด๊ธฐํ()
val initialStock = initialRoulette.stream()
.mapToInt { r -> r.stocks.get() }
.sum()
val rouletteGame = RouletteGame(initialRoulette)
val numThreads = 30
val executorService = Executors.newFixedThreadPool(numThreads)
for (i in 0 until numThreads) {
val future = executorService.submit<Roulette> { rouletteGame.play() }
future.get()
}
executorService.shutdown()
Assertions.assertThat(initialStock - numThreads)
.isEqualTo(rouletteGame.currentTotalStock())
}
@Test
@DisplayName("์ค๋ ๋ 30๊ฐ ๋์์ฑ ํ
์คํธ - Thread")
@Throws(
InterruptedException::class
)
fun multiThreadTestV2() {
val initialRoulette = ๋ฃฐ๋ _์ด๊ธฐํ()
val rouletteGame = RouletteGame(initialRoulette)
val numThreads = 30
val threads: MutableList<Thread> = ArrayList()
for (i in 0 until numThreads) {
val thread = Thread { rouletteGame.play() }
threads.add(thread)
thread.start()
}
for (thread in threads) {
thread.join()
}
Assertions.assertThat(100 - numThreads).isEqualTo(rouletteGame.currentTotalStock())
}
}
๐ ๊ทธ ๋ค์์?
์ด์ ํด์ผ ํ ์ผ์ ํฌ๊ฒ 2๊ฐ์ง๋ก ์ถ๋ ค๋ณผ ์ ์๋ค.
1. ์ธ๋ถ ์ ์ฅ์๋ฅผ ์ฐ๋ํด์ ๋ฃฐ๋ ์ ๊ด๋ฆฌ
2. ์น ํต์ ์ ์ด์ฉํด ์ค์ ์ฌ์ฉ์๊ฐ ๋ฃฐ๋ ๊ฒ์์ ํ ์ ์๋๋ก ๋ง๋๋ ๊ฒ.
์ธ์ ๋ค์ ์๋ฆฌ์ฆ๋ฅผ ์ธ์ง๋ ๋ชฐ๋ฃจ๊ฒ ์ง๋ง 2ํธ์์ ์ธ๋ถ ์ ์ฅ์๋ฅผ ์ฐ๋ํด์ ๋ฃฐ๋ ์ ๋ณด๋ฅผ ๊ด๋ฆฌํ๋ ๋ฒ์ ๋ค๋ค๋ณด๊ฒ ๋ค.
github
ref.
https://docs.oracle.com/javase/9/docs/api/java/util/NavigableMap.html
https://tech.socarcorp.kr/dev/2021/10/19/sub-interfaces-navigablemap.html
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.random/-random/