독창 마작 검사 표 판단 후 표 고 효율 저 메모리 알고리즘

76892 단어 마작
동료 가 나 에 게 마작 이 승 부 를 판정 하 는 데 효율 적 인 방법 이 있 느 냐 고 물 었 는데, 그 는 그 가 손 에 쓴 세 개의 독창 의 경우 판정 이 6 초 남짓 걸린다 고 말 했다.나 는 당시 에 그 가 순환 34 * 34 * 34( 34 ) 번 을 하고 차례대로 승 부 를 판정 해 야 한다 고 생각 했다. 이것 은 좋 은 방법 이 아니 라 나중에 야 39304 번 의 순환 에 불과 하 다 는 것 을 깨 달 았 다. 이렇게 오래 걸 리 지 않 을 정도 로 문 제 는 그 가 마작 의 승 부 를 판정 하 는 효율 이 약간 낮 다 는 것 이다.세 개의 독창 의 순환 횟수 를 어떻게 최적화 하고 줄 이 는 지 에 대해 서도 내 생각 이 있다. 어쨌든 나 는 그 에 게 실현 을 시도 하 겠 다 고 약속 했다. 본 고 는 관련 내용 을 정리 하 는 것 이다.
내 가 관련 자 료 를 찾 아 보지 않 았 을 때 처음에 나 는 두 가지 생각 이 있 었 다.모든 이 긴 패 를 조합 하여 맵 을 구성 하고 승 패 를 판정 하려 면 표를 검사 하면 됩 니 다. 키 값 은 정렬 하고 연 결 된 string 을 초보 적 으로 구상 합 니 다.
자 료 를 찾 아 보고 Thinkraft 가 대답 하 는 것 이 저 에 게 큰 영향 을 주 었 습 니 다. 왠 지 방법 에 탄복 하지만 효율 이 현저히 향상 되 지 않 았 습 니 다. (여기 서 향상 되 지 않 은 것 이 아니 라 뒤의 테스트 데 이 터 를 참고 할 수 있 습 니 다. 향상 되 는 효율 은 데이터 항목 의 감소 에서 비롯 되 어야 합 니 다) Golang map 에서 알고리즘 을 찾 는 것 이 상당히 효율 적 일 수 있 습 니 다.그럼 에 도 불구 하고 이런 방법 을 사용 하면 메모리 의 점용 을 효과적으로 낮 출 수 있 습 니 다. 제 가 제공 한 소스 코드 를 자세히 보 세 요.
마작 은 모두 34 가지 카드 로 위 키 - mahjong 위 키 - 마작 1 - 9 떡, 1 - 9 개, 1 - 9 만 개, 동, 남, 서, 북, 홍 중, 돈 벌 기, 화이트 보드 (나머지 유형 패 는 본 고의 알고리즘 과 무관 하 므 로 여기 서 토론 하지 않 습 니 다).
var tiles = []byte{
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // Dots
    0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, // Bamboo
    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, // Characters
    0x31, 0x41, 0x51, 0x61, 0x71, 0x81, 0x91, // East South West North Red Green White
}

마작 이 이 기 려 면 반드시 4 조 1 쌍 (본 고 는 다른 이 길 가능성 을 고려 하지 않 는 다. 예 를 들 어 7 쌍, 예 를 들 어 1 봉 / 터치 가 존재 하 는 전제 에서 3 조 1 쌍 이 이 길 수 있다). 이 긴 모든 카드 를 조합 하려 면 모든 쌍 과 모든 조 를 찾 아야 한다.네: 모두 34 쌍 입 니 다. 유형 별로 1 쌍 을 취 할 수 있 습 니 다.그룹: 총 34 + (9 - 2) * 3 조, 유형 당 1 조 를 취 할 수 있 습 니 다. 같은 패 조 는 34 조, 떡, 줄기, 만 유형 당 9 - 2 순서 패 조 는 21 조, 총 55 조 입 니 다.
func findPairs() [][]byte {
    pairs := make([][]byte, 0, len(tiles))

    for _, v := range tiles {
        pair := []byte{v, v}
        pairs = append(pairs, pair)
    }

    return pairs
}

func findGroups() [][]byte {
    groups := make([][]byte, 0, len(tiles)+(9-2)*3)

    // find three identical tiles
    for _, v := range tiles {
        group := []byte{v, v, v}
        groups = append(groups, group)
    }

    // find three sequence tiles
    for i := 2; i < len(tiles); i++ {
        if tiles[i-2]+1 == tiles[i-1] && tiles[i-1] == tiles[i]-1 {
            group := []byte{tiles[i-2], tiles[i-1], tiles[i]}
            groups = append(groups, group)
        }
    }

    return groups
}

