요청 및 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)
매우 잘 정리되어 있습니다.
하지만 JSON 데이터 100만개를 처리하는 데 시간이 얼마나 걸릴지 궁금해서 코드를 조금 수정했습니다.
https://hwan001.co.kr/178 함수의 실행 시간을 측정하는 데코레이터가 있습니다.
재귀를 이용한 clean 함수는 따로 작성해야 하지만 이 코드를 사용하여 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만 정도부터는 검색 속도가 확실히 느리게 느껴진다.
그리고 예제 데이터는 깊이가 얕아서 재귀는 잘 되는데 깊이가 너무 깊으면 검색시 메모리 에러가 납니다.
재귀가 아닌 큐나 스택을 활용하는 접근 방식으로 바뀌어야 하고, 실제로 사용하려면 좀 더 효율적인 알고리즘이 필요할 것 같습니다.