Custom Grid - boostcampwm2025/and01-boostcamp GitHub Wiki

๋ฌธ์ œ

Compose ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์ œ๊ณตํ•˜๋Š” Layout์ด UI ์š”๊ตฌ์‚ฌํ•ญ์„ ๋งŒ์กฑ์‹œํ‚ฌ ์ˆ˜ ์—†์Œ.

1. ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ฐœ์š”

1.1 2D Occupied Grid ๋ฐฉ์‹

  • rows ร— columns ํฌ๊ธฐ์˜ 2์ฐจ์› Boolean ๋ฐฐ์—ด์„ ์‚ฌ์šฉ
  • ๊ฐ ์•„์ดํ…œ ๋ฐฐ์น˜ ์‹œ, ํ•ด๋‹น ์•„์ดํ…œ์ด ์ฐจ์ง€ํ•  ๋ชจ๋“  ์…€์„ ์ˆœํšŒํ•˜๋ฉฐ ์ถฉ๋Œ ์—ฌ๋ถ€ ๊ฒ€์‚ฌ
  • ๋นˆ์นธ์ด ์กด์žฌํ•˜๋Š” ์ž„์˜ ์œ„์น˜ ๋ฐฐ์น˜ ํ—ˆ์šฉ

์ฃผ์š” ์ฝ”๋“œ

// ๊ทธ๋ฆฌ๋“œ ์ ์œ  ์ƒํƒœ
val occupied = Array(rows) { BooleanArray(columns) }

// ํŠน์ • ์œ„์น˜์— ์•„์ดํ…œ ๋ฐฐ์น˜ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ๊ฒ€์‚ฌ
fun canPlace(r: Int, c: Int, rs: Int, cs: Int): Boolean {
    if (r + rs > rows || c + cs > columns) return false

    for (rr in r until r + rs) {
        for (cc in c until c + cs) {
            if (occupied[rr][cc]) return false
        }
    }
    return true
}

// ์•„์ดํ…œ ๋ฐฐ์น˜ ํ›„ ์ ์œ  ์ƒํƒœ ๋งˆํ‚น
fun mark(r: Int, c: Int, rs: Int, cs: Int) {
    for (rr in r until r + rs) {
        for (cc in c until c + cs) {
            occupied[rr][cc] = true
        }
    }
}

// ์•„์ดํ…œ ๋ฐฐ์น˜ ๋กœ์ง
for (r in 0 until rows) {
    for (c in 0 until columns) {
        if (canPlace(r, c, item.rowSpan, item.colSpan)) {
            mark(r, c, item.rowSpan, item.colSpan)
            // placeable ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ
            break
        }
    }
}

1.2 ์ถ•(์—ด) ์Œ“๊ธฐ ๋ฐฉ์‹ (Skyline / Column Height)

  • ๊ฐ ์—ด(column)์— ๋Œ€ํ•ด ํ˜„์žฌ๊นŒ์ง€ ์Œ“์ธ ๋†’์ด๋งŒ ๊ด€๋ฆฌ
  • ์•„์ดํ…œ์€ ํ•ญ์ƒ ์•„๋ž˜์—์„œ ์œ„๋กœ๋งŒ ์Œ“์ž„
  • ์ค‘๊ฐ„ ๋นˆ์นธ(holes)์ด ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ตฌ์กฐ๋ฅผ ์ „์ œ๋กœ ํ•จ

์ฃผ์š” ์ฝ”๋“œ

// ๊ฐ ์—ด์˜ ํ˜„์žฌ ๋†’์ด
val columnHeights = IntArray(columns) { 0 }

// ์•„์ดํ…œ ๋ฐฐ์น˜ ๋กœ์ง
for (c in 0..columns - item.colSpan) {

    // ํ•ด๋‹น ์•„์ดํ…œ์ด ์ฐจ์ง€ํ•  ์—ด ๋ฒ”์œ„ ์ค‘ ๊ฐ€์žฅ ๋†’์€ ๋†’์ด
    val baseRow = (c until c + item.colSpan)
        .maxOf { columnHeights[it] }

    // ๊ทธ๋ฆฌ๋“œ ๋†’์ด ์ดˆ๊ณผ ์‹œ ๋ฐฐ์น˜ ๋ถˆ๊ฐ€
    if (baseRow + item.rowSpan > rows) continue

    // ๋ฐฐ์น˜ ํ™•์ • ํ›„ ์—ด ๋†’์ด ๊ฐฑ์‹ 
    for (cc in c until c + item.colSpan) {
        columnHeights[cc] = baseRow + item.rowSpan
    }

    // placeable ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ
    break
}

