Practice for coding test

프로그래머스 - Level 1. 공원 산책 / golang (go) / javascript (js)

Gray Park 2023. 3. 30. 20:00
728x90
반응형

문제설명

문제분석

이 문제는 특정 조건에서는 현재 위치를 벗어나지 않고, 그 외에는 산책로를 이동하며 위치를 변경하는 문제입니다. 특정 조건은 다음과 같습니다.

  • 주어진 방향으로 이동할 때 공원을 벗어나는지 확인합니다.
  • 주어진 방향으로 이동 중 장애물을 만나는지 확인합니다.

이 문제를 풀면서 조금 난감했던 지점이 있었는데, 코드와 함께 설명토록 하겠습니다.

이해하기 쉽게 코드작성하기

import (
    "strings"
    "strconv"
    "github.com/thoas/go-funk"
)

func solution(park []string, routes []string) []int {
	answer := getPosition(park, "S")
	for _, route := range routes {
		way, feets := splitWayAndFeets(route)
		answer = move(answer, park, way, feets)
	}
	return answer
}

func move(cur []int, park []string, way string, feets int) []int {
	if cannotMove(cur, park, way, feets) == true {
		return cur
	}

	switch way {
	case "N":
		return []int{cur[0] - feets, cur[1]}
	case "S":
		return []int{cur[0] + feets, cur[1]}
	case "W":
		return []int{cur[0], cur[1] - feets}
	case "E":
		return []int{cur[0], cur[1] + feets}
	}
	return cur
}

func cannotMove(cur []int, park []string, way string, feets int) bool {
	switch way {
	case "N":
		return cur[0]-feets < 0 || func() bool {
			f := funk.Map(park, func(x string) string {
				return string(x[cur[1]])
			}).([]string)

			i := funk.IndexOfString(f[cur[0]-feets:cur[0]], "X")

			if i == -1 {
				return false
			}
			return true
		}()
	case "S":
		return cur[0]+feets >= len(park) || func() bool {
			f := funk.Map(park, func(x string) string {
				return string(x[cur[1]])
			}).([]string)

			i := funk.IndexOfString(f[cur[0]:cur[0]+feets+1], "X")

			if i == -1 {
				return false
			}
			return true
		}()
	case "W":
		return cur[1]-feets < 0 || func() bool {
			f := strings.Split(park[cur[0]], "")

			i := funk.IndexOfString(f[cur[1]-feets:cur[1]], "X")

			if i == -1 {
				return false
			}
			return true
		}()
	case "E":
		return cur[1]+feets >= len(park[0]) || func() bool {
			f := strings.Split(park[cur[0]], "")

			i := funk.IndexOfString(f[cur[1]:cur[1]+feets+1], "X")

			if i == -1 {
				return false
			}
			return true
		}()
	}
	return true
}

func splitWayAndFeets(route string) (string, int) {
	r := strings.Split(route, " ")
	i, e := strconv.Atoi(r[1])
	if e != nil {
		panic(e)
	}
	return r[0], i
}

func getPosition(park []string, target string) []int {
	for x, road := range park {
		for y := 0; y < len(road); y++ {
			if string(park[x][y]) == target {
				return []int{x, y}
			}
		}
	}

	return []int{0, 0}
}

먼저 가장 어려웠던 지점은 go 문법에 익숙하지 않다는 겁니다. JavaScript 문법에 익숙한 저로써는 다양한 메서드를 활용하는 게 참 편했는데, go는 익숙하지 않아서 어디에 어떤 패키지가 있는지 일일이 찾아서 진행했습니다. 그래도 그 덕분에 몇가지 유용한 패키지를 알게되어 기쁩니다.

 

감상은 이쯤하고, 코드를 살펴보겠습니다.

 

먼저 solution 함수입니다.

func solution(park []string, routes []string) []int {
	answer := getPosition(park, "S")
	for _, route := range routes {
		way, feets := splitWayAndFeets(route)
		answer = move(answer, park, way, feets)
	}
	return answer
}

/*
 * func move(cur []int, park []string, way string, feets int) []int
 * ...생략...
 */
 
func splitWayAndFeets(route string) (string, int) {
	r := strings.Split(route, " ")
	i, e := strconv.Atoi(r[1])
	if e != nil {
		panic(e)
	}
	return r[0], i
}

func getPosition(park []string, target string) []int {
	for x, road := range park {
		for y := 0; y < len(road); y++ {
			if string(park[x][y]) == target {
				return []int{x, y}
			}
		}
	}

	return []int{0, 0}
}