쉽게 찾 을 수 있 지만 어떻게 조합 하 느 냐 에 대해 저 는 정신 이 없 었 습 니 다. 문 제 는 55 조 에서 34 조 의 같은 패 팀 이 조합 할 때 같은 1 조 에 한 번 밖 에 나타 나 지 않 았 습 니 다. 그러나 다른 21 조 의 순서 패 팀 은 조합 할 때 같은 1 조 에 최대 4 번 (유 저 는 싸 우 고 싶 지 않 습 니 다!) 이 나 올 수 있 고 효율 이 최고 라 고 생각 했 습 니 다.그러나 같은 목록 에 있 는 팀 은 서로 다른 처 리 를 해 야 한다. 나 는 이 55 조 의 목록 을 두 개의 목록 으로 나 누 어 복잡 도가 급증 하고 싶다.마지막 으로 현 재 는 데이터 준비 단계 이 고 어떤 효율 을 고려 해 최종 적 으로 정확 한 결 과 를 얻 는 것 이 왕도 다.폭력 조합 이면 돼!!
이 함 수 를 통 해 손 패 를 검사 하 는 것 이 효과 적 이 고 직접 정렬 하여 쉽게 이해 할 수 있 습 니 다. 나중에 효과 적 인 손 패 는 조만간 정렬 해 야 한 다 는 것 을 알 게 될 것 입 니 다.
func checkValid(win []byte) bool {
    sort.Sort(byteSlice(win))

    for i := 4; i < len(win); i++ {
        if win[i] == win[i-4] {
            return false
        }
    }

    return true
}

여기 서 명확 한 효율 문제 에 부 딪 힌 것 은 내 가 Golang 표준 창고 의 by tes. Equal () 함 수 를 과대 평가 한 것 이다.composeWin 실행 시간 눈대중 을 1 시간 이상 실행 해 야 합 니 다.그러나 이 를 탓 할 수 없다. 사고 자체 에 문제 가 존재 한다. 조합 결과 가 점점 많아 지면 서 notExist 를 집행 하 는 대가 가 점점 커 질 것 이다.
func notExist(win []byte, wins [][]byte) bool {
    for _, v := range wins {
        if bytes.Equal(win, v) {
            return false
        }
    }

    return true
}

func composeWin(pairs, groups [][]byte) [][]byte {
    wins := make([][]byte, 0, 11498658)

    tmp := make([]byte, 14)
    for _, pair := range pairs {
        for _, group1 := range groups {
            for _, group2 := range groups {
                for _, group3 := range groups {
                    for _, group4 := range groups {
                        copy(tmp, pair)
                        copy(tmp[2:], group1)
                        copy(tmp[5:], group2)
                        copy(tmp[8:], group3)
                        copy(tmp[11:], group4)

                        if checkValid(tmp) && notExist(tmp, wins) {
                            win := make([]byte, 0, 14)
                            win = append(win, tmp...)
                            wins = append(wins, win)
                        }
                    }
                }
            }
        }
    }

    return wins
}

다음 과 같은 방법 을 통 해 같은 이 긴 카드 가 있 는 지 확인 하 는 일 을 골 랑 맵 에 맡 기 고 몇 분 이면 결 과 를 얻 을 수 있 습 니 다.저 는 string 형식 을 사용 하여 map 키 형식 을 만 들 지 않 았 습 니 다. 사실 이 방법 은 string 형식 보다 키 형식 을 만 드 는 것 이 효율 적 이지 않 습 니 다.오히려 코드 를 많이 써 서 복잡 도 를 높 였 고 뒷글 에 테스트 데이터 가 있 을 것 이다.
type twoUint64 struct {
    H uint64 // High
    L uint64 // Low
}

func composeWinEx(pairs, groups [][]byte) map[twoUint64][]byte {
    wins := make(map[twoUint64][]byte)

    var key twoUint64
    tmp := make([]byte, 14)
    for _, pair := range pairs {
        for _, group1 := range groups {
            for _, group2 := range groups {
                for _, group3 := range groups {
                    for _, group4 := range groups {
                        copy(tmp, pair)
                        copy(tmp[2:], group1)
                        copy(tmp[5:], group2)
                        copy(tmp[8:], group3)
                        copy(tmp[11:], group4)

                        if checkValid(tmp) {
                            key.H = uint64(tmp[0])
                            key.L = uint64(tmp[6])

                            for _, v := range tmp[1:6] {
                                key.H = key.H<<8 + uint64(v)
                            }

                            for _, v := range tmp[7:] {
                                key.L = key.L<<8 + uint64(v)
                            }

                            if _, ok := wins[key]; !ok {
                                win := make([]byte, 0, 14)
                                win = append(win, tmp...)
                                wins[key] = win
                            }
                        }
                    }
                }
            }
        }
    }

    return wins
}

