Coverage for app/utility/dict.py: 100%

33 statements  

« prev     ^ index     » next       coverage.py v7.10.5, created at 2025-08-28 09:13 +0000

1"""utils module""" 

2 

3import re 

4 

5 

6def merge_dicts(*dictionaries: dict) -> dict: 

7 """Merge multiple dictionaries, keeping the first occurrence of each key. 

8 If a key appears in more than one dictionary, the value from the first dictionary containing the key will be used. 

9 

10 Example 

11 ------- 

12 >>> merge_dicts({'label': 'string'}, {'c': 'r', 'label': 'something'}) 

13 {'label': 'string', 'c': 'r'}""" 

14 

15 merged_dictionary = {} 

16 for dictionary in dictionaries: 

17 for key in dictionary.keys(): 

18 if key not in merged_dictionary: 

19 merged_dictionary[key] = dictionary[key] 

20 

21 return merged_dictionary 

22 

23 

24def filter_dicts( 

25 dicts: list[dict[str, float]], 

26 inequations: list[str], 

27 fixed: None | dict[str, float] = None, 

28) -> list[dict[str, float]]: 

29 # noinspection PyUnresolvedReferences 

30 """Filter a list of dictionaries given a list of inequations of the form "arg1 < arg2" or "arg1 > arg2" 

31 :param dicts: list of dictionaries sharing the same keys 

32 :param inequations: list of string (inequations) 

33 :param fixed: dictionary containing fixed values 

34 

35 Examples 

36 -------- 

37 >>> dicts1 = [{'a': a, 'b': b} for a, b in zip([4, 2, 8, 4], [2, 3, 4, 5])] 

38 >>> filter_dicts(dicts1, ['b < a']) 

39 [{'a': 4, 'b': 2}, {'a': 8, 'b': 4}] 

40 >>> dicts2 = [{'a': a} for a in [4, 2, 8, 4]] 

41 >>> filter_dicts(dicts2, ['a < b'], {'b': 4.1}) 

42 [{'a': 4}, {'a': 2}, {'a': 4}] 

43 >>> dicts3 = [{'a': a} for a in [4, 2, 8, 4]] 

44 >>> filter_dicts(dicts3, ['b < a'], {'b': 4.1}) 

45 [{'a': 8}]""" 

46 

47 keys = dicts[0].keys() 

48 if fixed is None: 

49 fixed = {} 

50 

51 def get_inequality_condition( 

52 value1: float | int, 

53 value2: float | int, 

54 sign: str, 

55 ) -> bool: 

56 """Get the inequality boolean depending on the value of a and b and sign 

57 :param value1: value 1 

58 :param value2: value 2 

59 :param sign: string containing '>', '<', '>=', '=>', '<=' or '=<'""" 

60 

61 operations = { 

62 ">": lambda a, b: a > b, 

63 "<": lambda a, b: a < b, 

64 ">=": lambda a, b: a >= b, 

65 "=>": lambda a, b: a >= b, 

66 "<=": lambda a, b: a <= b, 

67 "=<": lambda a, b: a <= b, 

68 } 

69 

70 return operations[sign](value1, value2) 

71 

72 for inequation in inequations: 

73 match = re.search(r"(<=|>=|=<|=>|<|>)", inequation) 

74 if not match: 

75 raise ValueError(f"Invalid inequality expression: {inequation}") 

76 ineq_sign = match.group(0) 

77 arg1, arg2 = [s.strip() for s in inequation.split(ineq_sign)] 

78 

79 if arg1 in keys and arg2 in keys: 

80 dicts = [p for p in dicts if get_inequality_condition(p[arg1], p[arg2], ineq_sign)] 

81 elif arg1 in fixed: 

82 dicts = [p for p in dicts if get_inequality_condition(fixed[arg1], p[arg2], ineq_sign)] 

83 elif arg2 in fixed: 

84 dicts = [p for p in dicts if get_inequality_condition(p[arg1], fixed[arg2], ineq_sign)] 

85 

86 return dicts 

87 

88 

89def list_to_dict(dicts: list[dict]) -> dict | None: 

90 """Convert a list of dictionaries to a dictionary of list. All dictionaries must share the same keys""" 

91 

92 new_dict = dict() 

93 for key in dicts[0]: 

94 new_dict[key] = [d[key] for d in dicts] 

95 return new_dict