먼저 getPosition 함수를 이용해 최초의 위치 좌표를 answer에 할당합니다. getPosition 함수는 park를 순회하며 target 문자를 최초로 찾는 경우 해당 문자의 좌표를 리턴합니다.

routes를 순회하며 routes를 방향과 거리로 구분했습니다. 자바스크립트에서는 String.split(" ")을 사용했을텐데... 하며 찾은 패키지 strings의 Split 메서드를 이용하면 동일한 결과를 얻을 수 있습니다. 그러나 split의 결과 중 두번째 요소인 거리는 숫자로 형변환이 필요하여 따로 함수로 정의하여 사용했습니다.

 

이렇게 구해진 answer(현재 좌표), way, feets를 move 함수에 전달하여 answer의 좌표를 이동합니다. 이때 move 내에서는 이동할 수 없는 경우를 확인하고, 이동이 불가한 경우 현재 좌표를 그대로 리턴합니다.

func move(cur []int, park []string, way string, feets int) []int {
	if cannotMove(cur, park, way, feets) == true {
		return cur
	}

	switch way {
	case "N":
		return []int{cur[0] - feets, cur[1]}
	case "S":
		return []int{cur[0] + feets, cur[1]}
	case "W":
		return []int{cur[0], cur[1] - feets}
	case "E":
		return []int{cur[0], cur[1] + feets}
	}
	return cur
}

func cannotMove(cur []int, park []string, way string, feets int) bool {
	switch way {
	case "N":
		return cur[0]-feets < 0 || func() bool {
			f := funk.Map(park, func(x string) string {
				return string(x[cur[1]])
			}).([]string)

			i := funk.IndexOfString(f[cur[0]-feets:cur[0]], "X")

			if i == -1 {
				return false
			}
			return true
		}()
	case "S":
		return cur[0]+feets >= len(park) || func() bool {
			f := funk.Map(park, func(x string) string {
				return string(x[cur[1]])
			}).([]string)

			i := funk.IndexOfString(f[cur[0]:cur[0]+feets+1], "X")

			if i == -1 {
				return false
			}
			return true
		}()
	case "W":
		return cur[1]-feets < 0 || func() bool {
			f := strings.Split(park[cur[0]], "")

			i := funk.IndexOfString(f[cur[1]-feets:cur[1]], "X")

			if i == -1 {
				return false
			}
			return true
		}()
	case "E":
		return cur[1]+feets >= len(park[0]) || func() bool {
			f := strings.Split(park[cur[0]], "")

			i := funk.IndexOfString(f[cur[1]:cur[1]+feets+1], "X")

			if i == -1 {
				return false
			}
			return true
		}()
	}
	return true
}

cannotMove 함수는 현재 위치로부터 이동할 방향(way)으로 feets만큼 이동할 수 있는지 확인합니다. 먼저 방향에 따라 이동 후의 좌표가 park 내에 있는지 검사하고, 갈 방향의 feets(거리) 내에 장애물("X")가 있는지 검사합니다. 만약 해당 방향(way)으로 feets만큼 이동했을 때 park를 벗어나거나 동선에 장애물("X")가 존재하는 경우 true를 리턴합니다.

 

제가 헤맨 곳은 동선에 장애물이 존재하는 경우입니다. 내가 앞으로 갈 구간만 추린 다음, 해당 구간에 장애물이 있는지 검사하면 됐었는데, 저는 그러지 않고 계속해서 해당 방향의 x 또는 y축 전체를 검사하고 있었습니다. 코드가 조금 길어지고, 문법에 익숙하지 않다보니 해결하는 데 조금 난항을 겪었습니다. 다행히 질문하기에 올라와있는 테스트케이스를 통해 문제점을 파악할 수 있었고, 범위를 제한하는 것만으로 문제를 해결할 수 있었습니다.

 

위 코드를 조금 손본다면, pass by value로 되어있는 부분 중 park에 대해 매번 새롭게 값을 생성하여 사용하고 있습니다. 이 부분을 pass by reference로 변경하면 메모리 누수를 줄일 수 있을 것 같습니다. (제 생각입니다.)

 

import (
    "strings"
    "strconv"
    "github.com/thoas/go-funk"
)

func solution(park []string, routes []string) []int {
	answer := getPosition(&park, "S")
	for _, route := range routes {
		way, feets := splitWayAndFeets(route)
		answer = move(answer, &park, way, feets)
	}
	return answer
}