์ถ• ์Œ“๊ธฐ ๋ฐฉ์‹ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ˆ˜์ •

var bestCol = -1
var bestBaseRow = Int.MAX_VALUE

for (c in 0..columns - item.colSpan) {
    val baseRow = (c until c + item.colSpan).maxOf { columnHeights[it] }
    if (baseRow + item.rowSpan > rows) continue

    if (baseRow < bestBaseRow) {
        bestBaseRow = baseRow
        bestCol = c
    }
}

๊ฐ€์žฅ ๋‚ฎ์€ ์ตœ์ ์˜ ์—ด์„ ์ฐพ๋Š” ๋ฐฉ์‹์œผ๋กœ ์ˆ˜์ •


2. ๋ฐฐ์น˜ ํŒ๋‹จ ๋กœ์ง ๋น„๊ต

2.1 2D Occupied Grid ๋ฐฉ์‹

  1. ๋ชจ๋“  (row, column) ์œ„์น˜๋ฅผ ์ˆœํšŒ
  2. ์•„์ดํ…œ์ด ์ฐจ์ง€ํ•  rowSpan ร— colSpan ์˜์—ญ ์ „์ฒด ๊ฒ€์‚ฌ
  3. ํ•˜๋‚˜๋ผ๋„ ์ด๋ฏธ occupied ์ƒํƒœ๋ฉด ๋ฐฐ์น˜ ๋ถˆ๊ฐ€
  4. ๋ฐฐ์น˜ ๊ฐ€๋Šฅ ์‹œ ํ•ด๋‹น ์˜์—ญ ์ „์ฒด๋ฅผ occupied๋กœ ๋งˆํ‚น
๋ธ”๋ก ํ•˜๋‚˜ ๋‹น ์‹œ๊ฐ„ ๋ณต์žก๋„: O(rows ร— columns ร— rowSpan ร— colSpan)

์žฅ์ 

  • ์ค‘๊ฐ„ ๋นˆ์นธ ํ™œ์šฉ ๊ฐ€๋Šฅ
  • ๋น„๊ต์  ์•„์ดํ…œ ์ˆœ์„œ ์˜์กด์„ฑ์ด ๋‚ฎ์Œ(๋นˆ ๊ณต๊ฐ„์ด ์ƒ๊ธฐ๋Š” ์•„์ดํ…œ ์ˆœ์„œ๋”๋ผ๋„ ๋ชจ๋“  ์•„์ดํ…œ์ด ๋ฐฐ์น˜ ๋จ)

๋‹จ์ 

  • ๋กœ์ง ๋ณต์žก๋„ ๋†’์Œ
  • ์‹œ๊ฐ„ ๋ณต์žก๋„๊ฐ€ ๋น„๊ต์  ๋†’์Œ

2.2 ์ถ• ์Œ“๊ธฐ ๋ฐฉ์‹

  1. ๋ฐฐ์น˜ ์‹œ์ž‘ ์—ด c๋งŒ ์ˆœํšŒ
  2. c .. c + colSpan ๋ฒ”์œ„์—์„œ ์ตœ๋Œ€ columnHeight ๊ณ„์‚ฐ
  3. baseHeight + rowSpan <= rows ์ธ์ง€๋งŒ ๊ฒ€์‚ฌ
  4. ๋ฐฐ์น˜ ํ™•์ • ์‹œ ํ•ด๋‹น ์—ด๋“ค์˜ height ๊ฐฑ์‹ 
๋ธ”๋ก ํ•˜๋‚˜ ๋‹น ์‹œ๊ฐ„ ๋ณต์žก๋„: O(columns ร— colSpan)

์žฅ์ 

  • ๋น„๊ต์  ์‹œ๊ฐ„ ๋ณต์žก๋„๊ฐ€ ๋‚ฎ์Œ