다음은 Thinkraft 가 제시 한 일본 인의 알고리즘 을 설명 합 니 다. 독자 들 은 Thinkraft 의 대답 과 일본 인 이 발표 한 글 을 최대한 읽 어 주 십시오. 저 는 이해 하기 어 려 운 부분 에 만 보충 합 니 다.
이 긴 패 를 판정 할 때 는 두 가 지 를 주의해 야 한다.
예 를 들 어 1, 1, 2, 2, 2, 3, 3, 4, 4, 5, 5, 3, 4, 2, 3, 3, 3, 4, 4, 4, 5, 5 순 위 를 매 긴 두 조수 카드 가 모두 이 기 는 것 을 보 여 준다. 이들 은 매우 비슷 한 지, 이런 이 기 는 유형 을 어떻게 요약 하 는 지, Thinkraft 는 이 를 패 형 이 라 고 부른다.
같은 패 의 수 를 계산 하고 연속 하면 같은 패 의 수 를 계속 계산 하 며, 연속 되 지 않 으 면 중간 에 숫자 0 으로 나 누 기 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 - > 3 4 4 (2 3 연속) 1 1 1 1 1 2 2 2 2 2 2 2 3 3 4 4 4 4 4 - > 3 4 4 3 4 4 4 4 3 (3 4 연속) 동 리 를 얻 을 수 있다.
1 1 1 2 3 4 6 7 8 동서 서 1 1 1 1 1 1 - > 3 1 1 1 2 - > 3 1 (1 2 연속) 1 1 2 3 - > 3 1 1 1 (2 3 연속) 1 1 1 2 3 4 - > 3 1 1 1 2 3 4 6 - > 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 3 4 6 7 - > 31, 1, 2, 3, 4, 6, 7, 8 동 동 동 동 - > 3, 1, 1, 0, 1, 1, 1, 2, 3, 3, 4, 6, 7, 8 동 동 동서 서 - > 3, 1, 1, 1, 1, 1, 1, 1, 0, 3, 3, 3, 1, 1, 0, 3, 3, 1, 1, 1, 0, 3
동 리 는 1, 2, 3, 5, 6, 7, 1, 2, 3, 5, 6, 7, 서 - > 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 2.
다음은 이 진 화 를 다음 과 같은 규칙 으로 1 - > 0 2 - > 1, 1, 0, 3 - > 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10 - > 1, 1, 1, 0, 30 - > 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1숫자 1, 2, 3, 4 는 각각 같은 패 의 수량 을 나타 낸다. 예 를 들 어 규칙 에서 4 또는 40 은 같은 패 4 장 (이 같은 패 와 뒤의 연속 만 구별 되 는 지) 을 나타 내 고 인 코딩 후의 길 이 는 각각 7 비트 (1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7 / 4 = 1.75, 8 / 4 = 2 이기 때문에 카드 당 1 ~ 2 비트 의 바 이 너 리 공간 만 차지한다.
Thinkraft 의 대답 댓 글 에서 개 선 된 호 프 만 인 코딩 이 라 고 생각 하 는 사람 이 있 습 니 다. 호 프 만 인 코딩 을 배 워 보 겠 습 니 다.위 키 - Huffman 위 키 호 프 만 이 호 프 만 인 코딩 에 따라 인 코딩 을 한다 면 오히려 14 장의 카드 데 이 터 를 int 32 에 저장 하 는 것 을 보장 할 수 없습니다. 여기 서 추론 합 니 다.
비로소 같은 패 의 수량 을 계산 하고 연속적으로 0 으로 분할 하지 않 으 면 모두 0, 1, 2, 3, 4 다섯 가지 문자 가 나타난다. 대략적인 통계 출현 횟수 는 다음 과 같다 [4: 2755728 2: 14386266 3: 26038905 0: 34871796 1: 43069053].호 프 만 인 코딩 4 - > 0 0 0 2 - > 0 0 1 3 - > 0 1 0 - > 1 0 1 - > 1 1 을 받 게 됩 니 다.
그 다음 에 이 부분 은 원작 자의 알고리즘 에서 약간 벗 어 났 습 니 다. 주로 저 는 일본 어 를 읽 지 못 하고 원작 자의 처리 에 대해 잘 이해 하지 못 했 습 니 다. 마침 Thinkraft 는 여기 서 자세히 말 하지 않 았 습 니 다. 저 는 Thinkraft 대답 에 댓 글 을 추가 하여 제 의문 을 설 명 했 습 니 다. 관심 이 있 는 친 구 는 가서 볼 수 있 습 니 다. 제 아 이 디: 장성 초, 제3 0 조 댓 글 입 니 다.
사실 이론 은 이미 명확 하 게 밝 혀 졌 다. 원작 자가 어떻게 생각 하 든내 가 이렇게 바 이 너 리 로 돌 리 는 건 항상 틀 리 지 않 아. 2, 2, 3, 3, 4, 5, 5. - > 3, 4, 4, 3, 3, 3. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1 1 1 2 3 4 6 7 8 동 동서 서 - > 3 1 1 1 0 1 1 1 1 1 1 1 1 1 1 0 3 2 - > 1 1 1 0 0 0 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0 1 1 0 1 1 0 2 - > 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0 1 0 0 1 0 1 0 0 0 0 0 1 0 1 0 1 0 1 0 0 1 0 0 1 0 0 0 1 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 1고위 보 1 은 더 나 아가 모든 효과 적 인 바 이 너 리 위 치 를 유지 합 니 다. 예 를 들 어 프로그램 이 이상 하면 [2, 3, 5, 6, 7 서쪽] 으로 int 32 함 수 를 호출 하면 얻 은 int 32 수 치 는 위의 것 과 일치 하여 프로그램 이 실 수 를 판정 할 것 입 니 다.
다음은 제 가 실현 한 위 에서 말 한 논 리 는 논 리 를 간소화 하기 위해 저 는 0 으로 분할 하지 않 았 습 니 다. 저 는 분할 할 곳 의 숫자 가 직접 + 10 입 니 다. 그러면 원작 자 는 저 와 다음 과 같 습 니 다. 1 - > 1 (0x 01) 2 - > 2 (0x 02) 3 - > 3 (0x 03) 4 - > 4 (0x04) 10 - > 11 (0x0B) 20 - > 12 (0x0C) 30 - > 13 (0x0D) 40 - > 14 (0x0E)이렇게 하면 뒤의 switch 논 리 를 편리 하 게 하고 선명 하 게 할 수 있 습 니 다.
func bytesToInt(win []byte) int {
    tmp := make([]byte, 0, 17)
    tmp = append(tmp, 1)
    for i, pos := 1, 0; i < len(win); i++ {
        if win[i-1] == win[i] {
            tmp[pos]++
        } else if win[i-1]+1 == win[i] {
            tmp = append(tmp, 1)
            pos++
        } else {
            tmp = append(tmp, 1)
            tmp[pos] += 0x0A
            pos++
        }
    }

    res := 1
    for _, v := range tmp {
        switch v {
        case 0x01:
            res <<= 1
        case 0x02:
            res <<= 3
            res |= 0x06
        case 0x03:
            res <<= 5
            res |= 0x1E
        case 0x04:
            res <<= 7
            res |= 0x7E
        case 0x0B:
            res <<= 2
            res |= 0x02
        case 0x0C:
            res <<= 4
            res |= 0x0E
        case 0x0D:
            res <<= 6
            res |= 0x3E
        case 0x0E:
            res <<= 8
            res |= 0xFE
        }
    }

    return res
}

다음은 스트레스 테스트 결 과 를 보 여 드 리 겠 습 니 다. 테스트 환경 을 걱정 하지 마 세 요. 기본 적 인 랜 덤 피 드 는 똑 같은 카드 기준 1000000 번 과 세 개의 독창 자 1000 번 의 승 패 를 거 쳤 습 니 다. 이 기 는 횟수 를 통계 하고 시간 int 알고리즘 을 통계 할 것 입 니 다.
Test total 10000000, Win 30, Time 59.662091651s
Test total 1000, Win 8, Time 20.836001166s

two uint 64 알고리즘
Test total 10000000, Win 30, Time 1m22.517894824s
Test total 1000, Win 8, Time 24.289389045s

string 알고리즘
Test total 10000000, Win 30, Time 1m26.392626271s
Test total 1000, Win 8, Time 24.320570688s

이때 그것들의 효율 은 이미 차이 가 매우 적다. 네가 어떻게 사용 하 느 냐 에 달 려 있다. 여기 서 한 가 지 를 제시한다. 어쨌든 int 알고리즘 은 메모리 에서 가장 적은 알고리즘 을 차지한다. 알고리즘 을 사용 하지 않 고 int 로 전환 할 때 메모 리 를 약 64 * 2 * 11, 498, 658 = 1, 471, 828, 224 (175 M) 를 차지한다. 그러나 int 로 전환 할 때 메모 리 를 약 32 * 8185 = 261, 920 (32K) 을 차지한다. 차 이 는 바로 여기에 있다.
세 개의 독창 상황 에서 순환 횟수 를 어떻게 효과적으로 줄 일 수 있 는 지 저 는 이렇게 생각 합 니 다. 위 에서 언급 한 두 가 지 를 빌려 서 사용 합 니 다. 똑 같이 해 야 합 니 다. 이 연속 적 인 것 은 연속 적 으로 해 야 합 니 다. 독창 은 이미 존재 하 는 카드 나 이미 존재 하 는 카드 와 연속 적 인 카드 로 교체 하 는 것 이 가장 좋 습 니 다!세심 한 사람 이 이런 걱정 을 할 수 있 습 니 다. 세 개의 독창 자 는 원래 한 조로 바 꿀 수 있 습 니 다. 이미 존재 하 는 패 와 다 르 고 이미 존재 하 는 패 와 연속 되 지 않 습 니 다. 저 는 증명 할 수 없 지만 이것 은 걱정 이 많 을 것 입 니 다. 왜냐하면 당신 은 서로 다른 비 연속 으로 독창 자 를 처리 해도 이 길 수 있 기 때 문 입 니 다. 똑 같이 연속 으로 독창 자 를 처리 하면 이미 이 겼 습 니 다. 몇 가지 예 를 들 어 테스트 해 보 세 요.
func availableTiles(win []byte) map[byte]bool {
    available := make(map[byte]bool)

    for _, v := range win {
        if v > 0x01 && v < 0x09 || v > 0x11 && v < 0x19 || v > 0x21 && v < 0x29 {
            available[v-1], available[v+1] = true, true
        }

        available[v] = true
    }

    return available
}

func benchmarkWinEx3Ex(n int, wins map[string][]byte) {
    var win int
    now := time.Now()
    for i := 0; i < n; i++ {
        perm := rand.Perm(136)
        hand := make([]byte, 14)

        for j := 0; j < 14; j++ {
            hand[j] = full[perm[j]]
        }

    EXIT:
        for v1 := range availableTiles(hand[3:]) {
            hand[2] = v1
            for v2 := range availableTiles(hand[2:]) {
                hand[1] = v2
                for v3 := range availableTiles(hand[1:]) {
                    hand[0] = v3

                    tmp := make([]byte, 0, 14)
                    tmp = append(tmp, hand...)

                    if checkValid(tmp) {
                        if _, ok := wins[string(tmp)]; ok {
                            win++
                            break EXIT
                        }
                    }
                }
            }
        }
    }

    fmt.Printf("Test total %d, Win %d, Time %v
"
, n, win, time.Since(now)) }

사실 위의 논 리 는 여전히 최적화 할 수 있다. 나 비 를 교체 한 후에 효과 가 있 는 지 다시 검사 하지 않 아 도 된다. 그러나 효율 적 인 측면 에서 오 르 지 않 고 오히려 떨어진다. 왜냐하면 무 작위 로 나 온 손 패 는 매우 복잡 하기 때문에 교체 할 수 없 는 패 를 촉발 할 수 있 는 상황 이 매우 적다.
func appendAvailableTiles(origin map[byte]int, ap ...byte) map[byte]int {
    available := make(map[byte]int)

    for k, v := range origin {
        available[k] = v
    }

    for _, v := range ap {
        if v > 0x01 && v < 0x09 || v > 0x11 && v < 0x19 || v > 0x21 && v < 0x29 {
            if _, ok := available[v-1]; !ok {
                available[v-1] = 0
            }

            if _, ok := available[v+1]; !ok {
                available[v+1] = 0
            }
        }

        available[v]++
    }

    return available
}

func benchmarkWinEx3Ex2(n int, wins map[string][]byte) {
    var win int
    now := time.Now()
    for i := 0; i < n; i++ {
        perm := rand.Perm(136)
        hand := make([]byte, 14)

        for j := 0; j < 14; j++ {
            hand[j] = full[perm[j]]
        }

        available1 := appendAvailableTiles(nil, hand[3:]...)

    EXIT:
        for v1 := range available1 {
            if available1[v1] >= 4 {
                continue
            }
            available2 := appendAvailableTiles(available1, v1)
            for v2 := range available2 {
                if available2[v2] >= 4 {
                    continue
                }
                available3 := appendAvailableTiles(available2, v2)
                for v3 := range available3 {
                    if available3[v3] >= 4 {
                        continue
                    }
                    tmp := make([]byte, 0, 14)
                    tmp = append(tmp, v1, v2, v3)
                    tmp = append(tmp, hand[3:]...)

                    sort.Sort(byteSlice(tmp))

                    if _, ok := wins[string(tmp)]; ok {
                        win++
                        break EXIT
                    }
                }
            }
        }
    }

    fmt.Printf("Test total %d, Win %d, Time %v
"
, n, win, time.Since(now)) }

낡은 방법 과 효과 적 인 방법 을 검증 해 야 하 는 효율 을 비교 하여 4 배 올 렸 다.
Test total 10000, Win 56, Time 3m59.80354974s
Test total 10000, Win 56, Time 54.301180241s

두 가지 새로운 방법 은 효율 을 대비 하여, 오 르 지 않 으 면 오히려 떨어진다.
Test total 50000, Win 277, Time 4m34.589149569s
Test total 50000, Win 277, Time 5m18.078941139s

모든 원본 코드
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "math/rand"
    "os"
    "sort"
    "time"
)

