Python 必须my_sample = random.sample(range(100), 10)
从[0, 100)
.
假设我已经对n
这些数字进行了采样,现在我想在不替换的情况下再采样一个(不包括任何以前采样的n
),如何超级有效地这样做?
更新:从“合理有效”更改为“超级有效”(但忽略常数因素)
Python 必须my_sample = random.sample(range(100), 10)
从[0, 100)
.
假设我已经对n
这些数字进行了采样,现在我想在不替换的情况下再采样一个(不包括任何以前采样的n
),如何超级有效地这样做?
更新:从“合理有效”更改为“超级有效”(但忽略常数因素)
如果您事先知道您想要多个不重叠的样本,最简单的方法是random.shuffle()
(list(range(100))
Python 3 - 可以跳过list()
Python 2 中的),然后根据需要剥离切片。
s = list(range(100))
random.shuffle(s)
first_sample = s[-10:]
del s[-10:]
second_sample = s[-10:]
del s[-10:]
# etc
否则@Chronial 的答案相当有效。
OP给读者的注意事项:请考虑查看最初接受的答案以理解逻辑,然后理解此答案。
Aaaaaand 为了完整起见:这是necromancer's answer的概念,但经过调整,它需要一个禁止数字列表作为输入。这与我之前的答案中的代码相同,但我们forbid
在生成数字之前从 构建状态。
O(f+k)
和记忆O(f+k)
。显然,这是对forbid
(排序/设置)格式没有要求的最快的事情。我认为这在某种程度上使它成为赢家^^。forbid
是一个集合,则重复猜测方法与 相比更快O(k⋅n/(n-(f+k)))
,O(k)
对于f+k
不是非常接近,非常接近n
。forbid
已排序,我的荒谬算法会更快:import random
def sample_gen(n, forbid):
state = dict()
track = dict()
for (i, o) in enumerate(forbid):
x = track.get(o, o)
t = state.get(n-i-1, n-i-1)
state[x] = t
track[t] = x
state.pop(n-i-1, None)
track.pop(o, None)
del track
for remaining in xrange(n-len(forbid), 0, -1):
i = random.randrange(remaining)
yield state.get(i, i)
state[i] = state.get(remaining - 1, remaining - 1)
state.pop(remaining - 1, None)
用法:
gen = sample_gen(10, [1, 2, 4, 8])
print gen.next()
print gen.next()
print gen.next()
print gen.next()
如果抽样的数量比总体少得多,只需抽样,检查它是否被选中,然后重复。这听起来可能很愚蠢,但是您选择相同数字的可能性呈指数衰减,因此它比O(n)
您甚至没有选择一小部分数字要快得多。
Python 使用 Mersenne Twister 作为其 PRNG,这已经足够了。我们可以完全使用其他东西来以可预测的方式生成不重叠的数字。
当和是素数时,二次余数x² mod p
, 是唯一的。2x < p
p
如果你“翻转”残基,p - (x² % p)
,此时也给出p = 3 mod 4
,结果将是剩余的空格。
这不是一个非常令人信服的数字分布,因此您可以增加功率,添加一些软糖常数,然后分布就很好了。
首先我们需要生成素数:
from itertools import count
from math import ceil
from random import randrange
def modprime_at_least(number):
if number <= 2:
return 2
number = (number // 4 * 4) + 3
for number in count(number, 4):
if all(number % factor for factor in range(3, ceil(number ** 0.5)+1, 2)):
return number
您可能会担心生成素数的成本。对于 10⁶ 元素,这需要十分之一毫秒。运行[None] * 10**6
需要更长的时间,而且由于它只计算一次,这不是一个真正的问题。
此外,该算法不需要素数的精确值。只需要最多比输入数大的常数因子。这可以通过保存值列表并搜索它们来实现。如果您进行线性扫描,那就是O(log number)
,如果您进行二进制搜索,则它是O(log number of cached primes)
. 事实上,如果你使用疾驰,你可以把它降低到O(log log number)
,它基本上是恒定的 ( log log googol = 2
)。
然后我们实现生成器
def sample_generator(up_to):
prime = modprime_at_least(up_to+1)
# Fudge to make it less predictable
fudge_power = 2**randrange(7, 11)
fudge_constant = randrange(prime//2, prime)
fudge_factor = randrange(prime//2, prime)
def permute(x):
permuted = pow(x, fudge_power, prime)
return permuted if 2*x <= prime else prime - permuted
for x in range(prime):
res = (permute(x) + fudge_constant) % prime
res = permute((res * fudge_factor) % prime)
if res < up_to:
yield res
并检查它是否有效:
set(sample_generator(10000)) ^ set(range(10000))
#>>> set()
现在,关于这个的可爱之处在于,如果你忽略首要性测试,它大约O(√n)
是n
元素的数量,这个算法有时间复杂度O(k)
,k
样本大小和O(1)
内存使用量在哪里!从技术上讲是这样O(√n + k)
,但实际上是这样O(k)
。
您不需要经过验证的 PRNG。这个 PRNG 比线性同余生成器(很流行;Java 使用它)要好得多,但它不像 Mersenne Twister 那样被证明。
您不会首先生成任何具有不同功能的项目。这通过数学而不是检查避免了重复。下一节我将展示如何去除这个限制。
短的方法一定是不够的(k
必须接近n
)。如果k
只有一半n
,就按照我原来的建议去做。
极大的内存节省。这需要不断的记忆......甚至没有O(k)
!
生成下一个项目的恒定时间。这实际上也相当快:它不如内置的 Mersenne Twister 快,但在 2 倍以内。
冷静。
要删除此要求:
您不会首先生成任何具有不同功能的项目。这通过数学而不是检查避免了重复。
我已经在时间和空间复杂度方面做出了最好的算法,这是我之前生成器的简单扩展。
这是纲要(n
是数字池的长度,k
是“外”键的数量):
O(√n)
;O(log log n)
对于所有合理的输入这是我的算法中唯一在算法复杂性方面技术上并不完美的因素,这要归功于O(√n)
成本。实际上,这不会有问题,因为预先计算将其归结为O(log log n)
非常接近恒定时间。
如果您以任何固定百分比耗尽可迭代对象,则成本是免费摊销的。
这不是一个实际问题。
O(1)
密钥生成时间显然这是无法改进的。
O(k)
密钥生成时间如果您有从外部生成的密钥,并且只要求它不能是该生成器已经生成的密钥,则这些被称为“外键”。假定外键是完全随机的。因此,任何能够从池中选择项目的函数都可以这样做。
因为可以有任意数量的外键并且它们可以是完全随机的,所以完美算法的最坏情况是O(k)
.
O(k)
如果假定外键是完全独立的,则每个外键代表一个不同的信息项。因此必须存储所有密钥。该算法碰巧在看到密钥时丢弃密钥,因此内存成本将在生成器的生命周期内清除。
嗯,这是我的两种算法。其实很简单:
def sample_generator(up_to, previously_chosen=set(), *, prune=True):
prime = modprime_at_least(up_to+1)
# Fudge to make it less predictable
fudge_power = 2**randrange(7, 11)
fudge_constant = randrange(prime//2, prime)
fudge_factor = randrange(prime//2, prime)
def permute(x):
permuted = pow(x, fudge_power, prime)
return permuted if 2*x <= prime else prime - permuted
for x in range(prime):
res = (permute(x) + fudge_constant) % prime
res = permute((res * fudge_factor) % prime)
if res in previously_chosen:
if prune:
previously_chosen.remove(res)
elif res < up_to:
yield res
更改就像添加一样简单:
if res in previously_chosen:
previously_chosen.remove(res)
您可以previously_chosen
随时通过添加到set
您传入的来添加。事实上,您也可以从集合中删除以添加回潜在池,尽管这仅在sample_generator
尚未产生或跳过它时才有效与prune=False
.
所以有。很容易看出它满足了所有的要求,很容易看出要求是绝对的。请注意,如果您没有集合,它仍然可以通过将输入转换为集合来满足最坏的情况,尽管它会增加开销。
从统计学上讲,我开始好奇这个 PRNG 到底有多好。
一些快速搜索使我创建了这三个测试,它们似乎都显示出很好的结果!
首先,一些随机数:
N = 1000000
my_gen = list(sample_generator(N))
target = list(range(N))
random.shuffle(target)
control = list(range(N))
random.shuffle(control)
这些是从0
到的 10⁶ 数字的“打乱”列表10⁶-1
,一个使用我们有趣的捏造 PRNG,另一个使用 Mersenne Twister 作为基线。三是控制。
这是一个测试,它查看沿线的两个随机数之间的平均距离。将差异与对照进行比较:
from collections import Counter
def birthdat_calc(randoms):
return Counter(abs(r1-r2)//10000 for r1, r2 in zip(randoms, randoms[1:]))
def birthday_compare(randoms_1, randoms_2):
birthday_1 = sorted(birthdat_calc(randoms_1).items())
birthday_2 = sorted(birthdat_calc(randoms_2).items())
return sum(abs(n1 - n2) for (i1, n1), (i2, n2) in zip(birthday_1, birthday_2))
print(birthday_compare(my_gen, target), birthday_compare(control, target))
#>>> 9514 10136
这小于每个的方差。
这是一个测试,它依次接受 5 个数字并查看元素的顺序。它们应该均匀分布在所有 120 个可能的顺序之间。
def permutations_calc(randoms):
permutations = Counter()
for items in zip(*[iter(randoms)]*5):
sorteditems = sorted(items)
permutations[tuple(sorteditems.index(item) for item in items)] += 1
return permutations
def permutations_compare(randoms_1, randoms_2):
permutations_1 = permutations_calc(randoms_1)
permutations_2 = permutations_calc(randoms_2)
keys = sorted(permutations_1.keys() | permutations_2.keys())
return sum(abs(permutations_1[key] - permutations_2[key]) for key in keys)
print(permutations_compare(my_gen, target), permutations_compare(control, target))
#>>> 5324 5368
这再次小于每个的方差。
这是一个测试,看看“运行”有多长时间,又名。连续增加或减少的部分。
def runs_calc(randoms):
runs = Counter()
run = 0
for item in randoms:
if run == 0:
run = 1
elif run == 1:
run = 2
increasing = item > last
else:
if (item > last) == increasing:
run += 1
else:
runs[run] += 1
run = 0
last = item
return runs
def runs_compare(randoms_1, randoms_2):
runs_1 = runs_calc(randoms_1)
runs_2 = runs_calc(randoms_2)
keys = sorted(runs_1.keys() | runs_2.keys())
return sum(abs(runs_1[key] - runs_2[key]) for key in keys)
print(runs_compare(my_gen, target), runs_compare(control, target))
#>>> 1270 975
这里的差异非常大,在我执行的几次处决中,两者的分布似乎是均匀的。因此,此测试通过。
向我提到了一个线性同余生成器,可能“更有成效”。我自己做了一个糟糕的LCG,看看这是否是一个准确的说法。
LCG,AFAICT,就像普通发电机一样,它们不是循环的。因此,我查看的大多数参考资料,又名。维基百科只涵盖了定义时期的内容,而不是如何制作特定时期的强大 LCG。这可能会影响结果。
开始:
from operator import mul
from functools import reduce
# Credit http://stackoverflow.com/a/16996439/1763356
# Meta: Also Tobias Kienzler seems to have credit for my
# edit to the post, what's up with that?
def factors(n):
d = 2
while d**2 <= n:
while not n % d:
yield d
n //= d
d += 1
if n > 1:
yield n
def sample_generator3(up_to):
for modulier in count(up_to):
modulier_factors = set(factors(modulier))
multiplier = reduce(mul, modulier_factors)
if not modulier % 4:
multiplier *= 2
if multiplier < modulier - 1:
multiplier += 1
break
x = randrange(0, up_to)
fudge_constant = random.randrange(0, modulier)
for modfact in modulier_factors:
while not fudge_constant % modfact:
fudge_constant //= modfact
for _ in range(modulier):
if x < up_to:
yield x
x = (x * multiplier + fudge_constant) % modulier
我们不再检查素数,但我们确实需要对因子做一些奇怪的事情。
modulier ≥ up_to > multiplier, fudge_constant > 0
a - 1
modulier
必须能被...中的每个因子整除fudge_constant
必须与modulier
请注意,这些不是 LCG 的规则,而是具有完整周期的 LCG,这显然等于mod
ulier。
我是这样做的:
modulier
至少尝试一次up_to
,满足条件时停止
multiplier
成为
删除重复项的产品multiplier
不小于modulier
,继续下一个modulier
fudge_constant
是 一个 小于 的 数字modulier
, 随机 选择fudge_constant
因素
这不是一个很好的生成方法,但我不明白为什么它会影响数字的质量,除了低fudge_constant
s 和multiplier
比完美的生成器更常见的事实。
无论如何,结果令人震惊:
print(birthday_compare(lcg, target), birthday_compare(control, target))
#>>> 22532 10650
print(permutations_compare(lcg, target), permutations_compare(control, target))
#>>> 17968 5820
print(runs_compare(lcg, target), runs_compare(control, target))
#>>> 8320 662
总之,我的 RNG 很好,而线性同余生成器不是。考虑到 Java 摆脱了线性同余生成器(尽管它只使用低位),我希望我的版本绰绰有余。
好的,我们开始吧。这应该是最快的非概率算法。它有 runtime ofO(k⋅log²(s) + f⋅log(f)) ⊂ O(k⋅log²(f+k) + f⋅log(f)))
和 space O(k+f)
。f
是禁止数字的数量,s
是最长的禁止数字条纹的长度。对此的期望更为复杂,但显然受f
. 如果您假设它s^log₂(s)
大于或只是对再次是概率f
的事实不满意,您可以将日志部分更改为二等分搜索以获得.s
forbidden[pos:]
O(k⋅log(f+k) + f⋅log(f))
这里的实际实现是,就像在列表中O(k⋅(k+f)+f⋅log(f))
插入一样。这很容易通过用blist sortedlist替换该列表来解决。forbid
O(n)
我还添加了一些评论,因为这个算法非常复杂。该lin
部分的作用与该部分相同log
,但需要s
而不是log²(s)
时间。
import bisect
import random
def sample(k, end, forbid):
forbidden = sorted(forbid)
out = []
# remove the last block from forbidden if it touches end
for end in reversed(xrange(end+1)):
if len(forbidden) > 0 and forbidden[-1] == end:
del forbidden[-1]
else:
break
for i in xrange(k):
v = random.randrange(end - len(forbidden) + 1)
# increase v by the number of values < v
pos = bisect.bisect(forbidden, v)
v += pos
# this number might also be already taken, find the
# first free spot
##### linear
#while pos < len(forbidden) and forbidden[pos] <=v:
# pos += 1
# v += 1
##### log
while pos < len(forbidden) and forbidden[pos] <= v:
step = 2
# when this is finished, we know that:
# • forbidden[pos + step/2] <= v + step/2
# • forbidden[pos + step] > v + step
# so repeat until (checked by outer loop):
# forbidden[pos + step/2] == v + step/2
while (pos + step <= len(forbidden)) and \
(forbidden[pos + step - 1] <= v + step - 1):
step = step << 1
pos += step >> 1
v += step >> 1
if v == end:
end -= 1
else:
bisect.insort(forbidden, v)
out.append(v)
return out
现在将其与 Veedrac 提出的“hack”(和 python 中的默认实现)进行比较,它有空间O(f+k)
和(n/(n-(f+k))
是“猜测”的预期数量</a>)时间:
我只是绘制了这个和k=10
一个相当大的n=10000
(它只会变得更大更极端n
)。我不得不说:我实施这个只是因为它看起来是一个有趣的挑战,但即使我也对它的极端程度感到惊讶:
让我们放大看看发生了什么:
是的——你生成的第 9998 个数字的猜测速度更快。请注意,正如您在第一个情节中看到的那样,即使是我的单线也可能更快f/n
(但对于 big 仍然有相当可怕的空间要求n
)。
把要点带回家:你在这里花时间做的唯一一件事就是生成集合,因为这是f
Veedrac 方法中的因素。
所以我希望我在这里的时间没有被浪费,我设法让你相信 Veedrac 的方法是正确的方法。我可以理解为什么概率部分给你带来困扰,但也许想想 hashmaps (= python dict
s) 和大量其他算法使用类似方法工作的事实,它们似乎做得很好。
您可能会害怕重复次数的差异。如上所述,这遵循具有 的几何分布p=n-f/n
。所以标准偏差(=您“应该期望”结果偏离预期平均值的量)是
这与均值 ( ) 基本相同√f⋅n < √n² = n
。
****编辑**:
我刚刚意识到s
实际上也是n/(n-(f+k))
. 所以我的算法更精确的运行时间是O(k⋅log²(n/(n-(f+k))) + f⋅log(f))
. 这很好,因为鉴于上面的图表,它证明了我的直觉,这比O(k⋅log(f+k) + f⋅log(f))
. 但请放心,这也不会改变上述结果,因为它f⋅log(f)
是运行时中绝对占主导地位的部分。
好的,最后一次尝试;-) 以改变碱基序列为代价,这不占用额外的空间,并且n
每次sample(n)
调用所需的时间成正比:
class Sampler(object):
def __init__(self, base):
self.base = base
self.navail = len(base)
def sample(self, n):
from random import randrange
if n < 0:
raise ValueError("n must be >= 0")
if n > self.navail:
raise ValueError("fewer than %s unused remain" % n)
base = self.base
for _ in range(n):
i = randrange(self.navail)
self.navail -= 1
base[i], base[self.navail] = base[self.navail], base[i]
return base[self.navail : self.navail + n]
小司机:
s = Sampler(list(range(100)))
for i in range(9):
print s.sample(10)
print s.sample(1)
print s.sample(1)
实际上,这实现了一个可恢复的,在元素被选择 random.shuffle()
后暂停。没有被破坏,而是被置换。n
base
这是一种不显式构建差异集的方法。但它确实使用了@Veedrac 的“接受/拒绝”逻辑的一种形式。如果您不愿意在进行过程中改变碱基序列,恐怕这是不可避免的:
def sample(n, base, forbidden):
# base is iterable, forbidden is a set.
# Every element of forbidden must be in base.
# forbidden is updated.
from random import random
nusable = len(base) - len(forbidden)
assert nusable >= n
result = []
if n == 0:
return result
for elt in base:
if elt in forbidden:
continue
if nusable * random() < n:
result.append(elt)
forbidden.add(elt)
n -= 1
if n == 0:
return result
nusable -= 1
assert False, "oops!"
这是一个小司机:
base = list(range(100))
forbidden = set()
for i in range(10):
print sample(10, base, forbidden)
您可以根据Wikipedia 的“Fisher--Yates shuffle#Modern method”实现一个洗牌生成器
def shuffle_gen(src):
""" yields random items from base without repetition. Clobbers `src`. """
for remaining in xrange(len(src), 0, -1):
i = random.randrange(remaining)
yield src[i]
src[i] = src[remaining - 1]
然后可以使用切片itertools.islice
:
>>> import itertools
>>> sampler = shuffle_gen(range(100))
>>> sample1 = list(itertools.islice(sampler, 10))
>>> sample1
[37, 1, 51, 82, 83, 12, 31, 56, 15, 92]
>>> sample2 = list(itertools.islice(sampler, 80))
>>> sample2
[79, 66, 65, 23, 63, 14, 30, 38, 41, 3, 47, 42, 22, 11, 91, 16, 58, 20, 96, 32, 76, 55, 59, 53, 94, 88, 21, 9, 90, 75, 74, 29, 48, 28, 0, 89, 46, 70, 60, 73, 71, 72, 93, 24, 34, 26, 99, 97, 39, 17, 86, 52, 44, 40, 49, 77, 8, 61, 18, 87, 13, 78, 62, 25, 36, 7, 84, 2, 6, 81, 10, 80, 45, 57, 5, 64, 33, 95, 43, 68]
>>> sample3 = list(itertools.islice(sampler, 20))
>>> sample3
[85, 19, 54, 27, 35, 4, 98, 50, 67, 69]
这是我的 Knuth shuffle 版本,最初由Tim Peters发布,由Eric美化,然后由necromancer进行了很好的空间优化。
这是基于 Eric 的版本,因为我确实发现他的代码非常漂亮:)。
import random
def shuffle_gen(n):
# this is used like a range(n) list, but we don’t store
# those entries where state[i] = i.
state = dict()
for remaining in xrange(n, 0, -1):
i = random.randrange(remaining)
yield state.get(i,i)
state[i] = state.get(remaining - 1,remaining - 1)
# Cleanup – we don’t need this information anymore
state.pop(remaining - 1, None)
用法:
out = []
gen = shuffle_gen(100)
for n in range(100):
out.append(gen.next())
print out, len(set(out))
相当快的单线(O(n + m)
, n=range,m=old 样本大小):
next_sample = random.sample(set(range(100)).difference(my_sample), 10)
编辑:查看下面@TimPeters 和@Chronial 提供的更简洁的版本。一个小的编辑把它推到了顶部。
这是我认为增量采样最有效的解决方案。调用者维护的状态不是先前采样的数字列表,而是包括可供增量采样器使用的字典,以及范围内剩余的数字计数。
下面是一个示范性的实现。与其他解决方案相比:
O(log(number_previously_sampled))
O(number_previously_sampled)
代码:
import random
def remove (i, n, state):
if i == n - 1:
if i in state:
t = state[i]
del state[i]
return t
else:
return i
else:
if i in state:
t = state[i]
if n - 1 in state:
state[i] = state[n - 1]
del state[n - 1]
else:
state[i] = n - 1
return t
else:
if n - 1 in state:
state[i] = state[n - 1]
del state[n - 1]
else:
state[i] = n - 1
return i
s = dict()
for n in range(100, 0, -1):
print remove(random.randrange(n), n, s)
这是@necromancer 很酷的解决方案的重写版本。将其包装在一个类中,使其更容易正确使用,并使用更多的dict方法来减少代码行。
from random import randrange
class Sampler:
def __init__(self, n):
self.n = n # number remaining from original range(n)
# i is a key iff i < n and i already returned;
# in that case, state[i] is a value to return
# instead of i.
self.state = dict()
def get(self):
n = self.n
if n <= 0:
raise ValueError("range exhausted")
result = i = randrange(n)
state = self.state
# Most of the fiddling here is just to get
# rid of state[n-1] (if it exists). It's a
# space optimization.
if i == n - 1:
if i in state:
result = state.pop(i)
elif i in state:
result = state[i]
if n - 1 in state:
state[i] = state.pop(n - 1)
else:
state[i] = n - 1
elif n - 1 in state:
state[i] = state.pop(n - 1)
else:
state[i] = n - 1
self.n = n-1
return result
这是一个基本的驱动程序:
s = Sampler(100)
allx = [s.get() for _ in range(100)]
assert sorted(allx) == list(range(100))
from collections import Counter
c = Counter()
for i in range(6000):
s = Sampler(3)
one = tuple(s.get() for _ in range(3))
c[one] += 1
for k, v in sorted(c.items()):
print(k, v)
和样本输出:
(0, 1, 2) 1001
(0, 2, 1) 991
(1, 0, 2) 995
(1, 2, 0) 1044
(2, 0, 1) 950
(2, 1, 0) 1019
通过眼球,该分布很好(如果您持怀疑态度,请进行卡方检验)。这里的一些解决方案没有以相等的概率给出每个排列(即使它们以相等的概率返回 n 的每个 k 子集),所以random.sample()
在这方面是不同的。
令人惊讶的是,这还没有在其中一个核心函数中实现,但这里是干净的版本,它返回采样值和列表而无需替换:
def sample_n_points_without_replacement(n, set_of_points):
sampled_point_indices = random.sample(range(len(set_of_points)), n)
sampled_point_indices.sort(reverse=True)
sampled_points = [set_of_points[sampled_point_index] for sampled_point_index in sampled_point_indices]
for sampled_point_index in sampled_point_indices:
del(set_of_points[sampled_point_index])
return sampled_points, set_of_points
这是一个旁注:假设您想解决完全相同的采样问题而不在列表上进行替换(我将称之为sample_space
),但不是在您尚未采样的元素集上均匀采样,而是给出初始概率分布p
,它告诉您对i^th
分布元素进行采样的概率,您是否对整个空间进行采样。
然后使用 numpy 的以下实现在数值上是稳定的:
import numpy as np
def iterative_sampler(sample_space, p=None):
"""
Samples elements from a sample space (a list)
with a given probability distribution p (numPy array)
without replacement. If called until StopIteration is raised,
effectively produces a permutation of the sample space.
"""
if p is None:
p = np.array([1/len(sample_space) for _ in sample_space])
try:
assert isinstance(sample_space, list)
assert isinstance(p, np.ndarray)
except AssertionError:
raise TypeError("Required types: \nsample_space: list \np type: np.ndarray")
# Main loop
n = len(sample_space)
idxs_left = list(range(n))
for i in range(n):
idx = np.random.choice(
range(n-i),
p= p[idxs_left] / p[idxs_left].sum()
)
yield sample_space[idxs_left[idx]]
del idxs_left[idx]
短小精悍,我喜欢。让我知道你们的想法!