2021. 9. 13. 13:27ㆍProgramming/Flutter
변수를 사용한 정규표현식의 활용
이번에는 정규표현식을 활용해서 1과 0으로 이루어진 문자열을 압축하는(?) 로직을 작성해보자. 나는 데이터가 기록된 문자열에서 시간을 파싱할 때 이 방법을 사용했었다. 예를 들어서 00시부터 24시까지의 문자열이 주어진다고 치면, 전체 문자열의 길이는 144자이며 한 문자는 10분의 단위시간을 가지게 된다. 정규표현식을 사용하여 문자열이 매칭되는 시작 지점과 끝 지점의 인덱스를 알아내면, 문자열로부터 데이터가 위치한 시간을 계산해낼 수 있는 셈이다.
아무튼 오늘 해볼 것은 0과 1로 이루어진 문자열에서 match를 사용하여 시작 지점과 끝 지점을 구하고, 구간 형태의 데이터로 분리해보자. 뭐, 딱히 쓸데는 없다. ' ㅈ';
문자열 만들기
우선은 0과 1로 구성된 문자열을 만들어보자. 간단하게 0000111100001111과 같은 형태로 문자열을 만드는 것도 좋지만, 이번에는 dart:math
패키지에 있는 Random
을 사용해서 임의의 문자열을 만들어보자.
import 'dart:math';
Random random = Random();
String schedule = List.generate(144, (index) => random.nextInt(2)).join();
이걸로 144자의 0과 1로 만들어진 문자열이 만들어졌다. 이걸로 0000000111111처럼 연속된 0과 1로 이루어진 문자열과 결과를 비교해보면, 이 압축 결과물(?)의 효과를 좀 더 시각적으로 볼 수 있지 않을까.
정규표현식과 값으로 이루어진 클래스 만들기
다음은 정규표현식과 값으로 이루어진 클래스를 만들어보자. 별다른 건 아니고 문자열에서 0이나 1로 이루어진 문자열을 검출하기 위해서는 r'([0]+)'
, r'([1]+)'
과 같은 정규 표현식을 선언해줘야한다. 지금은 검출해야하는 값이 0과 1 둘 뿐이니 단순 선언하더라도 상관없지만, 검출해야하는 값이 늘어난다면 상당히 귀찮은 일이 될 것이다. 게다가 검출할 때 for([초기값];[조건];[증감문])
형식으로 만들면 index를 기반으로 값을 산출하는 게 가능하긴 하지만, 21세기가 시작된지 어느덧 21년이 지난 마당에 컬렉션에서 제공해주는 메서드를 사용하지 않는 건 어쩐지 손해보는 느낌이다. 그런 이유로 각 구간과 구간의 값을 저장하기 위한 클래스를 만들어주자.
class RegExpWithValue<T> {
late final RegExp regexp;
final T value;
RegExpWithValue(this.value) {
regexp = RegExp(r'([' + value.toString() + ']+)');
}
}
위의 코드를 살펴보자. 생성자에서 받은 값을 기반으로 정규표현식을 생성하므로, _regexp값은 late로 선언해준다. _regexp와 value의 값은 변경될 일이 없으므로 final로 선언했다.
List<RegExpWithValue> list = List.generate(
2,
(index) =>
RegExpWithValue(index)
);
이제 위에서 작성한 데이터 클래스 RegExpWithValue
를 기반으로 List<RegExpWithValue>
를 생성하자. 이 정규표현식 리스트를 순회하면서, 문자열로부터 값을 검출하면 된다.
구간에 대한 값을 저장하는 데이터 클래스 만들기
알고리즘 문제였다면 검출하고자 하는 값 0, 1과 길이값을 붙여서 하나의 문자열로 만들었을 것이다. 00001111이라면 0414로 나타내는 식으로 말이다. 애시당초 그럴거면 정규표현식을 쓰는 것 보다 문자열을 처음부터 끝까지 순회하는게 더 낫기 때문에... 이번에는 시작 위치, 끝 위치, 저장된 값으로 구성된 클래스를 만들어서 값을 저장하도록 하자.
class Zip {
final int start;
final int end;
final int value;
const Zip(this.start, this.end, this.value);
}
너무 심플해서 별도로 설명할 필요도 없다.
정규표현식 리스트를 순회하면서 값 검출하기
위에서 만든 정규표현식 리스트 list를 순회하면서 값을 검출하여 Zip 객체로 파싱하고, 완성된 List<Zip>
을 start값을 기준으로 정렬하는 코드다.
List<Zip> result = list.fold<List<Zip>>([], (cons,
regexpWithValue) {
cons.addAll(regexpWithValue.regexp.allMatches(schedule).map((match) => Zip(match.start, match.end, regexpWithValue.value)));
return cons;
})
..sort((zipA, zipB) => zipA.start - zipB.start);
for (var zip in result) {
print("${zip.start}~${zip.end}: ${zip.value}");
}
마지막에서는 for...in
을 사용하여 List<Zip>
을 순회하며 내용을 출력하고 있다.
정리
전체 코드는 아래와 같다.
import 'dart:math';
class Zip {
final int start;
final int end;
final int value;
const Zip(this.start, this.end, this.value);
}
class RegExpWithValue<T> {
late final RegExp regexp;
final T value;
RegExpWithValue(this.value) {
regexp = RegExp(r'([' + value.toString() + ']+)');
}
}
void main() {
Random random = Random();
String schedule = List.generate(144, (index) => random.nextInt(2)).join();
List<RegExpWithValue> list = List.generate(
2,
(index) =>
RegExpWithValue(index)
);
List<Zip> result = list.fold<List<Zip>>([], (cons, regexpWithValue) {
cons.addAll(regexpWithValue.regexp.allMatches(schedule).map((match) => Zip(match.start, match.end, regexpWithValue.value)));
return cons;
})
..sort((zipA, zipB) => zipA.start - zipB.start);
for (var zip in result) {
print("${zip.start}~${zip.end}: ${zip.value}");
}
}
이번 글에서 정규표현식을 사용하는 방법은 사실상 뻘짓에 가깝지만, 정규표현식을 사용해서 문자열을 검출하는 건 생각보다 쉽고, 편리하며, 여러가지로 도움이 된다. RegExp.match
, RegExp.allMatch
를 사용하여 검출하고자 하는 문자열의 start
, end
값을 얻은 다음 다른 형태로 데이터를 가공할 수 있다는 점만 짚고 넘어가도록 하자. ' ㅂ')
'Programming > Flutter' 카테고리의 다른 글
[Flutter] Flutter에서 SharedPreferences에 저장한 값을, Android 네이티브 영역에서 참조해보자. (0) | 2021.10.28 |
---|---|
[Flutter] firebase_messaging과 flutter_local_notifications을 사용한 푸시 알림 기능 구현 (0) | 2021.10.18 |
[Flutter/Dart] 확장 함수를 사용한 리스트 중복 체크 (0) | 2021.09.13 |
[Flutter] Map에 확장함수(Extension method)를 만들어보자 (0) | 2021.08.19 |
[Flutter] 여러개의 비동기 처리에 대한 경쟁 상태의 처리 (0) | 2021.07.27 |