[Swift] [30일차] 뉴스 클러스터링

·2025년 1월 6일
0

SwiftAlgorithm

목록 보기
33/105
post-thumbnail

programers-뉴스클러스터링

문제 설명

  1. 문자열 두개 주어지는데 이걸 2글자씩 짤라라! (특수문자, 공백, 숫자 제외)
  2. 문자열끼리 비교하면서 유사도를 캐치해라 (교집합 / 합집합) * 66536 출력
  3. 중복하는 것도 min , max 로 정해진 규칙에 따라서 잘 추적해서 풀어라

문제 접근

  1. 일단 인덱스, 인덱스+1로 배열을 하나씩 만들면 될 것은 같은데
  2. 중요한 거는 같은원소가 있을 때 이거를 어떻게 머금고 있을 것이냐? 였다. SET로 해버리면 중복이 사라지다보니..
  3. 그럼 카운팅을 해서 빼줘야하는데, 카운팅은 딕셔너리로 해야하나?

일단 합집합은 같은 게 있다면 그 중에 max값, 그리고 각자만 가지고 있으면 다 더해주면 됨

교집합은 같은것 중에 min값


제출 코드

import Foundation

func containsSpecialCharacter(_ text: String) -> Bool {
    let specialCharacterSet = CharacterSet.alphanumerics.inverted // 특수문자 및 공백만 포함
    return text.unicodeScalars.contains { specialCharacterSet.contains($0) }
}

func solution(_ str1: String, _ str2: String) -> Int {
    var str1 = Array(str1.lowercased())
    var str2 = Array(str2.lowercased())
    var array_1 = [String]()
    var array_2 = [String]()

    for i in 0 ..< str1.count - 1 {
        if !containsSpecialCharacter(
            String(str1[i])) && !containsSpecialCharacter(String(str1[i+1])) // 일단 특수문자 포함안될때만 넣어줄 거
        {
            let new = String(str1[i])+String(str1[i+1])
            array_1.append(String(str1[i])+String(str1[i+1]))
        }
    }
    for i in 0 ..< str2.count - 1 {
        if !containsSpecialCharacter(
            String(str2[i])) && !containsSpecialCharacter(String(str2[i+1])) // 일단 특수문자 포함안될때만 넣어줄 거
        {
            let new = String(str2[i])+String(str2[i+1])
            array_2.append(String(str2[i])+String(str2[i+1]))
        }
    }
    if array_1.isEmpty || array_2.isEmpty {
        return 65536
    } // 공집합 처리
    var dict_1 = [String: Int]()
    var dict_2 = [String: Int]()

    for item in array_1 {
        dict_1[item, default: 0] += 1
    }

    for item in array_2 {
        dict_2[item, default: 0] += 1
    }
//    print(dict_1)
//    print(dict_2)

    // 초기 세팅 완료 ~ 이제 로직 구현
//    print(array_1)
//    print(array_2)
    var union = 0 // 합집합
    var intersection = 0 // 교집합
    for item in dict_1 {
        if let tmp = dict_2[item.key] {
            union += max(tmp, item.value)
            intersection += min(tmp, item.value)
        }
        else { // 안겹칠 때
            union += item.value
        }
    }
    for item in dict_2 {
        if let tmp = dict_1[item.key] {
            continue
        }
        else {
            union += item.value
        }
    }
    print(array_1)
    print(array_2)
    print(union)
    print(intersection)
    return Int((Float(intersection) / Float(union)) * 65536)
}

// print(solution("FRANCE", "french"))
print(solution("aa1+aa2", "AAAA12"))
// print(solution("handshake", "shake hands"))
// print(solution("E=M*C^2", "e=m*c^2"))

이렇게 해줬더니,

테스트3

입력값 〉 "aa1+aa2", "AAAA12"
기댓값 〉 43690

여기서 빠그라졌는데, 자세히 읽어보니까

세상에나 숫자도 안된다더라 그래서 이를 처리해주는 로직을 변경했다.
func containsSpecialCharacter(_ text: String) -> Bool {
    let specialCharacterSet = CharacterSet.alphanumerics.inverted // 특수문자 및 공백만 안되게 
    return text.unicodeScalars.contains { specialCharacterSet.contains($0) }
}

유효성 처리를 해주고 있던 이 containsSpecialCharacter 함수를

func containsInvalidCharacter(_ text: String) -> Bool {
    // 허용된 문자 집합: 알파벳만 포함
    let allowedCharacterSet = CharacterSet.letters
    return text.unicodeScalars.contains { !allowedCharacterSet.contains($0) }
}

