Coverage for partitionsets/cli.py: 82.03%

82 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-02-04 22:17 +0100

1""" 

2Module that contains the command line app. 

3 

4Why does this file exist, and why not put this in __main__? 

5 

6 You might be tempted to import things from __main__ later, 

7 but that will cause problems: the code will get executed twice: 

8 

9 - When you run `python -mpartitionsets` python will execute 

10 ``__main__.py`` as a script. That means there won't be any 

11 ``partitionsets.__main__`` in ``sys.modules``. 

12 - When you import __main__ it will get executed again (as a module) because 

13 there's no ``partitionsets.__main__`` in ``sys.modules``. 

14 

15 Also see (1) from http://click.pocoo.org/5/setuptools/#setuptools-integration 

16""" 

17 

18from __future__ import print_function 

19 

20import argparse 

21import json 

22import sys 

23 

24from partitionsets import ordered_set, partition 

25 

26 

27def parse_args(argv=None): 

28 """DRY and KISS.""" 

29 

30 parser = argparse.ArgumentParser(description='partitioning of small sets with 25 or less members') 

31 group = parser.add_mutually_exclusive_group() 

32 # group.add_argument('-v', '--verbose', action='store_true') 

33 group.add_argument('-q', '--quiet', action='store_true') 

34 group.add_argument('-v', '--verbosity', action='count', default=0, help='increase output verbosity') 

35 parser.add_argument( 

36 '-o', 

37 '--out-filename', 

38 action='store', 

39 nargs=1, 

40 help='out file name if specified, else all sent to stdout', 

41 required=False, 

42 ) 

43 parser.add_argument( 

44 '-T', 

45 '--type', 

46 type=str, 

47 choices=['text', 'csv', 'json'], 

48 default='text', 

49 help='type of output (format), defaults to text', 

50 ) 

51 parser.add_argument('-b', '--bell-numbers', action='store_true', help='export the Bell numbers known by package') 

52 parser.add_argument( 

53 '-m', '--multi-set', action='store_true', help='handle elements as being part of a multiset or bag' 

54 ) 

55 

56 parser.add_argument('element', nargs='+', help='define set as list of elements separated by spaces') 

57 

58 return parser.parse_args(argv) 

59 

60 

61def show_bell_numbers(args, bells): 

62 """Loosely coupled functionality.""" 

63 

64 if not args.type or args.type == 'text': 64 ↛ 67line 64 didn't jump to line 67, because the condition on line 64 was never false

65 for ndx in range(0, len(bells)): 

66 print('%2d: %20d' % (ndx, bells[ndx])) 

67 elif args.type == 'json': 

68 p_map = {'d': {}} 

69 p_map['d']['bellNumbers'] = [] 

70 for ndx in range(0, len(bells)): 

71 p_map['d']['bellNumbers'].append(bells[ndx]) 

72 print(json.dumps(p_map)) 

73 else: 

74 # must be args.type == 'csv' since choices are checked beforehand 

75 for ndx in range(0, len(bells)): 

76 print('%d,%d' % (ndx, bells[ndx])) 

77 sys.exit(0) 

78 

79 

80def main(argv=None): 

81 """ 

82 Args: 

83 argv (list): List of arguments 

84 

85 Returns: 

86 int: A return code 

87 

88 Partitioning of small sets with 25 or less members as given 

89 per command line arguments. A set may **not** contain 

90 multiple instances of a single element. 

91 

92 References: 

93 

94 [A0001101]: "Bell or exponential numbers: ways of placing n labeled balls 

95 into n indistinguishable boxes." at http://oeis.org/A000110 

96 

97 [BellNumber]: Wikipedia entry Bell_number 

98 at http://en.wikipedia.org/wiki/Bell_number 

99 

100 [OEIS]: Wikipedia entry On-Line_Encyclopedia_of_Integer_Sequences at 

101 http://en.wikipedia.org/wiki/On-Line_Encyclopedia_of_Integer_Sequences 

102 

103 [PartOfASet_WP]: Wikipedia entry Partition_of_a_set at 

104 http://en.wikipedia.org/wiki/Partition_of_a_set 

105 """ 

106 argv = sys.argv if argv is None else argv 

107 