๋‹จ์ 

  • ์ค‘๊ฐ„ ๋นˆ์นธ ํ™œ์šฉ ๋ถˆ๊ฐ€
  • ์•„์ดํ…œ ์ˆœ์„œ์— ๋”ฐ๋ฅธ ๊ฒฐ๊ณผ ์˜์กด์„ฑ ํผ(๋นˆ ๊ณต๊ฐ„์ด ์ƒ๊ธฐ๋Š” ์ˆœ์„œ์ผ ๊ฒฝ์šฐ ๋ชจ๋“  ์•„์ดํ…œ์„ ๋ฐฐ์น˜ํ•˜์ง€ ๋ชปํ•จ)

3. ๊ฒฐ๋ก 

  • 2D Occupied Grid ๋ฐฉ์‹์€ ๋†’์€ ์ž์œ ๋„์™€ ์ •๋ฐ€ ์ œ์–ด๋ฅผ ์ œ๊ณตํ•œ๋‹ค.
  • ์ถ• ์Œ“๊ธฐ ๋ฐฉ์‹์€ ๋‹จ์ˆœํ•จ๊ณผ ์„ฑ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ๋Œ€์‹  ์ œ์•ฝ์„ ์ˆ˜์šฉํ•ด์•ผ ํ•œ๋‹ค.
  • ํ˜„์žฌ๋Š” ๋ฐฐ์น˜์™€ ์ˆœ์„œ๋ฅผ ๋ณด์žฅํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์‹œ๊ฐ„๋ณต์žก๋„๊ฐ€ ๋‚ฎ์€ ์ถ• ์Œ“๊ธฐ ๋ฐฉ์‹์„ ์„ ํƒ

4. ์ถ”๊ฐ€ ๊ณ ๋ ค

  • ๊ทธ๋ฆฌ๋“œ ๋ทฐ์— ๋‹จ์ˆœ ์ด๋ฏธ์ง€ ์ปจํ…์ธ ๋งŒ์„ ๊ณ ์ •์ ์œผ๋กœ ์‚ฌ์šฉ๊ฐ€๋Šฅ ํ•˜๋‚˜ ์ถ”ํ›„ ๋‹ค๋ฅธ ์ปจํ…์ธ ๋„ ์‚ฝ์ž… ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ™•์žฅ

  • ํ˜„์žฌ๋Š” ๊ณ ์ •๋œ ๋ฐฐ์น˜, ์ˆœ์„œ๋กœ ๋ธ”๋Ÿญ๋“ค์„ ๋ฐฐ์น˜ํ•˜๊ณ  ์žˆ์œผ๋‚˜ ์ถ”ํ›„ ์‚ฌ์šฉ์ž์˜ ํŽธ์˜์„ฑ์„ ๊ณ ๋ คํ•ด์•ผํ•จ

    • ๊ทธ๋ฆฌ๋“œ์˜ ํ–‰๊ณผ ์—ด์„ ์ž์œ ๋กญ๊ฒŒ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ
    • ๊ทธ๋ฆฌ๋“œ ๋‚ด์˜ ๋ธ”๋ก๋“ค์˜ ๋ฐฐ์น˜๋ฅผ ๋ณ€๊ฒฝ
    • ๊ทธ๋ฆฌ๋“œ ๋‚ด ๋ธ”๋ก์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๋ธ”๋ก์˜ ํฌ๊ธฐ๋ฅผ ๋ณ€๊ฒฝ
  • ์ด๋ฏธ์ง€๋ฅผ ๋ธ”๋Ÿญ์— ๋ฐฐ์น˜ ํ›„ crop์œผ๋กœ ์ฒ˜๋ฆฌ ์ค‘์ด๊ธฐ์— ์ด๋ฏธ์ง€์˜ ๋น„์œจ์„ ๊ณ ๋ คํ•˜์ง€ ๋ชปํ•œ ์ฑ„ ์ž˜๋ ค์„œ ๋ณด์ผ ๊ฐ€๋Šฅ์„ฑ์ด ์กด์žฌ

    • ๋‚ด๋ถ€ ์ปจํ…์ธ  ํฌ๊ธฐ๋ฅผ ๊ณ ๋ คํ•ด ์ ์ ˆํ•œ ๋ธ”๋ก์— ๋ฐฐ์น˜ํ•˜๋Š” ์ฒ˜๋ฆฌ