var tiles = []byte{
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // Dots
    0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, // Bamboo
    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, // Characters
    0x31, 0x41, 0x51, 0x61, 0x71, 0x81, 0x91, // East South West North Red Green White
}

var full = []byte{
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // Dots
    0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, // Bamboo
    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, // Characters
    0x31, 0x41, 0x51, 0x61, 0x71, 0x81, 0x91, // East South West North Red Green White
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // Dots
    0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, // Bamboo
    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, // Characters
    0x31, 0x41, 0x51, 0x61, 0x71, 0x81, 0x91, // East South West North Red Green White
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // Dots
    0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, // Bamboo
    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, // Characters
    0x31, 0x41, 0x51, 0x61, 0x71, 0x81, 0x91, // East South West North Red Green White
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // Dots
    0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, // Bamboo
    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, // Characters
    0x31, 0x41, 0x51, 0x61, 0x71, 0x81, 0x91, // East South West North Red Green White
}

func findPairs() [][]byte {
    pairs := make([][]byte, 0, len(tiles))

    for _, v := range tiles {
        pair := []byte{v, v}
        pairs = append(pairs, pair)
    }

    return pairs
}

func findGroups() [][]byte {
    groups := make([][]byte, 0, len(tiles)+(9-2)*3)

    // find three identical tiles
    for _, v := range tiles {
        group := []byte{v, v, v}
        groups = append(groups, group)
    }

    // find three sequence tiles
    for i := 2; i < len(tiles); i++ {
        if tiles[i-2]+1 == tiles[i-1] && tiles[i-1] == tiles[i]-1 {
            group := []byte{tiles[i-2], tiles[i-1], tiles[i]}
            groups = append(groups, group)
        }
    }

    return groups
}