108 bells = [ 

109 1, 

110 2, 

111 5, 

112 15, 

113 52, 

114 203, 

115 877, 

116 4140, 

117 21147, 

118 115975, 

119 678570, 

120 4213597, 

121 27644437, 

122 190899322, 

123 1382958545, 

124 10480142147, 

125 82864869804, 

126 682076806159, 

127 5832742205057, 

128 51724158235372, 

129 474869816156751, 

130 4506715738447323, 

131 44152005855084346, 

132 445958869294805289, 

133 4638590332229999353, 

134 49631246523618756274, 

135 ] 

136 

137 n_bells_ok = 25 # could be len(bells), but overflows forbid that :-) 

138 args = parse_args(argv) 

139 

140 out_file = False 

141 if args.out_filename: 

142 out_file = ''.join(args.out_filename) 

143 print('Requested file ' + out_file + ' for output is ignored' ' in this version, sorry.') 

144 if args.bell_numbers: 

145 show_bell_numbers(args, bells) # will not return 

146 if args.element: 146 ↛ 151line 146 didn't jump to line 151, because the condition on line 146 was never false

147 if args.multi_set: 147 ↛ 148line 147 didn't jump to line 148, because the condition on line 147 was never true

148 an_xset = list(' '.join(args.element).split(' ')) 

149 else: 

150 an_xset = ordered_set.OrderedSet(list(' '.join(args.element).split(' '))) 

151 if args.verbosity >= 2: 

152 print( 

153 'Even a small class, such as {%s},' 

154 ' can be partitioned in a surprising' 

155 ' number of different ways:' % (', '.join(an_xset),) 

156 ) 

157 n_x_s = len(an_xset) 

158 bell_number = bells[n_x_s - 1] if n_x_s < n_bells_ok else bells[n_bells_ok] 

159 if n_x_s > n_bells_ok: 159 ↛ 160line 159 didn't jump to line 160, because the condition on line 159 was never true

160 print( 

161 'Error: Not prepared for %d partitions.' 

162 ' Sorry. Please use %d members or less.' 

163 '' 

164 % ( 

165 n_x_s, 

166 n_bells_ok, 

167 ) 

168 ) 

169 sys.exit(1) 

170 a_partition = partition.Partition(an_xset) 

171 if not args.type or args.type == 'text': 

172 for a_part in a_partition: 

173 d_part = repr(a_part) 

174 if args.verbosity >= 1: 

175 d_part = d_part.replace('[', '{').replace(']', '}') 

176 if args.verbosity >= 2: 

177 d_part = d_part.replace("'", '') 

178 if args.verbosity: 

179 print(' ' + '* ' + d_part) 

180 else: 

181 print(d_part) 

182 if args.verbosity: 

183 print( 

184 ' ' + '=> (Number of partitions = %d,' 

185 ' expected is %d)' 

186 % ( 

187 len(a_partition), 

188 bell_number, 

189 ) 

190 ) 

191 if args.verbosity >= 3: 

192 print( 

193 'Procedure class-partitions takes one argument, a' 

194 ' finite class C(members separated by one or more' 

195 ' spaces) and returns an itemized list like above' 

196 ' containing all partitions of C. (Thus the result' 

197 ' is a class of classes of classes of members' 

198 ' of C.)' 

199 ) 

200 elif args.type == 'json': 

201 p_map = {'d': {}} 

202 if args.verbosity: 202 ↛ 203line 202 didn't jump to line 203

203 p_map['d']['__metadata'] = { 

204 'set': list(an_xset), 

205 'bellNumber': bell_number, 

206 'numberOfPartitions': len(a_partition), 

207 } 

208 p_map['d']['partitions'] = [] 

209 for a_part in a_partition: 

210 p_map['d']['partitions'].append(a_part) 

211 print(json.dumps(p_map)) 

212 else: # must be args.type == 'csv' since choices are checked beforehand 

213 for a_part in a_partition: 

214 n_p_s = len(a_part) 

215 r_map = [] 

216 for a_p in a_part: 

217 r_map.append(' '.join(a_p)) 

218 r_map += ['' for __ in range(n_p_s, n_x_s)] 

219 print(','.join(r_map)) 

220 

221 return 0