func move(cur []int, park *[]string, way string, feets int) []int {
	if cannotMove(cur, park, way, feets) == true {
		return cur
	}

	switch way {
	case "N":
		return []int{cur[0] - feets, cur[1]}
	case "S":
		return []int{cur[0] + feets, cur[1]}
	case "W":
		return []int{cur[0], cur[1] - feets}
	case "E":
		return []int{cur[0], cur[1] + feets}
	}
	return cur
}

func cannotMove(cur []int, park *[]string, way string, feets int) bool {
	switch way {
	case "N":
		return cur[0]-feets < 0 || func() bool {
			f := funk.Map(*park, func(x string) string {
				return string(x[cur[1]])
			}).([]string)

			i := funk.IndexOfString(f[cur[0]-feets:cur[0]], "X")

			if i == -1 {
				return false
			}
			return true
		}()
	case "S":
		return cur[0]+feets >= len(*park) || func() bool {
			f := funk.Map(*park, func(x string) string {
				return string(x[cur[1]])
			}).([]string)

			i := funk.IndexOfString(f[cur[0]:cur[0]+feets+1], "X")

			if i == -1 {
				return false
			}
			return true
		}()
	case "W":
		return cur[1]-feets < 0 || func() bool {
			c := *park
			f := strings.Split(c[cur[0]], "")

			i := funk.IndexOfString(f[cur[1]-feets:cur[1]], "X")

			if i == -1 {
				return false
			}
			return true
		}()
	case "E":
		p := *park
		return cur[1]+feets >= len(p[0]) || func() bool {
			c := *park
			f := strings.Split(c[cur[0]], "")

			i := funk.IndexOfString(f[cur[1]:cur[1]+feets+1], "X")

			if i == -1 {
				return false
			}
			return true
		}()
	}
	return true
}

func splitWayAndFeets(route string) (string, int) {
	r := strings.Split(route, " ")
	i, e := strconv.Atoi(r[1])
	if e != nil {
		panic(e)
	}
	return r[0], i
}

func getPosition(park *[]string, target string) []int {
	p := *park
	for x, road := range p {
		for y := 0; y < len(road); y++ {
			if string(p[x][y]) == target {
				return []int{x, y}
			}
		}
	}

	return []int{0, 0}
}

 

다음은 같은 로직을 자바스크립트로 짠 코드입니다.

function solution(park, routes) {
    let answer = getPosition(park, "S");
    
    routes.forEach(x => {
        const wayAndFeets = x.split(" ");
        answer = goMove(answer, park, wayAndFeets[0], Number(wayAndFeets[1]));
    })
    return answer;
}

function goMove(cur, park, way, feets) {
    const toTheNorth = cur[0] - feets;
    const toTheSouth = cur[0] + feets;
    const toTheWest = cur[1] - feets;
    const toTheEast = cur[1] + feets
    if (cannotMove(cur, park, way, feets)) return cur;
    if(way === "N") return [toTheNorth, cur[1]];
    if(way === "S") return [toTheSouth, cur[1]];
    if(way === "W") return [cur[0], toTheWest];
    if(way === "E") return [cur[0], toTheEast];
    return cur;
}

function cannotMove(cur, park, way, feets) {
    const toTheNorth = cur[0] - feets;
    const toTheSouth = cur[0] + feets;
    const toTheWest = cur[1] - feets;
    const toTheEast = cur[1] + feets
    
    if(way === "N" && toTheNorth >= 0) {
        return park.map(x => x[cur[1]]).slice(toTheNorth, cur[0]).indexOf("X") > -1
    } else if(way === "S" && toTheSouth < park.length) {
        return park.map(x => x[cur[1]]).slice(cur[0], toTheSouth + 1).indexOf("X") > -1
    } else if(way === "W" && toTheWest >= 0) {
        return park[cur[0]].slice(toTheWest, cur[1]).indexOf("X") > -1
    } else if(way === "E" && toTheEast < park[0].length) {
        return park[cur[0]].slice(cur[1], toTheEast + 1).indexOf("X") > -1
    }
    return true;
}

function getPosition(park, target) {
    for(x = 0; x < park.length; x++) {
        y = park[x].indexOf(target);
        if(y >= 0) {
            return [x, y];
        }
    }
        
    return [0, 0]
}

golang에서는 if문에서 한줄로 작성하는 것이 불가능하고(에러), 삼항 연산자를 지원하지 않습니다. 때문에 if문이 많이 쓰이는 지금같은 경우 상당히 코드가 길어지는 아쉬움이 있습니다. 아마 아직 제가 golang에 익숙하지 않아 더 그런 것 같습니다. 문제를 좀 더 풀어보고, 프로그램을 작성하며 활용해보면서 빠르게 익숙해지도록 하겠습니다.

728x90
반응형