type byteSlice []byte

func (b byteSlice) Len() int {
    return len(b)
}

func (b byteSlice) Less(i, j int) bool {
    return b[i] < b[j]
}

func (b byteSlice) Swap(i, j int) {
    b[i], b[j] = b[j], b[i]
}

func checkValid(win []byte) bool {
    sort.Sort(byteSlice(win))

    for i := 4; i < len(win); i++ {
        if win[i] == win[i-4] {
            return false
        }
    }

    return true
}

func notExist(win []byte, wins [][]byte) bool {
    for _, v := range wins {
        if bytes.Equal(win, v) {
            return false
        }
    }

    return true
}

func composeWin(pairs, groups [][]byte) [][]byte {
    wins := make([][]byte, 0, 11498658)

    tmp := make([]byte, 14)
    for _, pair := range pairs {
        for _, group1 := range groups {
            for _, group2 := range groups {
                for _, group3 := range groups {
                    for _, group4 := range groups {
                        copy(tmp, pair)
                        copy(tmp[2:], group1)
                        copy(tmp[5:], group2)
                        copy(tmp[8:], group3)
                        copy(tmp[11:], group4)

                        if checkValid(tmp) && notExist(tmp, wins) {
                            win := make([]byte, 0, 14)
                            win = append(win, tmp...)
                            wins = append(wins, win)
                        }
                    }
                }
            }
        }
    }

    return wins
}

