Coverage for partitionsets/cli.py: 82.31%
82 statements
« prev ^ index » next coverage.py v7.0.3, created at 2023-01-07 17:50 +0100
« prev ^ index » next coverage.py v7.0.3, created at 2023-01-07 17:50 +0100
1"""
2Module that contains the command line app.
4Why does this file exist, and why not put this in __main__?
6 You might be tempted to import things from __main__ later,
7 but that will cause problems: the code will get executed twice:
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``.
15 Also see (1) from http://click.pocoo.org/5/setuptools/#setuptools-integration
16"""
17from __future__ import print_function
19import argparse
20import json
21import sys
23from partitionsets import ordered_set, partition
26def parse_args(argv=None):
27 """DRY and KISS."""
29 parser = argparse.ArgumentParser(description='partitioning of small sets with 25 or less members')
30 group = parser.add_mutually_exclusive_group()
31 # group.add_argument('-v', '--verbose', action='store_true')
32 group.add_argument('-q', '--quiet', action='store_true')
33 group.add_argument('-v', '--verbosity', action='count', default=0, help='increase output verbosity')
34 parser.add_argument(
35 '-o',
36 '--out-filename',
37 action='store',
38 nargs=1,
39 help='out file name if specified, else all sent to stdout',
40 required=False,
41 )
42 parser.add_argument(
43 '-T',
44 '--type',
45 type=str,
46 choices=['text', 'csv', 'json'],
47 default='text',
48 help='type of output (format), defaults to text',
49 )
50 parser.add_argument('-b', '--bell-numbers', action='store_true', help='export the Bell numbers known by package')
51 parser.add_argument(
52 '-m', '--multi-set', action='store_true', help='handle elements as being part of a multiset or bag'
53 )
55 parser.add_argument('element', nargs='+', help='define set as list of elements separated by spaces')
57 return parser.parse_args(argv)
60def show_bell_numbers(args, bells):
61 """Loosely coupled functionality."""
63 if not args.type or args.type == 'text': 63 ↛ 66line 63 didn't jump to line 66, because the condition on line 63 was never false
64 for ndx in range(0, len(bells)):
65 print('%2d: %20d' % (ndx, bells[ndx]))
66 elif args.type == 'json':
67 p_map = {'d': {}}
68 p_map['d']['bellNumbers'] = []
69 for ndx in range(0, len(bells)):
70 p_map['d']['bellNumbers'].append(bells[ndx])
71 print(json.dumps(p_map))
72 else:
73 # must be args.type == 'csv' since choices are checked beforehand
74 for ndx in range(0, len(bells)):
75 print('%d,%d' % (ndx, bells[ndx]))
76 sys.exit(0)
79def main(argv=None):
80 """
81 Args:
82 argv (list): List of arguments
84 Returns:
85 int: A return code
87 Partitioning of small sets with 25 or less members as given
88 per command line arguments. A set may **not** contain
89 multiple instances of a single element.
91 References:
93 [A0001101]: "Bell or exponential numbers: ways of placing n labeled balls
94 into n indistinguishable boxes." at http://oeis.org/A000110
96 [BellNumber]: Wikipedia entry Bell_number
97 at http://en.wikipedia.org/wiki/Bell_number
99 [OEIS]: Wikipedia entry On-Line_Encyclopedia_of_Integer_Sequences at
100 http://en.wikipedia.org/wiki/On-Line_Encyclopedia_of_Integer_Sequences
102 [PartOfASet_WP]: Wikipedia entry Partition_of_a_set at
103 http://en.wikipedia.org/wiki/Partition_of_a_set
104 """
105 argv = sys.argv if argv is None else argv
107 bells = [
108 1,
109 2,
110 5,
111 15,
112 52,
113 203,
114 877,
115 4140,
116 21147,
117 115975,
118 678570,
119 4213597,
120 27644437,
121 190899322,
122 1382958545,
123 10480142147,
124 82864869804,
125 682076806159,
126 5832742205057,
127 51724158235372,
128 474869816156751,
129 4506715738447323,
130 44152005855084346,
131 445958869294805289,
132 4638590332229999353,
133 49631246523618756274,
134 ]
136 n_bells_ok = 25 # could be len(bells), but overflows forbid that :-)
137 args = parse_args(argv)
139 out_file = False
140 if args.out_filename:
141 out_file = ''.join(args.out_filename)
142 print('Requested file ' + out_file + ' for output is ignored' ' in this version, sorry.')
143 if args.bell_numbers:
144 show_bell_numbers(args, bells) # will not return
145 if args.element: 145 ↛ 150line 145 didn't jump to line 150, because the condition on line 145 was never false
146 if args.multi_set: 146 ↛ 147line 146 didn't jump to line 147, because the condition on line 146 was never true
147 an_xset = list(' '.join(args.element).split(' '))
148 else:
149 an_xset = ordered_set.OrderedSet(list(' '.join(args.element).split(' ')))
150 if args.verbosity >= 2:
151 print(
152 'Even a small class, such as {%s},'
153 ' can be partitioned in a surprising'
154 ' number of different ways:' % (', '.join(an_xset),)
155 )
156 n_x_s = len(an_xset)
157 bell_number = bells[n_x_s - 1] if n_x_s < n_bells_ok else bells[n_bells_ok]
158 if n_x_s > n_bells_ok: 158 ↛ 159line 158 didn't jump to line 159, because the condition on line 158 was never true
159 print(
160 'Error: Not prepared for %d partitions.'
161 ' Sorry. Please use %d members or less.'
162 ''
163 % (
164 n_x_s,
165 n_bells_ok,
166 )
167 )
168 sys.exit(1)
169 a_partition = partition.Partition(an_xset)
170 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)
183 if args.verbosity:
184 print(
185 ' ' + '=> (Number of partitions = %d,'
186 ' expected is %d)'
187 % (
188 len(a_partition),
189 bell_number,
190 )
191 )
192 if args.verbosity >= 3:
193 print(
194 'Procedure class-partitions takes one argument, a'
195 ' finite class C(members separated by one or more'
196 ' spaces) and returns an itemized list like above'
197 ' containing all partitions of C. (Thus the result'
198 ' is a class of classes of classes of members'
199 ' of C.)'
200 )
202 elif args.type == 'json':
203 p_map = {'d': {}}
204 if args.verbosity: 204 ↛ 205line 204 didn't jump to line 205
205 p_map['d']['__metadata'] = {
206 'set': list(an_xset),
207 'bellNumber': bell_number,
208 'numberOfPartitions': len(a_partition),
209 }
210 p_map['d']['partitions'] = []
211 for a_part in a_partition:
212 p_map['d']['partitions'].append(a_part)
213 print(json.dumps(p_map))
214 else: # must be args.type == 'csv' since choices are checked beforehand
215 for a_part in a_partition:
216 n_p_s = len(a_part)
217 r_map = []
218 for a_p in a_part:
219 r_map.append(' '.join(a_p))
220 r_map += ['' for __ in range(n_p_s, n_x_s)]
221 print(','.join(r_map))
223 return 0