요청 및 json 패키지를 이용하여 JSON 데이터를 파싱할 때 아래와 같이 각 키의 데이터를 사전 형식으로 변환하고 값을 변경하여 데이터를 변경한다.
data = {"key1":"value1"}
data("key1") = "value2"
특정 값을 가진 키를 찾기 위해 전체 데이터를 스캔하려고 합니다.
다음 코드로 전체를 확인(for, if 사용)하면서 원하는 값의 키를 찾았습니다.
data = {
"key1":"value1",
"key2":"value2",
"key3":"value3",
"key4":"value4",
"key5":"value5",
}
for key in data:
if "value3" == data(key):
print(key)
break
간단한 데이터의 경우 위의 찾기 방법으로 충분하지만, 데이터의 수가 매우 많거나 복잡한 경우에도 같은 방법으로 값을 찾아 변경할 수 있습니까?
아래와 같이 사람 정보가 있는 JSON 데이터에서 “-“, “N/A”, “” 값을 가진 키를 찾아 제거하고 싶은데 각각의 데이터 유형이 다르고 모양이 일정하지 않습니다. 사람의 수가 매우 많다고 가정합니다.
{
"person_1":{
"name": {"first":"David", "middle":"", "last":"Smith"},
"age":12,
"address":"-",
"education":{"highschool":"N/A","college":"-"},
"hobbies":("running", "swimming", "-"),
},
"person_2":{
"name": {"first":"Mason", "middle":"henry", "last":"Thomas"},
"age":21,
"address":"-",
"education":{"highschool":"N/A", "college":"", "Universitry":"Stanford"},
"hobbies":("coding", "-", ""),
},
...
}
먼저 위의 형식으로 임의의 양의 데이터를 생성하는 함수를 만들었습니다.
아래 이미지를 보면 패턴이 있지만 데이터가 잘 구성되어 있는 것 같습니다.
def create_random_data(num:int) -> dict:
import random
data={}
random_string = lambda x: "".join((chr(random.randint(97, 122)) for tmp in range(x)))
for i in range(num):
key = f"person_{i+1}"
data(key) = {
"name":{"first":random_string(random.randint(0, 8)), "middle":random_string(random.randint(0, 8)), "last":random_string(random.randint(0, 8))},
"age":random.randint(10, 50),
"address":"-",
"education":{"highschool":random_string(random.randint(0, 8)), "college":"", "Universitry":random_string(random.randint(0, 8))},
"hobbies":("coding", "-", "", random_string(random.randint(0, 8))),
}
return data
print(create_random_data(5))
이제 그것을 파싱하는 함수를 만들어야 하는데, 위의 데이터의 형태를 보았을 때 가장 문제가 되는 부분이 데이터의 깊이라고 생각했습니다.
데이터의 깊이에 관계없이 데이터를 반복할 수 있도록 재귀를 사용하는 구문 분석 함수를 작성해 보겠습니다.
def clean(data):
remove_keyword = ("N/A", "-", "")
for key in list(data):
value = data(key)
if type(value) == dict:
clean(value)
if value == {}:
data.pop(key)
if type(value) == list:
for x in (keyword for keyword in remove_keyword if keyword in value):
for _ in range(value.count(x)):
value.remove(x)
if type(value) == str:
if value in remove_keyword:
data.pop(key)
datas = create_random_data(5)
print(datas)
clean(datas.copy())
print(datas)
꽤 잘 정리되어 있습니다. 그런데 100만 JSON 데이터를 처리하는 데 시간이 얼마나 걸릴지 궁금해서 코드를 조금 더 수정했습니다.
https://hwan001.co.kr/178 함수의 실행 시간을 측정하는 데코레이터가 있습니다.
재귀를 사용하는 cleanup 함수는 별도로 작성해야 하지만 이 코드를 사용하여 100만 건의 생성 시간과 cleanup 시간을 측정해 보자.
def my_decorator(func):
def wrapped_func(*args):
import time
start_r = time.perf_counter()
start_p = time.process_time()
ret = func(*args)
end_r = time.perf_counter()
end_p = time.process_time()
elapsed_r = end_r - start_r
elapsed_p = end_p - start_p
print(f'{func.__name__} : {elapsed_r:.6f}sec (Perf_Counter) / {elapsed_p:.6f}sec (Process Time)')
return ret
return wrapped_func
@my_decorator
def create_random_data(num:int) -> dict:
import random
data={}
random_string = lambda x: "".join((chr(random.randint(97, 122)) for tmp in range(x)))
for i in range(num):
key = f"person_{i+1}"
data(key) = {
"name":{"first":random_string(random.randint(0, 8)), "middle":random_string(random.randint(0, 8)), "last":random_string(random.randint(0, 8))},
"age":random.randint(10, 50),
"address":"-",
"education":{"highschool":random_string(random.randint(0, 8)), "college":"", "Universitry":random_string(random.randint(0, 8))},
"hobbies":("coding", "-", "", random_string(random.randint(0, 8))),
}
return data
def clean(data):
remove_keyword = ("N/A", "", "-")
for key in list(data):
value = data(key)
if type(value) == dict:
clean(value)
if value == {}:
data.pop(key)
if type(value) == list:
for x in (keyword for keyword in remove_keyword if keyword in value):
for _ in range(value.count(x)):
value.remove(x)
if type(value) == str:
if value in remove_keyword:
data.pop(key)
datas = create_random_data(1000000)
print(len(datas), datas, "\n")
import time
start_r = time.perf_counter()
start_p = time.process_time()
clean(datas.copy())
end_r = time.perf_counter()
end_p = time.process_time()
elapsed_r = end_r - start_r
elapsed_p = end_p - start_p
print(f'{"clean"} : {elapsed_r:.6f}sec (Perf_Counter) / {elapsed_p:.6f}sec (Process Time)')
print(len(datas), datas)
데이터가 너무 길어서 키워드 갯수와 일부 데이터(키워드가 줄어들지 않음)를 찍어봤습니다.
만드는 데 129.3초, 정리하는 데 13초가 걸렸습니다. 100만 정도부터는 검색 속도가 확실히 느리게 느껴진다.
또한 샘플 데이터는 깊이가 얕아서 재귀함수는 잘 되나 깊이가 너무 크면 검색시 메모리 오류가 발생한다.
재귀가 아닌 큐나 스택을 사용하는 방식으로 바뀌어야 하고, 실제로 사용하기 위해서는 좀 더 효율적인 알고리즘이 필요할 것 같습니다.