type twoUint64 struct {
    H uint64 // High
    L uint64 // Low
}

func composeWinEx(pairs, groups [][]byte) map[twoUint64][]byte {
    wins := make(map[twoUint64][]byte)

    var key twoUint64
    tmp := make([]byte, 14)
    for _, pair := range pairs {
        for _, group1 := range groups {
            for _, group2 := range groups {
                for _, group3 := range groups {
                    for _, group4 := range groups {
                        copy(tmp, pair)
                        copy(tmp[2:], group1)
                        copy(tmp[5:], group2)
                        copy(tmp[8:], group3)
                        copy(tmp[11:], group4)

                        if checkValid(tmp) {
                            key.H = uint64(tmp[0])
                            key.L = uint64(tmp[6])

                            for _, v := range tmp[1:6] {
                                key.H = key.H<<8 + uint64(v)
                            }

                            for _, v := range tmp[7:] {
                                key.L = key.L<<8 + uint64(v)
                            }

                            if _, ok := wins[key]; !ok {
                                win := make([]byte, 0, 14)
                                win = append(win, tmp...)
                                wins[key] = win
                            }
                        }
                    }
                }
            }
        }
    }

    return wins
}

type jsonData struct {
    H uint64 // High
    L uint64 // Low
    V []byte // Value
}

