mirror of
https://github.com/TheFunny/ArisuAutoSweeper
synced 2026-10-18 14:22:16 +00:00
fix: improve deep_get, deep_set, deep_pop, and deep_iter functions for better error handling and performance
This commit is contained in:
+138
-50
@@ -2,6 +2,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
from collections import deque
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
@@ -190,57 +191,95 @@ def alas_instance():
|
|||||||
|
|
||||||
|
|
||||||
def deep_get(d, keys, default=None):
|
def deep_get(d, keys, default=None):
|
||||||
"""
|
# 240 + 30 * depth (ns)
|
||||||
Get values in dictionary safely.
|
if type(keys) is str:
|
||||||
https://stackoverflow.com/questions/25833613/safe-method-to-get-value-of-nested-dictionary
|
|
||||||
|
|
||||||
Args:
|
|
||||||
d (dict):
|
|
||||||
keys (str, list): Such as `Scheduler.NextRun.value`
|
|
||||||
default: Default return if key not found.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
if isinstance(keys, str):
|
|
||||||
keys = keys.split('.')
|
keys = keys.split('.')
|
||||||
assert type(keys) is list
|
|
||||||
if d is None:
|
try:
|
||||||
return default
|
for k in keys:
|
||||||
if not keys:
|
d = d[k]
|
||||||
return d
|
return d
|
||||||
return deep_get(d.get(keys[0]), keys[1:], default)
|
# No such key
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
# Input `keys` is not iterable or input `d` is not dict
|
||||||
|
except TypeError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
def deep_set(d, keys, value):
|
def deep_set(d, keys, value):
|
||||||
"""
|
"""
|
||||||
Set value into dictionary safely, imitating deep_get().
|
Set value into dictionary safely, imitating deep_get().
|
||||||
"""
|
"""
|
||||||
if isinstance(keys, str):
|
# 150 * depth (ns)
|
||||||
|
if type(keys) is str:
|
||||||
keys = keys.split('.')
|
keys = keys.split('.')
|
||||||
assert type(keys) is list
|
|
||||||
if not keys:
|
first = True
|
||||||
return value
|
exist = True
|
||||||
if not isinstance(d, dict):
|
prev_d = None
|
||||||
d = {}
|
prev_k = None
|
||||||
d[keys[0]] = deep_set(d.get(keys[0], {}), keys[1:], value)
|
prev_k2 = None
|
||||||
return d
|
try:
|
||||||
|
for k in keys:
|
||||||
|
if first:
|
||||||
|
prev_d = d
|
||||||
|
prev_k = k
|
||||||
|
first = False
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
# if key in dict: dict[key] > dict.get > dict.setdefault > try dict[key] except
|
||||||
|
if exist and prev_k in d:
|
||||||
|
prev_d = d
|
||||||
|
d = d[prev_k]
|
||||||
|
else:
|
||||||
|
exist = False
|
||||||
|
new = {}
|
||||||
|
d[prev_k] = new
|
||||||
|
d = new
|
||||||
|
except TypeError:
|
||||||
|
# `d` is not dict
|
||||||
|
exist = False
|
||||||
|
d = {}
|
||||||
|
prev_d[prev_k2] = {prev_k: d}
|
||||||
|
|
||||||
|
prev_k2 = prev_k
|
||||||
|
prev_k = k
|
||||||
|
# prev_k2, prev_k = prev_k, k
|
||||||
|
# Input `keys` is not iterable
|
||||||
|
except TypeError:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Last key, set value
|
||||||
|
try:
|
||||||
|
d[prev_k] = value
|
||||||
|
return
|
||||||
|
# Last value `d` is not dict
|
||||||
|
except TypeError:
|
||||||
|
prev_d[prev_k2] = {prev_k: value}
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
def deep_pop(d, keys, default=None):
|
def deep_pop(d, keys, default=None):
|
||||||
"""
|
if type(keys) is str:
|
||||||
Pop value from dictionary safely, imitating deep_get().
|
|
||||||
"""
|
|
||||||
if isinstance(keys, str):
|
|
||||||
keys = keys.split('.')
|
keys = keys.split('.')
|
||||||
assert type(keys) is list
|
|
||||||
if not isinstance(d, dict):
|
try:
|
||||||
|
for k in keys[:-1]:
|
||||||
|
d = d[k]
|
||||||
|
return d.pop(keys[-1], default)
|
||||||
|
# No such key
|
||||||
|
except KeyError:
|
||||||
return default
|
return default
|
||||||
if not keys:
|
# Input `keys` is not iterable or input `d` is not dict
|
||||||
|
except TypeError:
|
||||||
|
return default
|
||||||
|
# Input `keys` out of index
|
||||||
|
except IndexError:
|
||||||
|
return default
|
||||||
|
# Last `d` is not dict
|
||||||
|
except AttributeError:
|
||||||
return default
|
return default
|
||||||
elif len(keys) == 1:
|
|
||||||
return d.pop(keys[0], default)
|
|
||||||
return deep_pop(d.get(keys[0]), keys[1:], default)
|
|
||||||
|
|
||||||
|
|
||||||
def deep_default(d, keys, value):
|
def deep_default(d, keys, value):
|
||||||
@@ -262,26 +301,75 @@ def deep_default(d, keys, value):
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
def deep_iter(data, depth=0, current_depth=1):
|
def deep_iter(data, min_depth=None, depth=3):
|
||||||
"""
|
"""
|
||||||
Iter a dictionary safely.
|
300us on alas.json depth=3 (530+ rows)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data (dict):
|
data:
|
||||||
depth (int): Maximum depth to iter
|
min_depth:
|
||||||
current_depth (int):
|
depth:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list: Key path
|
|
||||||
Any:
|
|
||||||
"""
|
"""
|
||||||
if isinstance(data, dict) \
|
if min_depth is None:
|
||||||
and (depth and current_depth <= depth):
|
min_depth = depth
|
||||||
for key, value in data.items():
|
assert 1 <= min_depth <= depth
|
||||||
for child_path, child_value in deep_iter(value, depth=depth, current_depth=current_depth + 1):
|
|
||||||
yield [key] + child_path, child_value
|
# Equivalent to dict.items()
|
||||||
else:
|
try:
|
||||||
yield [], data
|
if depth == 1:
|
||||||
|
for k, v in data.items():
|
||||||
|
yield [k], v
|
||||||
|
return
|
||||||
|
# Iter first depth
|
||||||
|
elif min_depth == 1:
|
||||||
|
q = deque()
|
||||||
|
for k, v in data.items():
|
||||||
|
key = [k]
|
||||||
|
if type(v) is dict:
|
||||||
|
q.append((key, v))
|
||||||
|
else:
|
||||||
|
yield key, v
|
||||||
|
# Iter target depth only
|
||||||
|
else:
|
||||||
|
q = deque()
|
||||||
|
for k, v in data.items():
|
||||||
|
key = [k]
|
||||||
|
if type(v) is dict:
|
||||||
|
q.append((key, v))
|
||||||
|
except AttributeError:
|
||||||
|
# `data` is not dict
|
||||||
|
return
|
||||||
|
|
||||||
|
# Iter depths
|
||||||
|
current = 2
|
||||||
|
while current <= depth:
|
||||||
|
new_q = deque()
|
||||||
|
# max depth
|
||||||
|
if current == depth:
|
||||||
|
for key, data in q:
|
||||||
|
for k, v in data.items():
|
||||||
|
yield key + [k], v
|
||||||
|
# in target depth
|
||||||
|
elif min_depth <= current < depth:
|
||||||
|
for key, data in q:
|
||||||
|
for k, v in data.items():
|
||||||
|
subkey = key + [k]
|
||||||
|
if type(v) is dict:
|
||||||
|
new_q.append((subkey, v))
|
||||||
|
else:
|
||||||
|
yield subkey, v
|
||||||
|
# Haven't reached min depth
|
||||||
|
else:
|
||||||
|
for key, data in q:
|
||||||
|
for k, v in data.items():
|
||||||
|
subkey = key + [k]
|
||||||
|
if type(v) is dict:
|
||||||
|
new_q.append((subkey, v))
|
||||||
|
q = new_q
|
||||||
|
current += 1
|
||||||
|
|
||||||
|
|
||||||
def parse_value(value, data):
|
def parse_value(value, data):
|
||||||
|
|||||||
Reference in New Issue
Block a user