Algorithm

패턴매칭

GREEN.1229 2021. 6. 2. 10:48

문제 제시

인자로 문자열 2개가 전달된다.

하나는 패턴을 나타내는 문자열이고 하나는 데이터를 담은 문자열이다.

데이터를 담은 문자열이 패턴과 일치하는지를 판별하는 메서드를 만드시오.

 

예시

func solution(_ pattern: String, _ data: String) -> Bool {
    ...
}

solution("abbaa", "dog cat cat dog dog") // true
solution("xyxy", "one two two one") // false
solution("cbac", "dog cat bird dog") // true

문제 해결

func solution(_ pattern:String, _ target: String) -> Bool {
    // 패턴과 데이터를 문자열로 변환
    let patternChar = pattern.map{$0}
    let input = data.split(separator: " ")

    // 패턴과 데이터 딕셔너리 생성
    var patternDic: [Int: String] = [:]
    var dataDic: [Int: String] = [:]


    var patternResult: [String] = []
    var dataResult: [String] = []

    var patternCount: Int = 1
    var dataCount: Int = 1

    // 패턴 배열 비교하여 딕셔너리 생성
    for i in patternChar {
        if patternDic.isEmpty {
            patternDic.updateValue(String(i), forKey: patternCount)
            patternCount += 1
        } else {
            if !(patternDic.values.contains(String(i))) {
                patternDic.updateValue(String(i), forKey: patternCount)
            }
        }
    }

    // 데이타 배열 비교하여 딕셔너리 생성
    for i in input {
        if dataDic.isEmpty {
            dataDic.updateValue(String(i), forKey: dataCount)
            dataCount += 1
        } else {
            if !(dataDic.values.contains(String(i))) {
                dataDic.updateValue(String(i), forKey: dataCount)
            }
        }
    }

    // 패턴 딕셔너리 치환
    for i in patternChar {
        for j in patternDic {
            if String(i) == j.value {
                patternResult.append(String(j.key))
            }
        }
    }

    // 데이터 딕셔너리 치환
    for i in input {
        for j in dataDic {
            if String(i) == j.value {
                dataResult.append(String(j.key))
            }
        }
    }

    return patternResult == dataResult
}

처음에는 이렇게 복잡하게 for문을 돌면서 어떻게 어찌저찌 구현을 하였다.

그러나 이렇게 한다면 분명 효율성과 시간초과가 나올것이라고 생각하여 리팩토링 해보았다.

func solution(_ pattern: String, _ target: String) -> Bool {
    // 패턴 파라미터의 값 중복 제거 (패턴 종류 파악)
    let uniquePattern: String = Array(pattern).reduce("", { (result, next) -> String in
        if result.contains(next) {
            return result
        }
        return result + String(next)
    })
    
    // 타겟 파라미터 공백 문자열 기준 분리
    let targets = target.components(separatedBy: " ")
    
    // 패턴과 값을 담을 딕셔너리 생성
    var targetDic: [String: String] = [:]
    
    // 패턴의 초기 위치 값
    var patternIndex = 0
    
    // 최종 치환된 문자열 변수
    var finalTarget: String = ""
    
    // 타겟 돌면서 치환
    for i in targets {
        // 딕셔너리에 해당 키 값이 있는지 판별
        if let value = targetDic[i] {
            finalTarget.append(value)
            continue
        }
        
        guard patternIndex < uniquePattern.count else {
            return false
        }
        
        // 치환
        let patternValue = uniquePattern[uniquePattern.index(uniquePattern.startIndex, offsetBy: patternIndex)]
        targetDic[i] = String(patternValue)
        finalTarget.append(patternValue)
        
        patternIndex += 1
    }
    
    // 최종 패턴과 치환된 타겟 비교
    return pattern == finalTarget
}

고차함수를 적절히 사용하여 조금 더 효율성 있게 구조화해보았다.

딕셔너리를 돌며 해당 키에 대한 벨류 값을 비교해주고 있다면 넘어가며 해당 키 값을 추가하고 없다면 새로운 키값과 벨류를 만들어준다.

이런식으로 조금 더 반복문을 줄이고 효율성을 높여보았다.

 

사용된 개념

 - 문자열 다루기

 - 조건 / 반복문

 - 딕셔너리

 - 문자 치환

문제 뒷담화

해당 문제는 간단하게 생각이 들었는데 막상 구현을 하려니 생각보다 빠르고 쉽게 풀리지 않았다.

강제로 처음 문제 해결 방식처럼 구현은 할 수 있었지만 효율적으로 고차함수를 사용하여 푸는것이 머리가 안돌았다.

딕셔너리는 키 값이 중복될 수 없는 점을 활용하여 타겟을 돌며 딕셔너리에 해당 키에 대한 벨류값이 있는지 파악하고 치환해줌으로 해결하였다.