func toJSON() {
    pairs := findPairs()

    groups := findGroups()

    wins := composeWinEx(pairs, groups)

    f, err := os.Create("json.data")
    defer f.Close()

    if err != nil {
        log.Fatal("Create", err)
    }

    var jd jsonData

    enc := json.NewEncoder(f)

    for k, v := range wins {
        jd.H = k.H
        jd.L = k.L
        jd.V = v

        if err := enc.Encode(jd); err != nil {
            log.Fatal("Encode", err)
        }
    }
}

func benchmarkWin(n int, wins map[twoUint64][]byte) {
    var win int
    var key twoUint64
    now := time.Now()
    for i := 0; i < n; i++ {
        perm := rand.Perm(136)
        hand := make([]byte, 14)

        for j := 0; j < 14; j++ {
            hand[j] = full[perm[j]]
        }

        sort.Sort(byteSlice(hand))

        key.H = uint64(hand[0])
        key.L = uint64(hand[6])

        for _, v := range hand[1:6] {
            key.H = key.H<<8 + uint64(v)
        }

        for _, v := range hand[7:] {
            key.L = key.L<<8 + uint64(v)
        }

        if _, ok := wins[key]; ok {
            win++
        }
    }

    fmt.Printf("Test total %d, Win %d, Time %v
"
, n, win, time.Since(now)) } func benchmarkWinEx(n int, wins map[twoUint64][]byte) { var win int var key twoUint64 now := time.Now() for i := 0; i < n; i++ { perm := rand.Perm(136) hand := make([]byte, 14) for j := 0; j < 14; j++ { hand[j] = full[perm[j]] } EXIT: for _, v1 := range tiles { for _, v2 := range tiles { for _, v3 := range tiles { tmp := make([]byte, 0, 14) tmp = append(tmp, v1, v2, v3) tmp = append(tmp, hand[3:]...) if checkValid(tmp) { key.H = uint64(tmp[0]) key.L = uint64(tmp[6]) for _, v := range tmp[1:6] { key.H = key.H<<8 + uint64(v) } for _, v := range tmp[7:] { key.L = key.L<<8 + uint64(v) } if _, ok := wins[key]; ok { win++ break EXIT } } } } } } fmt.Printf("Test total %d, Win %d, Time %v
"
, n, win, time.Since(now)) } type simpleData struct { K int // Key V []byte // Value } func bytesToInt(win []byte) int { tmp := make([]byte, 0, 17) tmp = append(tmp, 1) for i, pos := 1, 0; i < len(win); i++ { if win[i-1] == win[i] { tmp[pos]++ } else if win[i-1]+1 == win[i] { tmp = append(tmp, 1) pos++ } else { tmp = append(tmp, 1) tmp[pos] += 0x0A pos++ } } res := 1 for _, v := range tmp { switch v { case 0x01: res <<= 1 case 0x02: res <<= 3 res |= 0x06 case 0x03: res <<= 5 res |= 0x1E case 0x04: res <<= 7 res |= 0x7E case 0x0B: res <<= 2 res |= 0x02 case 0x0C: res <<= 4 res |= 0x0E case 0x0D: res <<= 6 res |= 0x3E case 0x0E: res <<= 8 res |= 0xFE } } return res } func toSimple(wins map[twoUint64][]byte) { f, err := os.Create("simple.data") defer f.Close() if err != nil { log.Fatal("Create", err) } var sd simpleData enc := json.NewEncoder(f) for _, win := range wins { sd.K = bytesToInt(win) sd.V = win if err := enc.Encode(sd); err != nil { log.Fatal("Encode", err) } } } func fromSimple() { f, err := os.Open("simple.data") defer f.Close() if err != nil { log.Fatal("Open", err) } var sd simpleData dec := json.NewDecoder(f) wins := make(map[int]bool) for { if err := dec.Decode(&sd); err == io.EOF { break } else if err != nil { log.Fatal("Decode", err) } wins[sd.K] = true } benchmarkWin2(10000000, wins) benchmarkWinEx2(1000, wins) } func benchmarkWin2(n int, wins map[int]bool) { var win int now := time.Now() for i := 0; i < n; i++ { perm := rand.Perm(136) hand := make([]byte, 14) for j := 0; j < 14; j++ { hand[j] = full[perm[j]] } sort.Sort(byteSlice(hand)) if _, ok := wins[bytesToInt(hand)]; ok { win++ } } fmt.Printf("Test total %d, Win %d, Time %v
"
, n, win, time.Since(now)) } func benchmarkWinEx2(n int, wins map[int]bool) { var win int now := time.Now() for i := 0; i < n; i++ { perm := rand.Perm(136) hand := make([]byte, 14) for j := 0; j < 14; j++ { hand[j] = full[perm[j]] } EXIT: for _, v1 := range tiles { for _, v2 := range tiles { for _, v3 := range tiles { tmp := make([]byte, 0, 14) tmp = append(tmp, v1, v2, v3) tmp = append(tmp, hand[3:]...) if checkValid(tmp) { if _, ok := wins[bytesToInt(tmp)]; ok { win++ break EXIT } } } } } } fmt.Printf("Test total %d, Win %d, Time %v
"
, n, win, time.Since(now)) } func benchmarkWin3(n int, wins map[twoUint64][]byte) { winsCopy := make(map[string][]byte) for _, v := range wins { winsCopy[string(v)] = v } var win int now := time.Now() for i := 0; i < n; i++ { perm := rand.Perm(136) hand := make([]byte, 14) for j := 0; j < 14; j++ { hand[j] = full[perm[j]] } sort.Sort(byteSlice(hand)) if _, ok := winsCopy[string(hand)]; ok { win++ } } fmt.Printf("Test total %d, Win %d, Time %v
"
, n, win, time.Since(now)) benchmarkWinEx3(1000, winsCopy) } func benchmarkWinEx3(n int, wins map[string][]byte) { var win int now := time.Now() for i := 0; i < n; i++ { perm := rand.Perm(136) hand := make([]byte, 14) for j := 0; j < 14; j++ { hand[j] = full[perm[j]] } EXIT: for _, v1 := range tiles { for _, v2 := range tiles { for _, v3 := range tiles { tmp := make([]byte, 0, 14) tmp = append(tmp, v1, v2, v3) tmp = append(tmp, hand[3:]...) if checkValid(tmp) { if _, ok := wins[string(tmp)]; ok { win++ break EXIT } } } } } } fmt.Printf("Test total %d, Win %d, Time %v
"
, n, win, time.Since(now)) } func availableTiles(win []byte) map[byte]bool { available := make(map[byte]bool) for _, v := range win { if v > 0x01 && v < 0x09 || v > 0x11 && v < 0x19 || v > 0x21 && v < 0x29 { available[v-1], available[v+1] = true, true } available[v] = true } return available } func benchmarkWinEx3Ex(n int, wins map[string][]byte) { var win int now := time.Now() for i := 0; i < n; i++ { perm := rand.Perm(136) hand := make([]byte, 14) for j := 0; j < 14; j++ { hand[j] = full[perm[j]] } EXIT: for v1 := range availableTiles(hand[3:]) { hand[2] = v1 for v2 := range availableTiles(hand[2:]) { hand[1] = v2 for v3 := range availableTiles(hand[1:]) { hand[0] = v3 tmp := make([]byte, 0, 14) tmp = append(tmp, hand...) if checkValid(tmp) { if _, ok := wins[string(tmp)]; ok { win++ break EXIT } } } } } } fmt.Printf("Test total %d, Win %d, Time %v
"
, n, win, time.Since(now)) } func appendAvailableTiles(origin map[byte]int, ap ...byte) map[byte]int { available := make(map[byte]int) for k, v := range origin { available[k] = v } for _, v := range ap { if v > 0x01 && v < 0x09 || v > 0x11 && v < 0x19 || v > 0x21 && v < 0x29 { if _, ok := available[v-1]; !ok { available[v-1] = 0 } if _, ok := available[v+1]; !ok { available[v+1] = 0 } } available[v]++ } return available } func benchmarkWinEx3Ex2(n int, wins map[string][]byte) { var win int now := time.Now() for i := 0; i < n; i++ { perm := rand.Perm(136) hand := make([]byte, 14) for j := 0; j < 14; j++ { hand[j] = full[perm[j]] } available1 := appendAvailableTiles(nil, hand[3:]...) EXIT: for v1 := range available1 { if available1[v1] >= 4 { continue } available2 := appendAvailableTiles(available1, v1) for v2 := range available2 { if available2[v2] >= 4 { continue } available3 := appendAvailableTiles(available2, v2) for v3 := range available3 { if available3[v3] >= 4 { continue } tmp := make([]byte, 0, 14) tmp = append(tmp, v1, v2, v3) tmp = append(tmp, hand[3:]...) sort.Sort(byteSlice(tmp)) if _, ok := wins[string(tmp)]; ok { win++ break EXIT } } } } } fmt.Printf("Test total %d, Win %d, Time %v
"
, n, win, time.Since(now)) } func main() { // fromSimple() // return f, err := os.Open("json.data") defer f.Close() if err != nil { log.Fatal("Open", err) } var jd jsonData dec := json.NewDecoder(f) wins := make(map[twoUint64][]byte) for { if err := dec.Decode(&jd); err == io.EOF { break } else if err != nil { log.Fatal("Decode", err) } wins[twoUint64{ H: jd.H, L: jd.L, }] = jd.V } benchmarkWin(10000000, wins) benchmarkWinEx(1000, wins) //benchmarkWin3(10000000, wins) }

좋은 웹페이지 즐겨찾기