이렇게 변경했다. letters만 되게끔 처리 방식을 변경했다.


수정 코드

import Foundation

func containsSpecialCharacter(_ text: String) -> Bool {
    let specialCharacterSet = CharacterSet.alphanumerics.inverted // 특수문자 및 공백만 포함
    return text.unicodeScalars.contains { specialCharacterSet.contains($0) }
}

func solution(_ str1: String, _ str2: String) -> Int {
    var str1 = Array(str1.lowercased())
    var str2 = Array(str2.lowercased())
    var array_1 = [String]()
    var array_2 = [String]()

    for i in 0 ..< str1.count - 1 {
        if !containsSpecialCharacter(
            String(str1[i])) && !containsSpecialCharacter(String(str1[i+1])) // 일단 특수문자 포함안될때만 넣어줄 거
        {
            let new = String(str1[i])+String(str1[i+1])
            array_1.append(String(str1[i])+String(str1[i+1]))
        }
    }
    for i in 0 ..< str2.count - 1 {
        if !containsSpecialCharacter(
            String(str2[i])) && !containsSpecialCharacter(String(str2[i+1])) // 일단 특수문자 포함안될때만 넣어줄 거
        {
            let new = String(str2[i])+String(str2[i+1])
            array_2.append(String(str2[i])+String(str2[i+1]))
        }
    }
    if array_1.isEmpty || array_2.isEmpty {
        return 65536
    } // 공집합 처리
    var dict_1 = [String: Int]()
    var dict_2 = [String: Int]()

    for item in array_1 {
        dict_1[item, default: 0] += 1
    }

    for item in array_2 {
        dict_2[item, default: 0] += 1
    }


    // 초기 세팅 완료 ~ 이제 로직 구현
//    print(array_1)
//    print(array_2)
    var union = 0 // 합집합
    var intersection = 0 // 교집합
    for item in dict_1 {
        if let tmp = dict_2[item.key] {
            union += max(tmp, item.value)
            intersection += min(tmp, item.value)
        }
        else { // 안겹칠 때
            union += item.value
        }
    }
    for item in dict_2 {
        if let tmp = dict_1[item.key] {
            continue
        }
        else {
            union += item.value
        }
    }
    return Int((Float(intersection) / Float(union)) * 65536)
}

근데 이것도 테케 하나가 틀리다고 나와서 좀 오래 방황했던 것 같다.

 if array_1.isEmpty || array_2.isEmpty {
        return 65536
    }

이 부분 때문이었는데, 나는 무조건 최소값이 65536이 나와야한다고 생각을 했는데, 한쪽만 공집합이면 아예 0이 나오는 것이더라. 좀 제대로 읽고 풀걸 그랬다.

 if array_1.isEmpty && array_2.isEmpty {
        return 65536
    }

최종코드

&&로 수정해줘서 다행히 다 맞을 수는 있었다.

import Foundation

func containsInvalidCharacter(_ text: String) -> Bool {
    // 허용된 문자 집합: 알파벳만 포함
    let allowedCharacterSet = CharacterSet.letters
    return text.unicodeScalars.contains { !allowedCharacterSet.contains($0) }
}

func solution(_ str1: String, _ str2: String) -> Int {
    var str1 = Array(str1.lowercased())
    var str2 = Array(str2.lowercased())

    var array_1 = [String]()
    var array_2 = [String]()

    for i in 0 ..< str1.count - 1 {
        if !containsInvalidCharacter(
            String(str1[i])) && !containsInvalidCharacter(String(str1[i+1])) // 일단 특수문자 포함안될때만 넣어줄 거
        {
            let new = String(str1[i])+String(str1[i+1])
            array_1.append(String(str1[i])+String(str1[i+1]))
        }
    }
    for i in 0 ..< str2.count - 1 {
        if !containsInvalidCharacter(
            String(str2[i])) && !containsInvalidCharacter(String(str2[i+1])) // 일단 특수문자 포함안될때만 넣어줄 거
        {
            let new = String(str2[i])+String(str2[i+1])
            array_2.append(String(str2[i])+String(str2[i+1]))
        }
    }
    if array_1.isEmpty && array_2.isEmpty {
        return 65536
    } // 공집합 처리
    var dict_1 = [String: Int]()
    var dict_2 = [String: Int]()

    for item in array_1 {
        dict_1[item, default: 0] += 1
    }

    for item in array_2 {
        dict_2[item, default: 0] += 1
    }

    // 초기 세팅 완료 ~ 이제 로직 구현

    var union = 0 // 합집합
    var intersection = 0 // 교집합
    for item in dict_1 {
        if let tmp = dict_2[item.key] {
            union += max(tmp, item.value)
            intersection += min(tmp, item.value)
        }
        else { // 안겹칠 때
            union += item.value
        }
    }
    for (key, value) in dict_2 {
        if dict_1[key] == nil {
            union += value
        }
    }

    if union == 0 {
        return 65536
    }
    return Int((Float(intersection) / Float(union)) * 65536)
}

