모든 * 완벽한 영어 팬 그램
An 영어 판 그램은 영어 알파벳 26자를 모두 포함하는 문장입니다. 가장 잘 알려진 영어 팬 그램은 아마도 “The quick brown fox jumps over the lazy dog”일 것입니다. 제가 가장 좋아하는 팬 그램은 “놀랍게도 주크 박스를 제공하는 디스코텍이 거의 없습니다.”입니다.
완벽한 팬 그램은 각 글자가있는 팬 그램입니다. 한 번만 나타납니다. 온라인에서 알려진 완벽한 판 그램을 나열한 소스를 찾았습니다. 모두 다 만들어 내려고 노력한 사람은 아무도없는 것 같아서 재미있는 도전이라고 생각했습니다. 이것이 제가 완벽한 영어 판 그램을 모두 찾은 방법입니다. 별표는 나중에 설명하겠습니다.
- Crwth vox zaps qi gym fjeld bunk. (셀틱 바이올린의 소리가 스칸디나비아의 황량한 고원에 위치한 동부의 영적 힘 중심의 피트니스 센터를 강타합니다.) 이것은 모두 스크래블 법률 단어입니다!
- Squdgy kilp job zarf nth cwm vex. (잘못 형성된 다시마는 계곡이나 산 꼭대기에있는 많은 반쯤 열려있는 가파른 쪽의 구멍 중 하나가 자극받은 장식용 컵 워머를 구입합니다.)
- Jock nymphs waqf drug vex blitz. (자선 기부금은 공격에 가담 한 운동 선수를 좌절시킨 숲의 영혼을 취했습니다.)
- 흠, 피오르드 왈츠, cinq busk, pyx veg. (보자, 길고 좁고 깊은 입구가 춤을 추고, 주사위의 다섯 개가 길거리에서 음악을 만들고, 병 들고 쉬지 못하는 사람들을위한 작은 둥근 용기.) 또한 스크래블 합법적이지만 감탄사 (음)가 있습니다.
불행히도 다음은 제가 찾을 수있는 가장 읽기 쉬운 문장입니다 *. 감탄사없는 스크래블 용 공식 토너먼트 및 클럽 단어 목록 3 (OWL3)에서 생성 된 모든 완벽한 팬 그램에는 cwm 또는 crwth라는 단어가 포함됩니다. Waqf는 북미 이외의 지역에서 합법적 인 Scrabble 토너먼트입니다.
완벽한 팬 그램을 모두 찾는 방법
완벽한 팬 그램을 찾는 방법은 두 단계로 나뉩니다. 첫 번째는 영어 알파벳의 각 문자를 한 번 포함하는 모든 단어 집합을 찾는 것입니다. 두 번째 단계는 유효한 영어 문장으로 재 배열 할 수있는 집합을 확인하는 것입니다.
1 단계 : 완벽한 판 그램을위한 단어 집합 찾기
영어 알파벳에 걸쳐 영어 단어 목록이 필요합니다. 양질의 단어 목록을 찾고 유지하는 것은 내가 예상했던 것보다 훨씬 어려웠습니다. 원래이 프로젝트는 이틀이 걸릴 거라고 생각했지만 데이터 품질 문제로 2 주가 걸렸습니다.
무료로 사용할 수있는 영어 단어 목록 인 Unix 사전으로 시작했습니다. 거의 모든 Unix 기반 운영 체제와 함께 제공됩니다. 목록에 품질 문제가 있음을 즉시 알았습니다. 첫째, 알파벳의 각 문자는 유닉스 사전에서 단어로 간주되었고 “vejoz”와 같은 많은 비 단어를 포함했습니다. 이것은 온라인에서 찾은 단어 목록을 관리하기 위해 블랙리스트가 필요함을 보여줍니다. 유닉스 사전에는 단어에 대한 복수형이 없기 때문에 사전에는 “오렌지”라는 단어는 포함되지만 “오렌지”는 포함되지 않습니다. 단어 목록이 너무 제한적이므로 이전에 알려진 완벽한 판 그램에는 유닉스 사전의 단어 만 포함되어 있지 않습니다. “squdgy kilp job zarf nth cwm vex”와 같은 일부.
그런 다음 더 큰 단어 집합을 찾기 위해 인터넷을 사용했습니다. 엄청나게 큰 단어 세트를 찾았지만 그 목록에서 완벽한 팬 그램을 찾기 시작했을 때 유효한 영어 단어가 아닌 낮은 품질의 단어로 너무 오염되어 있음을 발견했습니다. 여러 차례 반복 한 후에도 합리적이고 관리 가능한 판 그램을 찾기 위해 목록을 줄이지 못했습니다. 특정 길이의 단어 화이트리스트를 만들어 정리하려고했지만 목록의 품질이 여전히 매우 낮았습니다.
마지막으로 여러 번 반복 한 후 15 달러를 지불하여 북미 지역의 평가판 멤버십을 구입했습니다. Scrabble® Players Association은 일부 논란의 원인이되는 독점적이고 저작권이있는 OWL3에 대한 액세스 권한을 부여했습니다. 그럼에도 불구하고 한글자 단어 “a”와 “I”와 같이 영어로 알려진 일부 단어를 추가해야했습니다.
적절한 단어 목록으로 무장하여 생성하는 알고리즘을 구현했습니다. 각 영어 알파벳 문자 중 하나를 포함하는 해당 목록의 모든 단어 세트. 아래의 “알고리즘”섹션에서 알고리즘에 대해 자세히 설명하겠습니다.
2 단계 : 단어 모음에서 영어 문장 형성
단어 세트가 주어 졌는지 확인하고 제공된 모든 단어로 유효한 영어 문장이 가능하지만 사소한 문제는 아니지만 대부분의 다른 자연어 처리 (NLP) 문제보다 쉽습니다.
부적격 한 문장을 걸러 내기위한 유용한 휴리스틱이 있습니다. 이러한 휴리스틱을 따라 나머지 단어에서 유효한 영어 문장을 만들 수있었습니다. 문장은 종종 무의미하지만 여전히 유효합니다. 내가 사용한 휴리스틱은 다음과 같습니다.
- 동사가 하나 이상 있어야합니다.
- 접속 사나 연결 사가 없으면 동사보다 명사는 하나만 더있을 수 있습니다. 둘 다 매우 드문 전치사입니다.
- 형용사가 있으면 명사도 있어야합니다.
암시 적 가능성 때문에 휴리스틱이 부분적으로 작동합니다. 주어 (완벽도 팬 그램도 아니지만 “조용히 움직이고 부드럽게 말하십시오”는 두 개의 동사와 명사가 포함 된 문장이며 “you”라는 암시 적 주어가 있습니다.)
완벽한 팬 그램에 참여하는 것은 작을 수 있습니다. 각 개별 단어에 적격 한 품사를 수동으로 태그하고 단어 세트가이 세 가지 간단한 휴리스틱을 따르는 지 확인하는 것은 쉽습니다. 생성 된 문장의 품질이 마음에 드는지 여부는 취향의 문제입니다.
알고리즘
이 섹션은 약간 기술적 인 내용이지만 여전히 쉽게 따라 할 수 있습니다. “결과 & 학습”섹션으로 건너 뛰어도됩니다.
높은 수준의 전략
목표는 가능한 모든 세트를 생성하는 것입니다. 영어 알파벳을 “완벽하게”포함하는 주어진 단어 목록의 단어.
- 검색 공간을 크게 줄이려면 단어 목록을 정리합니다. “문자”와 같이 문자가 반복되는 단어를 제거합니다.
- 비트 마스크를 사용하여 단어를 효율적으로 표현하고 원래 단어 집합에 다시 매핑합니다.
- 가능한 모든 상태를 검색합니다. 각각 비트 마스크 목록을 반복적으로 반복하여 가능한 문자 조합을 나타냅니다. 동적 프로그래밍을 통해 성능이 크게 향상됩니다.
- 완벽한 팬 그램 상태에서 화살표 (방향 가장자리)를 그립니다. 영문자를 구성한 중개 국에 전달합니다. 중개 상태를 다시 수행하여 완벽한 판 그램이 될 수있는 단어 세트를 재구성 할 수있는 데이터 구조를 만듭니다.이를 역 추적이라고합니다.
- 출력 나무처럼 완벽한 판 그램 일 가능성이있는 단어 세트
목록 정리, 일명 정규화
첫 번째 단계는 원래 단어 목록을 정리하여 검색 공간을 줄이고 출력 품질을 높이는 것입니다.
- 단어 주변의 모든 공백을 제거합니다. 소문자로만 변환하십시오.
- 단어에 영어 알파벳 문자 만 포함되어 있는지 확인하십시오. 간단한 정규식 필터를 사용했습니다.
/^+$/
- 다른 목록에 대해 필터링 (예 : 블랙리스트; 블랙리스트에 단어가 있으면 해당 단어를 건너 뛰십시오.
- 반복되는 문자가있는 모든 단어 제거
이렇게하면 검색 공간이 200,000 ~ 370,000 개 단어 목록에서 훨씬 더 작은 35,000 ~ 65,000 단어.
비트 마스크 사용
비트 마스크는 상태의 정수 표현입니다. 비트 마스크에는 몇 가지 장점이 있습니다.
- 비트 마스크는이 문제를 잘 나타냅니다. 문자 순서는 중요하지 않으므로 모든 단어 조합은 26 자리 길이의 0과 1로 표현 될 수 있으며 각 숫자는 조합에 문자가 있는지 여부를 나타냅니다. 예를 들면. 단어 집합에 문자 “e”가 포함되어 있으면 5 번째 숫자는 1이되고 그렇지 않으면 0이됩니다.
- 비트 마스크가 효율적입니다. 검색 공간이 일정하기 때문에 비트 마스크는 효율적인 저장 공간을 제공하고 가능한 모든 문자 조합의 표현입니다. 또한 비트 연산이 빠릅니다. 두 비트 마스크를 결합하여 더 큰 비트 마스크를 생성 할 수 있는지 테스트하려면 두 마스크의 비트 AND가 0인지 확인하십시오. 빠른 작업.
그러므로 각 단어를 정수로 나타낼 수있는 비트 마스크로 변환합니다. 예를 들어 “cab”이라는 단어는 111의 비트 마스크에 매핑됩니다. 10 진수 7입니다. 단어 “be”는 10 진수 18 인 10010에 매핑됩니다. 가능한 가장 큰 비트 마스크는 알파벳의 모든 문자, 가능한 완벽한 팬 그램 상태 인 11111111111111111111111111입니다. 이는 십진수 67,108,863 또는 2²⁶ -1입니다. 이는 표준 부호있는 32 비트 정수에 잘 맞습니다. 2³¹-1로.
비트 마스크를 사용하면 단일 단어 애너그램이 동일한 비트 마스크에 매핑되므로 공간이 더욱 압축됩니다. “kiln”과 “link”는 모두 십진수 11520 인 마스크 10110100000000에 매핑됩니다. 이렇게하면 35,000 ~ 65,000 단어의 검색 공간이 25,000 ~ 45,000 비트 마스크로 더 줄어 듭니다.
비트 마스크의 매핑을 파생 된 단어 집합으로 다시 유지합니다. 이것은 단어 세트를 출력 할 때 유용합니다.
동적 프로그래밍으로 완벽한 pangram 검색
알고리즘의 핵심은 매우 간단합니다.
가능한 상태 (기존 단어의 유효한 조합으로 구성됨)가 주어지면 초기 단어 목록의 모든 마스크를 시도하여 새 유효한 상태를 생성 할 수 있는지 확인합니다 (비트 AND 상태와 마스크는 0과 같으며 겹치는 문자가 없음을 의미합니다.) 모든 1을 병합하는 비트 OR 연산을 사용하여 새 상태를 만듭니다. 발견 된 각 새 상태에 대해 더 이상 탐색되지 않은 상태가 없을 때까지 반복하십시오. 이것이 끝에 도달하면 알고리즘이 가능한 완벽한 팬 그램 단어 세트를 하나 이상 찾았 음을 의미합니다. 가능한 모든 상태를 열거 할 수있는 첫 번째 가능한 상태는 빈 상태 또는 알파벳 문자가 포함되지 않은 0입니다. 그러니 거기에서 시작하여 어떤 상태가 가능한지 재귀 적으로 발견하십시오.
간헐적 상태에 도달하는 방법은 여러 가지가 있고 상태에 대한 작업은 그 상태에 따라 변경되지 않는다는 것을 알아 차리는 것입니다. 도달했습니다. 따라서 상태가 재검토 될 때 작업을 반복하는 대신 각 상태의 결과를 저장하십시오. 이 기술을 동적 프로그래밍이라고하며 복잡한 조합 문제를 선형 프로그램으로 전환합니다. 간헐적 인 상태를 저장하는 과정을 메모 화라고합니다.
그러므로 0에서 67,108,863 사이의 2²⁶ 크기의 배열을 만듭니다. 각 인덱스는 앞에서 설명한 비트 마스크 상태를 나타냅니다. 배열의 각 인덱스에있는 값은 상태에 대해 알려진 것을 나타냅니다. 0은 상태가 변경되지 않았거나 도달 할 수 없음을 의미합니다. 1은 주가 가능한 완벽한 판 그램 상태에 도달 할 수있는 방법을 찾았 음을 의미합니다. -1은 상태가 끝까지 도달하는 방법을 찾지 못했음을 의미합니다.
아래 의사 코드 :
Interlude : 복잡성과 실용적인 런타임 분석
2²⁶ 일련의 26 비트에 대해 가능한 비트 마스크. 각 상태는 메모 화로 인해 한 번만 처리되기 때문에이 알고리즘의 실행 시간은 O (n 2 ^ d)입니다. 여기서 d는 알파벳 크기 26입니다. 변수 n은 단어 수를 나타내지 않고 비트 마스크 수. 67,108,863 개와 약 45,000 비트 마스크를 사용하면 약 3 조에 달합니다. MacBook Pro는 약 45 분만에 처리 할 수 있습니다. 모든 최신 컴퓨터에서 다루기 쉽습니다. 또한 재귀 호출 스택이 26보다 깊어지지 않으므로 (15보다 깊어지지 않을 가능성이 높음) 해당 차원에서도 매우 관리 할 수 있습니다.
비트 마스크 접근 방식의 한 가지 장점은 다음과 같습니다. 2²⁶ 상태 만 있으면 모든 상태가 메모리에 저장 될 수 있습니다. 상태 (-1, 0, 1) 당 3 개의 값만 있기 때문에 단일 바이트에 저장할 수 있습니다. 상태 당 단일 바이트에서 2²⁶ 상태는 약 67 메가 바이트로 나오며 이는 다시 매우 관리하기 쉽습니다.
하지만 알파벳이 증가하면 검색 공간이 기하 급수적으로 증가하고 런타임도 증가합니다. 매우 빨리 다루기 어려워지는 문제. 더 큰 알파벳을위한 완벽한 팬 그램에 접근하는 방법에 대한 간략한 설명은 아래의 “Language w / Larger Alphabets”섹션에 있습니다.
다이내믹 비순환 그래프 (DAG) 구축
이제 비트 마스크 상태를 채웠습니다. 솔루션을 검색 할 시간입니다!
가능한 완벽한 판 그램 세트를 만든 단어 세트를 찾으려면 최종 상태를 구성하는 데 필수적인 중간 상태를 도출해야합니다. . 그런 다음 후속 질문은 어떤 다른 중개 상태가 이러한 중개 상태를 구성했는지에 대한 질문으로, 남은 것은 단어에 직접 매핑되는 상태뿐입니다.이 과정을 역 추적이라고합니다.
유지 상태 간의 관계를 추적하는 것이 목표는 Di를 생성하는 것입니다. Rected Acyclical Graph (DAG)는 주어진 상태를 구성하는 중간 상태를 유지합니다. DAG는 특히 비 주기적 특성으로 인해 출력을 검색하기 위해 탐색하기 쉽습니다. 구성하려면 가능한 완벽한 팬 그램 상태에서 시작하여이를 구성하는 중간 상태를 가리키는 방향 가장자리 (화살표)를 만듭니다. 중간 상태로 프로세스를 반복하면 DAG가 생성됩니다. 화살표는 항상 더 작은 값을 가진 상태를 가리 키기 때문에 어떤 순환도 없을 것입니다.
검색 단계에서 발견 된 관계를 재 구축하는 대신 수조 개의 가능한 상태 조합을 다시 탐색하는 작업을 포함하는 대신 동적 프로그래밍 단계에서 DAG를 구축하는 것이 더 효율적입니다. solve 메서드 내에서 새로 구성된 상태가 가능한 완벽한 팬 그램 상태에 도달 할 수있는 경우, 원래 상태가 보수보다 작은 경우에만 (가장자리 중복을 줄이기 위해) 새로 구성된 상태에서 원래 상태로 향하는 가장자리를 저장합니다.
노력의 열매를 나무 형태로 인쇄하세요!
결과 단어 집합을보기위한 가장 쉬운 형식은 루트 노드를 완벽한 팬 그램 상태로하는 트리로 나열하는 것입니다. 위에서 구성된 DAG를 감안할 때 압축을 푸는 가장 좋은 방법은 트리가 DAG보다 훨씬 크기 때문에 메모리 내 대신 각 단계에서 각 상태를 디스크에 기록하여 재귀 적으로 수행하는 것입니다.
이 확장 형식의 개선 사항은 가능한 단어 조합이 하나 뿐인 상태를 요약하는 것입니다. 단어를 가리는 상태이고 그것을 구성하는 하위 상태가없는 상태는 간단하게 요약 할 수 있습니다. 상태는 하위 상태와 그 합성물을 요약 할 수 있고 자체 및 하위에서 파생 된 모든 마스크에 겹치는 비트 / 문자가없는 경우 요약 될 수 있습니다. 요약 된 DAG를 인쇄하면 결과 출력 트리를 짧게 및 단순화하여 가독성이 향상됩니다.
요약은 두 상태 중 더 작은 상태에만 의존하므로 초기 상태 인 0에서 위쪽으로 배열을 반복하고 요약 규칙을 관리하기 위해 위의 규칙을 사용하면이 작업이 선형 시간에 완료 될 수 있습니다.
생산 된 Pangram Trees!
완벽한 Pangram 트리를 자유롭게 탐색하여 흥미로운 문장을 찾을 수 있습니다!
완벽한 팬 그램이 많이 있습니다
완벽한 팬 그램의 수에 놀랐습니다. 많이 있습니다! 그것들을 함께 연결하기위한 최선의 전략은 복잡한 자연어 프로세서를 필요로하지 않습니다. 후보 단어가 명사 또는 동사 적격으로 라벨이 지정되면 단어 모음에는 최소한 하나의 명사, 하나의 동사, 명사와 동사의 올바른 비율이 포함되어야합니다.
데이터 품질은 어려운 문제입니다
알고리즘 섹션은 2 일이 걸렸지 만 데이터 품질 문제는 2 주가 걸렸습니다. Google의 수석 엔지니어 인 제 친구에게이 발견을 언급했을 때 그는 데이터 품질 문제가 엔지니어링에서 가장 어려운 문제 중 일부라고 언급하면서 놀라지 않았습니다. 교훈을 얻었습니다.
완벽한 팬 그램의 규칙
완벽한 팬 그램에 해당하는 것이 무엇인지에 대해서는 많은 뉘앙스가 있습니다! 감탄사 (예 : hm, pht)없이 팬 그램을 검색하고 싶었지만 약어, 두문자어, 축약, 이니셜, 분리 된 문자, 고유 명사 및 로마 숫자와 같은 다른 인기있는 제한 사항도 있습니다. Qoph와 같이 글자의 이름 인 단어도 있습니다. 저는 속임수라고 느꼈습니다.
이러한 제약 중 일부가 완화되면 “완벽한”팬 그램이 많이 있습니다. 수조 단위로, 아마도 . 많은 두문자어와 이니셜이 있습니다.
별표
별표는 영어의 모든 완벽한 팬 그램의 정의가 잘 정의되지 않았기 때문에 제자리에 있습니다. 뉘앙스가 있습니다. 영어의 완벽한 판 그램에 허용되어야하는 것과 관련이 있습니다. 또한 어떤 단어가 영어 단어인지 아닌지에 대한 많은 논쟁이 있습니다. 이러한 뉘앙스를 감안할 때 완벽한 판 그램을 모두 찾았다 고 말하기가 정말 어렵습니다. 저는 상당히 자신있게 두 가지 주장을 할 수 있습니다.
- 비슷하거나 더 작은 문자 세트를 사용하여 영어 및 기타 언어의 모든 완벽한 판 그램을 생성하는 방법론을 찾았습니다.
- I 공식 Scrabble 토너먼트 사전을 사용하여 완벽한 판 그램을 형성 할 수있는 모든 단어 세트를 열거했습니다. y, OWL3.
이 게시물에 설명 된 기술로 자신 만의 완벽한 팬 그램을 자유롭게 제작하세요!
웨일스 어와 아랍어 어근에 대한 완벽한 팬 그램의 의존성
웨일스 어 및 아랍어에서 파생 된 단어는 완벽한 영어 팬 그램의 존재에 정말 중요했습니다 (완벽한 팬 그램의 제약이 완화되지 않는 한). 완벽한 판 그램에 관한 엄격한 규칙이있는 OWL3 단어 목록을 사용하면 웨일스 어 단어 인 “cwm (s)”또는 “crwth (s)”가 포함되지 않은 완벽한 판 그램은 없습니다. 국제 스크래블에서 아랍어에서 파생 된 단어 “waqf (s)”는 “cwm (s)”또는 “crwth (s)”에 의존하지 않고도 완벽한 판 그램을 생성 할 수있는 유효한 단어입니다.
작업 흐름 효율성
이 프로젝트 동안 작업을 병렬화하는 데 더 효율적이되는 것이 중요했습니다. 전체 실행에는 Unix 사전의 경우 25 분이 걸리고 실제로 큰 사전의 경우 한 시간에 가깝습니다. 30 분 동안 초기 문제 컨텍스트 전환이 있었지만 생산성을 향상시키기 위해 진행하면서 더 나아졌습니다.
확장 / 일반화 — Anagram Finder
완벽한 pangram search는 “abcdefghijklmnopqrstuvwxyz”문자열에 대한 anagram finder와 동일합니다. 일반적인 anagram finder를 구축하려면 어떻게해야합니까?
확인을위한 상태 표현 및 관리 규칙이있는 한 동일한 기술을 사용할 수 있습니다. 단어 조합 유효성이 업데이트됩니다. 상태를 정수로 관리하는 대신 해당 문자의 맵으로 상태를 추적하는 것이 더 쉬울 것입니다. 조합이 유효한지 확인하는 것은 두 맵의 조합이 각 문자에 대한 anagram의 원하는 문자 수입니다. 상태 공간이 다루기 쉬운 지 확인하십시오. 문자가 너무 많으면 검색 공간이 순식간에 커질 수 있습니다. 또한 단어를 반복 할 수 있습니까? 해당 규칙을 내부에 정의해야합니다. 당신의 동적 프로그래밍 솔루션입니다.
더 큰 알파벳이있는 언어
이 접근 방식과 솔루션은 단어 집합 크기에서 선형이지만 알파벳 크기에서는 기하 급수적입니다. 이 접근 방식은 더 큰 문자 집합에서는 작동하지 않을 수 있습니다. 예를 들어 46 음절이있는 현대 일본어가 있습니다. 2⁴⁶은 70,368,744,177,664입니다. 영어 검색 공간 인 2²⁶ = 67,108,864보다 백만 배 이상 큽니다.
이 접근 방식이 일본어에 적합한 지 여부는 명확하지 않습니다. 일본어의 엔트로피가 충분히 낮은 경우 가능하면이 접근 방식이 가능합니다. 크기가 2⁴⁶ 인 배열을 초기화하는 대신 상태가지도에서 추적됩니다. 더욱이 일본의 구조는 악용 될 수 있습니다. 예를 들어 kana を (wo)는 게시물 위치 분사로 거의 독점적으로 사용되며 검색에서 제외되어 검색 공간을 줄일 수 있습니다.
캄보디아어 인 크메르어는 74로 가장 큰 알파벳을 가지고 있습니다. 또 다른 가능한 다음 단계는 알파벳 크기가 기하 급수적 인 솔루션을 탐색하는 것입니다.
영감
저는 Aubrey De Grey가 평면의 색수를 찾는 발전에 영감을 받았습니다. 최소한 5. 이것은 기본적인 계산 방법을 통해 달성 된 상당한 발전입니다.
물론, 완벽한 판 그램을 찾는 것은 평면의 색채 수의 하한선을 개선하는 데 초를 잡을 수 없습니다.
이것은 수동으로 다루기 힘든 문제를 해결하기위한 간단한 계산 방법을 가진 낮은 매달려있는 과일 문제가 많이 있다고 믿게합니다. 이러한 문제 중 일부를 찾아 해결하도록 도전합니다. 뭔가 찾으면 알려주세요!
감사합니다
저와 함께 교정과 재밍을 도와 준 훌륭한 친구들, 특히 Anna Zeng, Catherine에게 감사합니다. Gao, Danny Wasserman, George Washington, Nick Wu!