在Python中从数字列表打印格式化的范围字符串
我写了这个类,用来把数字列表压缩成序列字符串,如果步长大于1的话,还会包括步长的值。不过我觉得代码还是有点笨重。有没有什么库可以做到类似的事情?或者说,能不能用更简单的代码来实现?
import re
class Foo( object ):
def __init__( self, num_list ):
self.num_list = sorted( list( set( [ int(n) for n in num_list ] ) ) )
# end def __init__
def gen_seq_data( self ):
self.seq_data = list()
index_offset = None
backward_step_value = None
forward_step_value = None
sub_list = list()
sub_list_step_value = None
for index, num in enumerate( self.num_list ):
if index - 1 < 0:
backward_step_value = None
# end if
else:
backward_step_value = num - self.num_list[ index - 1 ]
# end else
try:
forward_step_value = self.num_list[ index + 1 ] - num
# end try
except IndexError:
forward_step_value = None
# end except
if backward_step_value is None:
sub_list.append( num )
# end if
elif backward_step_value == forward_step_value:
sub_list.append( num )
if forward_step_value is None:
self.seq_data.append( ( sub_list_step_value, sub_list ) )
# end if
# end if
elif backward_step_value == sub_list_step_value:
sub_list.append( num )
if sub_list:
self.seq_data.append( ( sub_list_step_value, sub_list ) )
# end if
sub_list = list()
# end elif
else:
if sub_list:
self.seq_data.append( ( sub_list_step_value, sub_list ) )
# end if
sub_list = [ num ]
if forward_step_value is None:
self.seq_data.append( ( sub_list_step_value, sub_list ) )
# end if
# end else
try:
sub_list_step_value = sub_list[ -1 ] - sub_list[ -2 ]
# end try
except IndexError:
sub_list_step_value = None
# end except
# end for
# end def gen_seq_object
def format_elements( self ):
format_elements = list()
for step, num_list in self.seq_data:
if step is None:
format_elements.append( '%s' % ( num_list[ 0 ] ) )
# end if
elif step == 1:
format_elements.append( '%s-%s' % ( num_list[ 0 ], num_list[ -1 ] ) )
# end elif
else:
format_elements.append( '%s-%sx%s' % ( num_list[ 0 ], num_list[ -1 ], step ) )
# end else
# end for
return format_elements
# end def format_range
def format_range( self ):
return ','.join( self.format_elements() )
# end def format_range
def expand_range( self ):
num_list = list()
for r_token in self.format_range().split( ',' ):
if r_token.isdigit():
num_list.append( int( r_token ) )
# end if
elif '-' in r_token:
if 'x' in r_token:
start, end, step = re.split( r'[-|x]', r_token )
num_list.extend( range( int( start ), int( end ) + 1, int( step ) ) )
# end if
else:
start, end = r_token.split( '-' )
num_list.extend( range( int( start ), int( end ) + 1 ) )
# end else
# end elif
# end for
return num_list
# end def expand_range
# end class Foo
输入/输出:
data = [ 1, 4, 5, 6, 10, 15, 16, 17, 18, 20, 22, 24, 26, 27, 28, 30, 35, 40, 45, 50, 56, 63, 66, 69, 72 ]
foo = Foo( data )
foo.gen_seq_data()
print data
print foo.format_range()
1,4-6,10,15-18,20-26x2,27,28,30-50x5,56,63-72x3
print foo.expand_range()
[1, 4, 5, 6, 10, 15, 16, 17, 18, 20, 22, 24, 26, 27, 28, 30, 35, 40, 45, 50, 56, 63, 66, 69, 72]
2 个回答
1
下面这个解决方案可以处理不连续的范围,同时也保持了忽略长度为2的范围的特性。
def reduce_list(seq):
l = sorted(set(seq))
low = high = l[0]
step = None
for v in l[1:]:
if step is None or v - high == step:
# Extend the current range.
step = v - high
high = v
elif high - low == step:
# The current range only has two values. Yield the
# first value, and start a new range comprising the
# second value and the current value.
yield low, low, None
step = v - high
low = high
high = v
else:
# Yield the current range, and start a new one.
yield low, high, step
low = high = v
step = None
if high - low == step:
# The final range has only two values. Yield them
# individually.
yield low, low, None
step = None
low = high
yield low, high, step
def format_element(low, high, step):
if step is None:
assert low == high
return "%s" % (low,)
elif step == 1:
return "%s-%s" % (low, high)
else:
return "%s-%sx%s" % (low, high, step)
def format_list(seq):
return ','.join(format_element(*e) for e in seq)
这里有一些测试代码:
def test( *args ):
print args, "==", format_list(reduce_list(args))
test(1)
test(1, 2)
test(1, 2, 3)
test(0, 10)
test(0, 10, 20)
test(0, 10, 11, 12, 14, 16)
test(0, 2, 4, 8, 16, 32, 64)
test(0, 1, 3, 4, 6, 7, 9, 10)
test(0, 1, 3, 6, 10, 15, 21, 28)
它的输出结果是:
(1,) == 1
(1, 2) == 1,2
(1, 2, 3) == 1-3
(0, 10) == 0,10
(0, 10, 20) == 0-20x10
(0, 10, 11, 12, 14, 16) == 0,10-12,14,16
(0, 2, 4, 8, 16, 32, 64) == 0-4x2,8,16,32,64
(0, 1, 3, 4, 6, 7, 9, 10) == 0,1,3,4,6,7,9,10
(0, 1, 3, 6, 10, 15, 21, 28) == 0,1,3,6,10,15,21,28
2
第一点:去掉所有的 #END 注释。这些注释完全没有用。你的缩进已经说明了一切,直接用它就行。
第二点:别把这个做成一个类。它并不是一个有独特职责的独立对象。它只是一个算法,由一些函数组成。最多也就是一个全是静态方法的类。
第三点:绝对不要这样做
for index, num in enumerate( self.num_list ):
if index - 1 < 0:
backward_step_value = None
# end if
else:
backward_step_value = num - self.num_list[ index - 1 ]
# end else
如果第一个元素特别,那就单独处理它。
backward_step_value = self.num_list[0]
for num in self.num_list[1:]:
你很少需要像这样的索引。实际上,唯一需要索引的原因似乎就是为了特别处理第一个元素。
最后,这是一种“归约”。使用生成器函数
def reduce_list( some_list ):
v= min(some_list)
low, high = v, v
for v in sorted(some_list)[1:]:
if v == high+1:
high= high+1
else:
yield low, high
yield low, high
这可以生成你需要的连续范围列表。然后你可以对这些进行格式化。
format_elements( reduce_list( some_list ) )