타인의 코드

import Foundation

extension String {
    var isAlphanumeric: Bool {
        return !isEmpty && range(of: "[^a-zA-Z]", options: .regularExpression) == nil
    }
}

func solution(_ str1:String, _ str2:String) -> Int {
    let str1 = str1.lowercased()
    let str2 = str2.lowercased()

    var aDic: [String:Int] = [:]
    var bDic: [String:Int] = [:]
    var denomiator: Int = 0
    var numerator: Int = 0

    for i in 0 ..< str1.count - 1 {
        let subString = String(str1.prefix(i+2).suffix(2))
        if !subString.isAlphanumeric { continue }
        if aDic[subString] != nil {
            aDic[subString]! += 1
        } else {
            aDic[subString] = 1
        }
    }
    for i in 0 ..< str2.count - 1 {
        let subString = String(str2.prefix(i+2).suffix(2))
        if !subString.isAlphanumeric { continue }
        if bDic[subString] != nil {
            bDic[subString]! += 1
        } else {
            bDic[subString] = 1
        }
    }
    if aDic.isEmpty && bDic.isEmpty { return 65536 }

    aDic.keys.forEach { key in
        numerator += min(aDic[key] ?? 0, bDic[key] ?? 0)
    }
    Set(aDic.keys.map { String($0) } + bDic.keys.map { String($0) }).forEach { key in
        denomiator += max(aDic[key] ?? 0, bDic[key] ?? 0)
    }

    return 65536 * numerator / denomiator
}
  1. 고수들은 항상 extension 사용해서 아예 그 타입에 대해서 좀 더 직관적으로 표현하고자 하는 것 같다. 나는 그냥 함수로 만들어줬는데 이런것들을 좀 다음에 사용해야 할듯
  2. prefix().suffix()를 해줌으로서 2개씩 잘라주는 방식을 학습할 수 있었다.
  3. 사실 가장 큰 차이는 containsInvalidCharacter 일 것이다.
func containsInvalidCharacter(_ text: String) -> Bool {
    let allowedCharacterSet = CharacterSet.letters
    return text.unicodeScalars.contains { !allowedCharacterSet.contains($0) }
}

CharacterSet 공식문서

유니코드 범위를 기반으로 문자 그룹을 정의/ 문자열의 특정 문자들이 특정 그룹에 속하는지 검사할 때 사용하라고 적혀있다.

Returns a character set containing the characters in Unicode General Category L and M. 가 무슨 뜻일까?

L*(Letter)

L 카테고리는 "문자(letter)"를 포함하며, 다시 세부적으로 나뉩니다Lu: Uppercase Letter (대
문자, 예: A, B, C)

  • Lo: Other Letter (기타문자(한자같은 문자)

즉, L*는 유니코드에서 대소문자 알파벳과 다른 문자들(예: 한글, 한자 등)을 포함합니다.

M* (Mark)

M 카테고리는 부가 기호(mark)를 포함하며, 문자와 결합하여 동작하는 기호

그니까 즉 문자열 관련된 기호는 가능하면서 원래는 문자만 가능하다는 뜻이다. 사실 [^a-zA-Z]보다는 좀 더 큰 범위였다.

그럼 여기에 이제 N*이 포함된다고 하니, N은 숫자다.

N* (Number): 숫자

유니코드에서 정의된 모든 숫자가 이에 해당한다.

처음에 숫자가 되는지알고 isalphanumerics를 해줬던 것이다 그래서.. 근데 맞기는 했는데 이게 테스트케이스가 요상한걸 던져주지 않아서지, 사실 문제에서 원하는 것은 [^a-zA-Z]이기를 바라는 것 같아서 정규식을 좀 더 공부해야할 필요성을 느끼는 계기가 되었다.

profile
기억보단 기록을

0개의 댓글