스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
Swift Regex 소개
Swift Regex를 활용하여 문자열을 더 효과적으로 처리하는 방법을 알아보세요. 새롭고 선언적인 방식으로 문자열을 처리하여 간결한 리터럴을 지원하는 Regex 빌더를 활용하세요. 또한 문자열의 유니코드 모델을 살펴보고, Swift Regex로 유니코드에 적합한 처리를 손쉽게 수행할 수 있는 방법을 소개합니다.
리소스
관련 비디오
WWDC22
-
다운로드
안녕하세요 저는 Michael Ilseman입니다 Swift 표준 라이브러리 팀의 엔지니어죠 저와 함께 Swift의 정규식에 대해 알아볼 건데요 Swift의 수많은 정규식이 제공하는 모든 기능을 맛보는 시간이 될 겁니다 우리가 금융 조사관과 협력하여 부정 거래 분석 도구를 만드는 개발자라고 가정해보죠 이렇게 중요한 작업의 경우 잘 구조화된 데이터를 처리할 거라 생각하겠지만 그 대신 여러 문자열만 있는 상황이에요 첫 번째 필드는 거래의 종류이고 두 번째는 거래일 세 번째 필드는 개인 또는 기관 정보 네 번째 필드는 미화로 표시된 금액이죠 각 필드는 2칸 이상의 공백이나 탭으로 구분되어 있는데 이는 관련된 사람이 기억할 수 없는 매우 중요한 기술적인 이유 때문이죠 또한 날짜 필드도 상당히 모호한데요 월, 일, 년 순이기를 바라며 어떻게 되는지 보겠습니다 이런 거래 처리에는 문자열 처리가 포함되는데 문자열은 컬렉션이에요 제네릭 컬렉션 알고리즘에 액세스한다는 뜻이죠 이러한 알고리즘은 기본적으로 두 종류로 나뉘는데 하나는 요소에 대해, 다른 하나는 인덱스에 대해 작동합니다 거래 필드를 분할하여 요소 기반 알고리즘을 사용하는 방법이 있지만 필드 구분자가 탭이나 2칸 이상의 공백인 경우에는 쉽지 않습니다 공백만으로는 분할되지 않기 때문이죠 또 다른 방법은 저급 인덱스 조작 코드로 드롭다운하는 것인데
제대로 하기도 어려우며 설령 할 수 있다고 하더라도 많은 코드가 필요합니다 분할을 다시 살펴보죠 이 방법이 통하지 않는 이유는 필드 구분자는 복잡한 패턴인데 이에 반해 요소 기반으로 작동하기 때문입니다 많은 언어에서 이러한 문제의 해결책으로 정규식을 사용하고 있는데요 정규식은 정규 언어를 정의하는 형식 언어 이론에서 나온 것으로 편집기와 명령줄 도구의 실용적인 검색 응용 프로그램뿐만 아니라 컴파일러에서의 구문 분석에 사용되는데 이러한 응용 프로그램들은 정규식을 원래의 이론적 용도를 넘어 입력값의 부분을 추출하고 실행을 제어 및 명령하며 표현력을 강화하기 위해 활용합니다 Swift는 거기서 한 단계 더 나아가는데 이를 파생 정규식이라고 하죠 정규식은 캡처를 포함하여 그 적용 결과인 출력값에 대한 제네릭 구조로 슬래시 구분 기호 사이에 정규식 구문이 들어가는 리터럴을 사용해 만들 수 있으며 Swift의 정규식 구문은 Perl, Python, Ruby, Java NSRegularExpression와 그 밖의 많은 언어와 호환됩니다 이 정규식은 하나 이상의 숫자와 일치하는데 컴파일러는 정규식 구문을 알고 있으므로 구문 강조와 컴파일 타임 오류 및 나중에 필요한 엄격한 타입의 캡처까지 얻을 수 있죠 정규식은 런타임에 동일한 정규식 구문을 포함하는 문자열에서 만들 수 있는데 편집기나 명령줄 도구의 검색 필드에서 유용하며 입력값에 잘못된 구문이 포함된 경우 런타임에 오류가 발생합니다 출력값 타입은 실존 AnyRegexOutput인데 캡처의 유형과 수는 런타임까지 알 수 없기 때문이죠
정규식 빌더를 사용하면 더 길기는 하지만 선언적이고 잘 구조화된 동일한 정규식을 작성할 수 있습니다
앞서 언급한 분할 방식을 적용하여 정규식 리터럴을 사용해보죠 첫 번째 부분은 공백이 2개 이상인 경우와 매치되며 두 번째 부분은 수평 탭 1개와 매치됩니다 그리고 수직선 기호는 대체 요소 사이의 선택을 나타내는 것으로 2칸 이상의 공백 또는 단일 탭 문자를 찾아주죠 이제 필드가 분할되었으니 문명 자체에 기여하고 해당 필드 구분 기호를 단일 탭으로 정규화하여 완료할 건데요 분할 후 결과값에 대해 'join'을 호출할 수도 있지만 보다 뛰어난 알고리즘이 있습니다 'replacing'은 모든 필드 구분자를 단일 탭으로 대체해주죠
저희는 이러한 탁월한 방식을 모든 사람에게 전파하고 있는데 채택 속도는 느리지만 전망은 밝습니다 정규식을 잘 아는 분들은 그 엇갈린 평판도 익히 들어보셨을 거예요 옛말에 문제 하나를 해결하려고 정규식을 썼더니 문제가 둘로 늘었다는 말도 있죠 하지만 Swift 정규식은 다릅니다 Swift는 4가지 핵심 영역에서 혁신을 이루어냈죠 정규식 구문은 간결하고 표현력이 뛰어나지만 너무 축약적이어서 읽기 어려울 수 있습니다 또한 새로운 기능이 생길수록 구문은 점점 더 암호화되죠
Swift 정규식은 정규식 빌더를 통해 소스 코드를 구조화하고 조직화하는 방식으로 구조화 및 조직화될 수 있습니다 리터럴은 간결하고 빌더는 구조를 제공하므로 리터럴을 통해 빌더 내에서 완벽한 균형을 찾을 수 있죠 데이터의 텍스트 표현은 훨씬 복잡해져서 올바르게 처리하려면 표준을 따르는 파서가 필요한데요 Swift 정규식을 사용하면 매우 강력한 파서를 정규식의 개별 구성 요소로 결합시킬 수 있습니다 이는 라이브러리 확장 방식으로 수행되는데 모든 파서가 참여할 수 있다는 뜻이죠
정규식 적용 역사의 대부분은 전체 컴퓨터 시스템이 하나의 언어와 인코딩 특히 아스키 코드만을 지원하던 시절에 이루어졌지만 지금은 유니코드의 시대죠 Swift 정규식은 유니코드를 쓰면서 표현력은 그대로 유지합니다 마지막으로 정규식의 힘을 활용하면 철저히 탐구되어야 하는 광범위한 검색 영역을 열 수 있 수 있는데 이러한 특징으로 인해 실행 방식을 예측하기 어렵죠 일부 언어는 제어 문자를 지원하지만 난해한 구문 뒤에 감춰져 있어서 이해하기 어려운 경향이 있습니다 Swift 정규식은 실행을 예측할 수 있고 제어 문자를 쉽게 봅니다 앞서 작업하던 재무제표로 돌아가 Swift의 선언적인 문자열 처리 방식인 정규식 빌더를 사용하여 각 거래 구문을 낱낱히 분석해보겠습니다 먼저 RegexBuilder 모듈을 가져오고 방금 정의한 필드 구분자 정규식을 재사용할 수 있어요 첫 번째 필드는 간단하게 차변 또는 대변으로 이미 살펴본 정규식 리터럴 구문을 사용하여 작성할 수 있습니다 그 다음 필드 구분자와 날짜가 나오는데 날짜를 수동으로 파싱하는 건 바람직하지 않아요 Foundation에는 정규식 빌더에서 직접 사용할 수 있는 날짜, 숫자, URL과 같은 유형을 위한 훌륭한 파서가 있으며
또한 우리는 시스템의 현재 로케일을 암묵적으로 쓰는 대신 저자의 의도 추측에 최적인 명시적인 로케일을 제공하는데 코드에 명시적으로 한 가정이므로 나중에 언제든 변경할 수 있습니다
세 번째 필드는 '모든 것'이 될 수 있으므로 '하나 이상의 모든 것'이라고 작성하는 것이 좋은데 이렇게 하면 정답을 구할 수 있지만 그 뒤에 오는 모든 것과의 매칭을 수행하기 때문에 많은 불필요한 작업이 수반되죠 정규식은 한 번에 하나의 문자를 백업하고 나머지 패턴을 수행합니다 종료 필드 구분자가 나오면 정규식을 중단하고 싶은 경우 다양한 방법을 사용할 수 있는데요 바람직한 방법 중 하나는 입력값의 다음 부분을 실제로 소비하지 않고도 엿볼 수 있는 NegativeLookahead를 쓰는 건데요 입력을 미리 살펴봄으로써 문자를 매칭하기 전에 필드 구분자가 표시되지 않도록 합니다 NegativeLookahead는 정규식이 구성 요소와 매칭되는 방식을 정확히 제어할 수 있는 도구 제품군 중 하나죠
마지막으로 금액 매칭에도 Foundation의 파서 중 하나를 사용하는데 이번에는 통화예요 쉼표는 천 단위 구분 기호이고 마침표는 소수점 구분 기호라고 가정하고 이 가정을 명시적으로 만들어 줍니다 이렇게 거래 원장 중 한 줄을 분석가능한 정규식을 만들었는데 우리가 원하는 건 해당 라인만이 아닌 데이터 일부를 추출하는 거죠 이때 Capture를 사용하면 입력값의 일부를 추출할 수 있습니다 관례상 '0번째' 캡처는 전체 정규식과 매칭되는 입력값의 부분이며 이어서 각각의 명시적 캡처가 뒤따르죠 거래 유형은 입력의 한 조각인 하위 문자열로 캡처됩니다 날짜의 경우 텍스트를 후처리할 필요 없이 파싱된 엄격한 타입의 값을 캡처하고 개인 또는 기관 역시 입력의 일부로 캡처되죠 소수점 캡처는 또 다른 강력한 타입의 값인데 이를 사용하려면 매칭 결과에서 날짜 및 소수점 값을 추출하고 조사관은 이 단계에서 데이터를 가져옵니다 이쯤에서 저희는 구조화된 쿼리 같은 명백한 이익을 위해 데이터를 실제 데이터베이스에 덤프하도록 권장하는데요 문제가 있습니다 조사관들은 모든 걸 문자열로 유지하고 싶어 한다는 거죠 좋은 소식이에요, Swift 정규식을 더 자세히 살펴볼 수 있으니까요 모든 게 순조롭게 진행되다가 갑자기 문제가 발생했어요 우리는 방금 모호할 것이라고 가정했던 날짜 순서가 실제로 모호하다는 걸 알게 됐습니다 날짜 순서는 항상 같지 않으며 거래에 사용된 통화에 따라 달라진다는 게 유력한 이론인데요 당연히 그렇겠죠 미국 달러일 경우 월/일/년 순이고 영국 파운드는 일/월/년 순서가 될 거예요 차이를 명확히 보기 위해 sed와 유사한 스크립트를 작성해보죠 이 정규식의 경우 확장된 구분자를 사용할 건데요 이러면 슬래시를 이스케이프 처리 하지 않고도 내부에 넣을 수 있죠 또한 공백이 무시되는 확장 구문 모드에 대한 액세스도 제공합니다 일반 코드처럼 가독성을 위해 공백을 사용할 수 있다는 뜻이죠 저희는 명명된 캡쳐를 사용해서 정규식 출력을 튜플 레이블로 표시했습니다 통화 기호 인식을 위해 유니코드 속성을 사용하는데 이렇게 하면 정규식의 적응력이 향상되죠 응용 프로그램 로직에서 특정 기호를 다룰 건데요
텍스트를 수동으로 자르고 붙이는 대신 이번에도 Foundation의 날짜 구문 분석기를 사용하겠습니다 pickStrategy는 통화 기호를 수신하고 이를 기반으로 구문 분석 전략을 결정하는데 모든 가정은 코드에 명시되어 있기 때문에 결국 필요하게 될 무언가를 보다 쉽게 조정 및 발전시킬 수 있죠
캡처를 포함한 일치 결과를 사용하는 클로저를 제공함으로써 찾기 및 대체 알고리즘이 있는 정규식 및 헬퍼 함수를 통해 대체 문자열을 구성해봅시다 캡처된 통화를 기반으로 전략을 정하고 캡처된 날짜를 파싱하는데 위치뿐만이 아니라 이름으로도 캡처에 액세스할 수 있죠 출력의 경우 명확한 업계 표준인 ISO-8601을 사용하여 새로운 날짜의 형식을 지정하는데 그러면 이러한 원장이 명확하게 변환되죠 실제 날짜 파서와 포매터를 사용하기 때문에 변화하는 요구 사항에 훨씬 더 잘 적응할 수 있습니다 또한 유니코드 속성을 사용하여 통화 기호를 인식하면 훨씬 더 빠르게 발전시킬 수 있죠 정규식은 일부 문자열 모델에 대해 알고리즘을 선언하며 Swift 문자열은 유니코드로 작업 가능한 다양한 모델을 제공합니다 이 문자열은 사랑 이야기를 표현한 문자 3개로 이루어져 있는데 이러한 문자는 복잡한 엔티티로 공식적으로 유니코드 확장 문자소 클러스터라고 불리며 단일 문자는 1개 이상의 유니코드 스칼라 값으로 구성되죠 문자열은 UnicodeScalarView를 제공하여 해당 내용의 저급 표현에 액세스할 수 있도록 하는데 이를 통해 가용성이 향상되고 다른 시스템과의 호환성도 갖출 수 있죠
이야기의 주인공인 첫 번째 문자는 유니코드 스칼라 4개로 구성되는데 좀비 이모티콘과 폭이 0인 결합자, 여성 기호 변형 선택자 16으로 이 컨텍스트에서는 이모티콘으로 렌더링되는 경향이 있어요 당연한 얘기죠! 이 스칼라들은 눈으로 볼 수 있는 하나의 이모티콘을 생성하는데 문자열이 메모리에 저장되면 UTF-8 바이트로 인코딩되고 UTF-8 보기를 사용하여 이러한 바이트를 볼 수 있는 거죠 UTF-8은 가변 너비 인코딩으로 하나의 스칼라에 여러 바이트가 필요할 수 있고 앞에서 보았듯이 하나의 문자에 여러 스칼라가 필요할 수도 있어요 유니코드 스칼라 4개로 표현되는 이 이야기의 주인공은 UTF-8 바이트 13개로 인코딩되죠 여러 개의 스칼라로 구성되는 것 외에도 정확히 같은 문자가 다른 스칼라 집합으로 표현되는 경우도 있는데 영어 이외의 언어를 0다룰 때 자주 발생하죠 이 예에서 양음 부호가 있는 'e'는 사전 구성된 양음 부호가 있는 단일 스칼라로 표현하거나 아스키 코드 'e' 다음에 양음 부호를 결합하여 표현할 수 있어요 둘은 동일한 문자이므로 문자열 비교 시 참이 반환됩니다 문자열이 공식적으로 유니코드 규범적 등가 규칙을 따르기 때문이죠
UnicodeScalarView 관점이나 UTF-8 보기에서 보이는 내용은 서로 다르며 이렇게 낮은 수준의 뷰에서 비교해 그 차이를 볼 수 있습니다 문자열과 같이, Swift 정규식은 기본적으로 유니코드를 엄격히 따릅니다 하지만 표현력은 저하되지 않죠 문자열을 한 쌍을 바꿔볼게요 첫 번째 문자열은 점으로 표시된 모든 문자로 둘러싸인 '반짝이는 하트'라고 명명된 유니코드 스칼라와 매칭되고
모든 문자 클래스는 모든 Swift 문자 즉 모든 유니코드 확장 문자소 클러스터와 매칭되죠
두 번째 문자열의 경우 equal로 동일한 문자를 비교하고 대소문자는 무시하도록 합니다 이제 단순했던 사랑 이야기가 훨씬 더 복잡해졌어요 살 수도 있겠지만 이 경우엔 죽음이겠군요 처리할 복잡한 일들이 있죠
문자열 마찬가지로 호환성이나 하위 문자소 클러스터의 정확성을 위해 유니코드 스칼라 값을 직접 처리해야 할 경우 'unicodeScalar' 시멘틱과 매칭시키면 됩니다 유니코드 스칼라 수준에서 매칭하면 점이 전체 Swift 문자가 아닌 단일 유니코드 스칼라 값과 매칭되죠 우리의 친구 변형 선택자 16을 다시 볼 수 있다는 뜻입니다 이 작고 친숙한 선택자는 점에 의해 매칭되며 혼자서는 빈 공백으로 렌더링되므로 보이지 않죠 정말 유용한 친구예요
이제 정확성과 정밀성이 해결되었으니 금융 데이터로 돌아가서 조금 다른 작업을 해보죠 조사관들이 돌아왔는데 이번에는 아주 흥미로운 요구를 합니다 조사관들은 장부를 사후 처리하는 대신 실시간으로 거래를 조사할 수 있도록 거래 매칭 도구를 변경했는데요 코드를 보니 상당히 잘 작성했지만 확장 문제를 해결하려면 우리의 도움이 필요한 상황입니다 이들이 처리하는 거래는 매우 유사하지만 약간의 차이점이 있는데요 날짜 대신에 정확한 타임스탬프가 있다는 거죠 이러한 타임스탬프는 명확하고 분명하며 놀랍도록 독점적인 형식으로 표현되는데요 한 세기 전에 작성된 정규 표현식이 정확히 이런 형식이었지만 괜찮아요 다음으로 개인과 식별 코드를 포함하는 세부 정보 필드가 있는데요 입력에서 파생된 런타임 컴파일된 정규식을 사용하여 이 필드에 대한 거래를 필터링합니다 이건 실시간 도구이고 이후에 더 많은 필드가 있으므로 관심 없는 거래는 조기에 제외시키려는 거죠 다음으로 금액과 체크섬 같은 기타 필드가 있는데 이러한 필드는 자체적으로 잘 처리되고 있어요 물론 각 필드는 2개 이상의 공백이나 단일 탭으로 구분됩니다
transactionmatcher는 우리와 상당히 비슷해요 타임스탬프에 대한 자체 정규식이 있고 details 정규식이 입력에서 컴파일되어 나머지 필드를 처리합니다 상당히 잘 작성되었으며 모든 것이 기술적으로 잘 작동하지만 확장성이 떨어집니다 타임스탬프와 details 정규식이 필드보다 입력과 매칭되는 경우가 훨씬 많았던 거죠 이러한 정규식은 단일 필드에서만 실행되도록 제한하는 것이 이상적입니다 앞서 NegativeLookahead로 비슷한 문제를 해결한 적이 있으니 해당 정규식을 가져와 보죠
'field'는 필드 구분자를 찾을 때까지 문자와 효율적으로 매칭되며 'field'를 사용하여 정규식을 포함하고자 합니다 이는 후처리 단계로 수행할 수도 있지만 이 경우 실시간이므로 정규식이 해당 필드와 일치하지 않으면 조기에 제외하려 하는데요 이때 TryCapture를 사용하면 되죠 TryCapture는 매칭되는 필드를 클로저에 전달하는데 이 단계에서 조사관의 타임스탬프와 세부 정규식을 테스트합니다 매칭되는 경우 필드의 값을 반환하는데 매칭에 성공하여 필드가 캡처됐음을 의미하죠 한편 매칭되지 않으면 매칭 실패를 알리는 nil을 반환합니다 TryCapture의 클로저는 매칭에 적극적으로 참여하는데 그게 바로 우리가 원했던 기능이죠 이렇게 해서 중요한 확장 문제를 해결했는데요 아직 문제가 하나 남았습니다 이후에 transactionmatcher에서 오류가 발생할 경우 종료까지 오랜 시간이 걸릴 수 있다는 거죠
맨 처음에 정의한 fieldSeparator 정규식은 우리가 원했던 대로 2개 이상의 공백이나 단일 탭과 매칭됩니다 8개의 공백 문자가 있는 경우 나머지 정규식을 실행하기 전에 모두 매칭되죠 그러나 이후의 정규식이 매칭에 실패하면 뒤로 돌아가서 7개의 공백 문자만 재시도 전에 매칭하게 됩니다 그리고 그게 실패하면 6개의 공백 문자만 매칭하는 식으로 진행되죠
모든 대체 요소를 시도한 후에야 매칭이 실패하게 됩니다 이렇게 대체 매칭을 위해 돌아가는 것을 전역 백트래킹이라고 하거나 형식 논리에서는 클레이니 클로저라고 하는데요 이 기능은 정규식에 고유한 힘을 부여하여 보다 광범위한 탐색 공간을 열어주지만 이 경우 우리에게 필요한 것은 선형적인 검색 공간이죠 모든 공백을 하나도 빠짐 없이 매칭하고자 하는 건데요 이때 사용할 수 있는 도구가 몇 가지 있습니다 가장 일반적인 도구는 fieldSeparator를 전역 범위가 아닌 로컬 백트래킹 범위에 넣는 거죠
로컬 빌더는 포함된 정규식이 성공적으로 매칭되지 않으면 시도되지 않은 대체 요소는 버려지는 범위를 생성하며
이후의 transactionmatcher가 실패하더라도 더 적은 공간을 소비하려고 다시 돌아가지 않습니다 정규식의 기본값인 전역 백트래킹은 검색 및 퍼지 매칭에 적합하고 Local은 정확하게 지정된 토큰을 매칭시킬 때 유용합니다 fieldSeparator는 상당히 번거롭지만 정확성이 뛰어난 반면
Local은 아토믹 비 캡처 그룹으로 불리기도 하는데 좀 무서운 이름이죠 마치 정규식이 폭발할 것 같잖아요 하지만 실제로는 그 반대로 검색 공간을 포함하며
이를 통해 확장성 문제를 해결하도록 도울 수 있죠 오늘은 Swift 정규식에 대해 알아봤는데 다루지 못한 내용이 너무 많습니다 제 동료 리처드의 'Swift 정규식의 기초를 넘어'를 꼭 확인해보세요 마무리하기 전에 요점을 짚어보겠습니다 정규식 빌더는 구조를 제공하는 한편 정규식 리터럴은 간결하죠 둘 중 어떤 걸 선택할지는 결국 주관적인 결정에 달려 있어요 가능하면 언제나 실제 파서를 사용하도록 하세요 시간을 훨씬 절약하면서도 손쉽게 작업을 수행할 수 있습니다 Swift 기본값만 사용해도 독보적으로 많은 유니코드 지원과 혜택을 누릴 수 있죠 통화 기호를 매칭하는 경우처럼 문자 속성 등을 효과적으로 활용할 수 있는 방법을 찾아보세요 마지막으로 lookahead와 Local 백트래킹 범위 같은 컨트롤을 통해 검색 및 처리 알고리즘을 단순화하시길 바랍니다 시청해 주셔서 감사합니다
-
-
1:35 - Processing collections
let transaction = "DEBIT 03/05/2022 Doug's Dugout Dogs $33.27" let fragments = transaction.split(whereSeparator: \.isWhitespace) // ["DEBIT", "03/05/2022", "Doug\'s", "Dugout", "Dogs", "$33.27"]
-
1:49 - Low-level index manipulation
var slice = transaction[...] // Extract a field, advancing `slice` to the start of the next field func extractField() -> Substring { let endIdx = { var start = slice.startIndex while true { // Position of next whitespace (including tabs) guard let spaceIdx = slice[start...].firstIndex(where: \.isWhitespace) else { return slice.endIndex } // Tab suffices if slice[spaceIdx] == "\t" { return spaceIdx } // Otherwise check for a second whitespace character let afterSpaceIdx = slice.index(after: spaceIdx) if afterSpaceIdx == slice.endIndex || slice[afterSpaceIdx].isWhitespace { return spaceIdx } // Skip over the single space and try again start = afterSpaceIdx } }() defer { slice = slice[endIdx...].drop(while: \.isWhitespace) } return slice[..<endIdx] } let kind = extractField() let date = try Date(String(extractField()), strategy: Date.FormatStyle(date: .numeric)) let account = extractField() let amount = try Decimal(String(extractField()), format: .currency(code: "USD"))
-
2:47 - Regex literals
// Regex literals let digits = /\d+/ // digits: Regex<Substring>
-
3:20 - Regex created at run-time
// Run-time construction let runtimeString = #"\d+"# let digits = try Regex(runtimeString) // digits: Regex<AnyRegexOutput>
-
3:44 - Regex builder
// Regex builders let digits = OneOrMore(.digit) // digits: Regex<Substring>
-
3:56 - Split approach with a regex literal
let transaction = "DEBIT 03/05/2022 Doug's Dugout Dogs $33.27" let fragments = transaction.split(separator: /\s{2,}|\t/) // ["DEBIT", "03/05/2022", "Doug's Dugout Dogs", "$33.27"]
-
4:36 - Normalize field separators
let transaction = "DEBIT 03/05/2022 Doug's Dugout Dogs $33.27" let normalized = transaction.replacing(/\s{2,}|\t/, with: "\t") // DEBIT»03/05/2022»Doug's Dugout Dogs»$33.27
-
6:55 - Create a Regex builder
// CREDIT 03/02/2022 Payroll from employer $200.23 // CREDIT 03/03/2022 Suspect A $2,000,000.00 // DEBIT 03/03/2022 Ted's Pet Rock Sanctuary $2,000,000.00 // DEBIT 03/05/2022 Doug's Dugout Dogs $33.27 import RegexBuilder let fieldSeparator = /\s{2,}|\t/ let transactionMatcher = Regex { /CREDIT|DEBIT/ fieldSeparator One(.date(.numeric, locale: Locale(identifier: "en_US"), timeZone: .gmt)) fieldSeparator OneOrMore { NegativeLookahead { fieldSeparator } CharacterClass.any } fieldSeparator One(.localizedCurrency(code: "USD").locale(Locale(identifier: "en_US"))) }
-
9:04 - Use Captures to extract portions of input
let fieldSeparator = /\s{2,}|\t/ let transactionMatcher = Regex { Capture { /CREDIT|DEBIT/ } fieldSeparator Capture { One(.date(.numeric, locale: Locale(identifier: "en_US"), timeZone: .gmt)) } fieldSeparator Capture { OneOrMore { NegativeLookahead { fieldSeparator } CharacterClass.any } } fieldSeparator Capture { One(.localizedCurrency(code: "USD").locale(Locale(identifier: "en_US"))) } } // transactionMatcher: Regex<(Substring, Substring, Date, Substring, Decimal)>
-
10:31 - Plot twist!
private let ledger = """ KIND DATE INSTITUTION AMOUNT ---------------------------------------------------------------- CREDIT 03/01/2022 Payroll from employer $200.23 CREDIT 03/03/2022 Suspect A $2,000,000.00 DEBIT 03/03/2022 Ted's Pet Rock Sanctuary $2,000,000.00 DEBIT 03/05/2022 Doug's Dugout Dogs $33.27 DEBIT 06/03/2022 Oxford Comma Supply Ltd. £57.33 """ // 😱
-
10:53 - Use named captures
let regex = #/ (?<date> \d{2} / \d{2} / \d{4}) (?<middle> \P{currencySymbol}+) (?<currency> \p{currencySymbol}) /# // Regex<(Substring, date: Substring, middle: Substring, currency: Substring)>
-
11:33 - Use Foundation's date parser
let regex = #/ (?<date> \d{2} / \d{2} / \d{4}) (?<middle> \P{currencySymbol}+) (?<currency> \p{currencySymbol}) /# // Regex<(Substring, date: Substring, middle: Substring, currency: Substring)> func pickStrategy(_ currency: Substring) -> Date.ParseStrategy { switch currency { case "$": return .date(.numeric, locale: Locale(identifier: "en_US"), timeZone: .gmt) case "£": return .date(.numeric, locale: Locale(identifier: "en_GB"), timeZone: .gmt) default: fatalError("We found another one!") } }
-
11:48 - Find and replace
let regex = #/ (?<date> \d{2} / \d{2} / \d{4}) (?<middle> \P{currencySymbol}+) (?<currency> \p{currencySymbol}) /# // Regex<(Substring, date: Substring, middle: Substring, currency: Substring)> func pickStrategy(_ currency: Substring) -> Date.ParseStrategy { … } ledger.replace(regex) { match -> String in let date = try! Date(String(match.date), strategy: pickStrategy(match.currency)) // ISO 8601, it's the only way to be sure let newDate = date.formatted(.iso8601.year().month().day()) return newDate + match.middle + match.currency }
-
12:45 - A zombie love story
let aZombieLoveStory = "🧟♀️💖🧠" // Characters: 🧟♀️, 💖, 🧠
-
13:01 - A zombie love story in unicode scalars
aZombieLoveStory.unicodeScalars // Unicode scalar values: U+1F9DF, U+200D, U+2640, U+FE0F, U+1F496, U+1F9E0
-
13:44 - A zombie love story in UTF8
aZombieLoveStory.utf8 // UTF-8 code units: F0 9F A7 9F E2 80 8D E2 99 80 EF B8 8F F0 9F 92 96 F0 9F A7 A0
-
14:12 - Unicode canonical equivalence
"café".elementsEqual("cafe\u{301}") // true
-
14:49 - String's views are compared at binary level
"café".elementsEqual("cafe\u{301}") // true "café".unicodeScalars.elementsEqual("cafe\u{301}".unicodeScalars) // false "café".utf8.elementsEqual("cafe\u{301}".utf8) // false
-
15:14 - Unicode processing
switch ("🧟♀️💖🧠", "The Brain Cafe\u{301}") { case (/.\N{SPARKLING HEART}./, /.*café/.ignoresCase()): print("Oh no! 🧟♀️💖🧠, but 🧠💖☕️!") default: print("No conflicts found") }
-
15:54 - Complex scalar processing
let input = "Oh no! 🧟♀️💖🧠, but 🧠💖☕️!" input.firstMatch(of: /.\N{SPARKLING HEART}./) // 🧟♀️💖🧠 input.firstMatch(of: /.\N{SPARKLING HEART}./.matchingSemantics(.unicodeScalar)) // ️💖🧠
-
17:56 - Live transaction matcher
let timestamp = Regex { ... } // proprietary let details = try Regex(inputString) let amountMatcher = /[\d.]+/ // CREDIT <proprietary> <redacted> 200.23 A1B34EFF ... let fieldSeparator = /\s{2,}|\t/ let transactionMatcher = Regex { Capture { /CREDIT|DEBIT/ } fieldSeparator Capture { timestamp } fieldSeparator Capture { details } fieldSeparator // ... }
-
18:26 - Replace field separator
let field = OneOrMore { NegativeLookahead { fieldSeparator } CharacterClass.any }
-
18:55 - Use TryCapture
// CREDIT <proprietary> <redacted> 200.23 A1B34EFF ... let fieldSeparator = /\s{2,}|\t/ let field = OneOrMore { NegativeLookahead { fieldSeparator } CharacterClass.any } let transactionMatcher = Regex { Capture { /CREDIT|DEBIT/ } fieldSeparator TryCapture(field) { timestamp ~= $0 ? $0 : nil } fieldSeparator TryCapture(field) { details ~= $0 ? $0 : nil } fieldSeparator // ... }
-
21:45 - Fixing the scaling issues
// CREDIT <proprietary> <redacted> 200.23 A1B34EFF ... let fieldSeparator = Local { /\s{2,}|\t/ } let field = OneOrMore { NegativeLookahead { fieldSeparator } CharacterClass.any } let transactionMatcher = Regex { Capture { /CREDIT|DEBIT/ } fieldSeparator TryCapture(field) { timestamp ~= $0 ? $0 : nil } fieldSeparator TryCapture(field) { details ~= $0 ? $0 : nil } fieldSeparator // ... }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.