随机播放无局部变量的牌组


14

这个难题的目的是拿出52张纸牌并洗牌,以使每张纸牌处于随机位置。

鉴于:

  • deck代表卡片的52个不同整数组成的数组。当您开始时,deck仅以未知顺序包含每张卡中的一张。
  • 函数,该函数int rand(min, max)返回ins minmax(含)之间的随机整数。您可以假定此函数是真正随机的。
  • 一种功能,void swap(x, y)可以交换卡片组中的两张卡。如果您致电swap(x, y),则这些卡将在位置xy并将切换位置。

什么时候:

  • 程序调用shuffle()(或shuffle(deck)deck.shuffle()或者您的实现喜欢运行),

然后:

  • deck 应该完全随机地包含每张卡中的一张。

抓住:

您不能声明任何变量。随心所欲地调用swaprand,但是您不能声明自己的任何变量。这包括for循环计数器-甚至是隐式计数器(如)foreach

说明:

  • 您可以更改次要详细信息以适合您选择的语言。例如,您可以编写swap以通过引用切换两个整数。所做的更改应该是使它与您的语言配合使用,而不是使难题变得更容易。
  • deck 可以是全局变量,也可以将其作为参数。
  • 您可以对的内容进行任何操作deck,但不能更改其长度。
  • 您的卡可以编号为0-51、1-52或您喜欢的任何编号。
  • 您可以用任何一种语言编写此代码,但不会欺骗您的语言的内置shuffle功能。
  • 是的,您可以在同一行中编写52次。没有人会留下深刻的印象。
  • 执行时间并不重要,但真正的随机性却很重要。
  • 这并不是真正的代码高尔夫球,但是请随时最小化/混淆您的代码。

编辑:样板代码和可视化工具

如果您使用.NET或JavaScript,以下一些测试代码可能会有用:

JavaScript:

C#:

这段代码对卡牌进行了数千次排序和改组,并执行了一些基本的健全性测试:对于每次改组,它都验证卡组中是否确实有52张卡片没有重复。然后,可视化工具绘制最终出现在牌组中每个位置的每张卡的频率,显示灰度热图。

可视化器的输出应看起来像没有明显图案的雪。显然,它不能证明真正的随机性,但这是一种快速简便的抽查方法。我建议使用它或类似的东西,因为混洗算法中的某些错误会导致输出中的模式很容易识别。这是两个实现的输出示例,一个实现有一个常见缺陷:

可视化器输出

有缺陷的版本确实会部分打乱甲板,因此如果您手动检查阵列,可能会看起来不错。可视化器使您更容易注意到图案。


许多语言将数组建模为有效的无限数组,因此允许使用$ deck [52]及更高版本代替局部变量。也许这也应该被禁止。
Timwi

2
功能是否被视为变量?功能参数是否被视为变量?
zzzzBov 2011年

1
@zzzzBov-我想到的是将函数参数视为变量,但是在@mellamokb回答之前我没有指定。我知道可以在没有任何参数的情况下完成此操作deck
贾斯汀·摩根

1
@eBusiness-这是我的问题,不是问题本身。我很生气,因为答疑人发现了漏洞。
贾斯汀·摩根

1
@user未知-我想我明白。答案基本上是swap,只要满足基本目的,您就可以假设自己喜欢的任何实现。我做出建议swap的部分原因是,人们可以将其视为“魔术”并专注于主要问题,而不必担心它会以他们选择的语言工作。您可以这样做,也可以自己编写swap,这取决于您。
贾斯汀·摩根

Answers:


9

的JavaScript

我相信这是解决方案的预期形式,我使用位置0的卡来跟踪进度,仅将已经用作计数器的卡改组,就可以达到标准52!具有完美均等分布的排列。由于XOR交换不允许元素本身交换,因此该过程变得很复杂。

编辑:我建立了一个排序,可以在使用每个元素之前将每个元素排序到位,从而允许它与未排序的数组一起使用。我还放弃了递归调用,而改为使用while循环。

deck=[]
for(a=0;a<52;a++){
    deck[a]=a
}
function swap(a,b){
    deck[a]=deck[b]^deck[a]
    deck[b]=deck[b]^deck[a]
    deck[a]=deck[b]^deck[a]
}
function rand(a,b){
    return Math.floor(Math.random()*(1+b-a))+a
}
function shuffle(){
    while(deck[0]!=0){ //Sort 0 into element 0
        swap(0,deck[0])
    }
    while(deck[0]<51){ //Run 51 times
        while(deck[deck[0]+1]!=deck[0]+1){ //Sort element deck[0]+1 into position deck[0]+1
            swap(deck[deck[0]+1],deck[0]+1)
        }
        swap(0,deck[0]+1) //Swap element deck[0]+1 into position 0, thus increasing the value of deck[0] by 1
        if(rand(0,deck[0]-1)){ //Swap the element at position deck[0] to a random position in the range 1 to deck[0]
            swap(deck[0],rand(1,deck[0]-1))
        }
    }
    if(rand(0,51)){ //Swap the element at position 0 to a random position
        swap(0,rand(1,51))
    }
}
for(c=0;c<100;c++){
    shuffle()
    document.write(deck+"<br>")
}

那正是我的想法。一旦我对此进行测试,我会投票赞成并可能接受。
贾斯汀·摩根

看起来工作正常,尽管仔细检查发现它与我的并不完全相同。已接受,我将很快发布我自己的答案。
贾斯汀·摩根

这也称为Knuth随机播放算法(en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)。
鲍勃

14

哈斯克尔

这是一个没有意义的实现。没有变量,形式参数或显式递归。我使用了lambdabot@pl(“毫无意义”)重构功能。

import Data.List
import Control.Applicative
import Control.Monad
import System.Random

shuffle :: [a] -> IO [a]
shuffle = liftM2 (<$>) ((fst .) . foldl' (uncurry ((. flip splitAt) . (.) .
          (`ap` snd) . (. fst) . flip flip tail . (ap .) . flip flip head .
          ((.) .) . (. (++)) . flip . (((.) . (,)) .) . flip (:))) . (,) [])
          (sequence . map (randomRIO . (,) 0 . subtract 1) . reverse .
          enumFromTo 1 . length)

main = print =<< shuffle [1..52]

这是我的测试过程,以确保数字均匀分布:

main = print . foldl' (zipWith (+)) (replicate 52 0)
       =<< replicateM 1000 (shuffle [1..52])

这是原始算法:

shuffle :: [a] -> IO [a]
shuffle xs = shuffleWith xs <$>
             sequence [randomRIO (0, i - 1) | i <- reverse [1..length xs]]

shuffleWith :: [a] -> [Int] -> [a]
shuffleWith xs ns = fst $ foldl' f ([], xs) ns where
    f (a,b) n = (x:a, xs++ys) where
        (xs, x:ys) = splitAt n b

Haskell +1。现在我必须学习Haskell,这样我才能阅读。:P
贾斯汀·摩根

进度如何存储?
aaaaaaaaaaaaaa

8
我怀疑除Haskell程序员以外的任何人都会说他们的代码毫无意义,并为此感到自豪。
aaaaaaaaaaaaaa

4
((.) .) . (. (++)),这(((.) . (,)) .)是我的最爱。哇,lambdabot。只是,哇。
丹·伯顿

2
@eBusiness“无积分”与“无意义”完全不同。
fredoverflow 2014年

6

Ĵ

忽略甲板是一个变量,很明显...

52 ? 52

当然,如果您确实想要一个功能,则可以使用该功能,即使您忘记删除小丑(或尝试洗牌以外的东西)也可以使用。

{~ (# ? #)

以便...

shuffle =: {~ (# ? #)
deck =: i. 52
shuffle deck

这可能超出了该问题的意图,该问题是您自己从rand(?)实现改组。我不应该工作时可以稍后再做。

说明

的说明52 ? 52

  • x ? y 是y中的x个随机唯一项。

{~ (# ? #)由于有叉子和钩子,因此很难解释。基本上,它与相同shuffle =: 3 : '((# y) ? (# y)) { y',只有一个隐式参数(y)。

  • # y 给出y的长度
  • 这给52?52像以前一样,它是0..51的随机排列
  • x { y 是索引x中y的项,或者(在这种情况下)是x中索引中的项。
  • 这使您可以改组传入的所有内容,而不仅仅是整数。

有关运算符的详细信息,请参见J词汇表,尽管由于等级和默认编程,语法和语义有些棘手。


+1:应该是工作时工作的代码高尔夫球场。哈哈,我太:P
mellamokb

1
您能解释一下这对J受损者有何作用吗?我最近听说它被描述为表情工厂中的爆炸(codegolf.stackexchange.com/questions/1294/anagram-code-golf/…),听起来很对。
贾斯汀·摩根

@贾斯汀:添加了解释。
杰西·米利坎

这也适用于APL。语法是相同的,因此我不会费心添加一个新答案(这{52?⍵}是一个匿名函数,该函数从其参数中获取52个随机项,这将是52个整数的列表)
Arc676

4

蟒蛇

import random
def rand(x, y):
 return random.randrange(x, y+1)

def swap(deck, x, y):
 deck[x] ^= deck[y]
 deck[y] ^= deck[x]
 deck[x] ^= deck[y]

def shuffle(deck):
 if len(deck)>1:
  deck[1:]=shuffle(deck[1:])
  if rand(0,len(deck)-1)>0:swap(deck, 0, rand(1, len(deck)-1))
 return deck

print shuffle(range(52))

什么[1:]意思 这会在的子数组上递归deck吗?
贾斯汀·摩根

是的,[1:]表示从索引1到数组末尾的子数组。因此,它会递归地重排除第一个元素以外的所有内容,将其分配(复制)回原始数组中的同一位置,然后将第一个元素随机放置在某个位置。
基思·兰德尔

非常聪明。我认为这是这里最漂亮的解决方案之一,它正确使用了Fisher-Yates算法。+1。对于我来说,这是了解我不熟悉的语言之美的好方法。
贾斯汀·摩根

2
您可能喜欢这个a, b = b, a窍门。

3

使用分解式表示

在排列的阶乘表示中,元素i取从0到Ni的值。因此,随机排列仅rand(0,i)适用于每个Ni。

在J中:

? |.>:i.52
2 39 20 26 ... 2 0 1 0 0 0

这里? xrand(0,x-1)|.>:i.5252 51 ... 1

然后,如果aith factoradic的值是,我们进行交换:swap(deck[i], deck[i+a])。要交换的对的列表为:

(,. i.52) ,. (,. ((?|.>:i.52)+i.52))
0 33
1 20
2  3
...
49 50
50 50
51 51

我们将使用的交换如下所示:

deck
24 51 14 18 ...
deck =: 0 1 swap deck
51 24 14 18 ...

它不是真正的“通过引用”,但是在J中没有真正的功能。

我们将使用Deck的长度(#deck)避免使用常量。

J中的完整程序:

deck =: 52 ? 52                           NB. Initial random deck
swap =: 4 : 'deck =: (x { y) (|.x) } y'   NB. Given swap "function"
f =: 3 : 0                                NB. function that calls the swap for a pair
({.y) swap deck
}.y
)
f^:(#deck) (,.,.[:,.]+[:?[:|.>:) i.#deck

3

C#

这是我根据Fisher-Yates算法得出的答案。如果您的随机数生成器足够好,应该给您一个完美的改组。

英文版:

  1. 在多次交换卡deck[0]与一个在deck[v],哪里v是在卡的面值deck[0]。重复直到v == 0。这将对牌组进行部分排序,但这无关紧要。现在,您知道卡0位于卡座的前面,这意味着您可以窃取阵列中的该空间并将其用作循环计数器。这是局部变量问题的关键“秘籍”。
  2. 从位置1(卡组中的第二张卡片)开始,将的卡片i与的卡片交换rand(i, 51)。请注意,您需要rand(i, 51),而不是 rand(1, 51)。那不会确保每张卡都是随机的。
  3. 设置deck[0]回0。现在整个甲板洗牌除了第一张牌,所以交换deck[0]deck[rand(0, 51)],你就大功告成了。

C#版本:

public static void shuffle(int[] deck)
{
    while (deck[0] > 0)
        swap(ref deck[0], ref deck[deck[0]]);

    for (deck[0] = 1; deck[0] < 52; deck[0]++)
        swap(ref deck[deck[0]], ref deck[rand(deck[0], 51)]);

    deck[0] = 0;
    swap(ref deck[0], ref deck[rand(0, 51)]);
}

JavaScript版本:

while (deck[0] > 0)
    swap(0, deck[0]);

for (deck[0] = 1; deck[0] < 52; deck[0]++)
    swap(deck[0], rand(deck[0], 52));

deck[0] = 0;
swap(0, rand(0, 52));

... 与swap(a, b)交换。deck[a]deck[b]


2

红宝石,一行

这算作弊吗?它应尽可能随机。

deck=(0..51).to_a # fill the deck
deck[0..51] = (0..51).map{deck.delete_at(rand deck.length)}

(Ruby的rand方法只接受一个参数,然后生成一个数字n,使得0 <= number <参数。)

另外-与sogart的Perl解决方案类似,但据我所知它没有遇到问题:

deck = deck.sort_by{rand}

Ruby的sort_by与sort不同-它首先生成值列表以对数组进行排序,然后才对它们进行排序。当找到我们要排序的属性的成本很高时,它会更快,而在所有其他情况下,它会稍慢一些。在代码高尔夫:P中也很有用


本质上,我不会称其为作弊,但是deck[0..51]通过使用某种语言的功能,它确实在某种程度上规避了“无变量”规则。公平,我只是认为它失去了一些挑战。:)我不了解Ruby;你能解释一下(0..51).map{deck.delete_at(rand deck.length)}吗?那会删除卡片deck吗?
贾斯汀·摩根

@JustinMorgan是的,它会删除52次随机卡片deck并将其添加到map正在累积的内部结果列表中,这是52次。然后,当结果一无所有时deckmap结果将被复制到中deck。基本上有一个临时功能,但这是语言功能,而不是显式变量:)
hobbs

deck.sort_by!{rand}更短。
埃里克·杜米尼尔

1

的JavaScript

注意:此解决方案在技术上是不正确的,因为它i在对的调用中使用了第二个参数,该参数被shuffle视为外部变量。

function shuffle(deck, i) {
    if (i <= 0)
        return;
    else {
        swap(deck[rand(0,i-1)], deck[i-1]);
        shuffle(deck, i - 1);
    }
}

致电 shuffle(deck,52)

一个完整的工作示例(swap由于JavaScript中没有传递整数的引用,因此需要稍作修改):

function rand(min, max) { return Math.floor(Math.random()*(max-min+1)+min); }
function swap(deck, i, j) {
    var t=deck[i];
    deck[i] = deck[j];
    deck[j] = t;
}

function shuffle(deck, i) {
    if (i <= 0)
        return;
    else {
        swap(deck, rand(0,i-1), i-1);
        shuffle(deck, i - 1);
    }
}

// create deck
var deck=[];
for(i=0;i<52;i++)deck[i]=i;
document.writeln(deck);
shuffle(deck,52);
document.writeln(deck);

做得好。我想到的是考虑将参数shuffleas作为变量,但我没有指定+1。很好地使用递归。
贾斯汀·摩根

-1,不会生成所有排列,这很明显,因为元素51永远不会占据其原始位置,并且因为您仅调用rand足以生成51!可能的52种排列!
aaaaaaaaaaaaaa

2
@eBusiness:在原始规格中,甲板是任意订购的,不一定是1-52的顺序。我只是用它,因为那是最简单的。
mellamokb

1
@eBusiness:我进行了修改,以允许使用deck[rand(0,i-1)]而不是将元素保留在同一位置deck[rand(0,i-2)]。也要一直交换到,i=0而不是在停下来i=1。有帮助吗?
mellamokb

是的,应该这样做,只是您现在破坏了XOR交换规范。
aaaaaaaaaaaaaa

1

C ++

#include <cstdlib>
#include <ctime>
#include <iostream>

int deck[52];

void swap(int a, int b) {
    deck[a] ^= deck[b];
    deck[b] ^= deck[a];
    deck[a] ^= deck[b];
}

int r(int a, int b) {
    return a + (rand() % (b - a + 1));
}

void s(int *deck) {
    swap(1, r(2, 51));
    deck[0] *= 100;

    for(deck[0] += 2; (deck[0] % 100) < 51; deck[0]++) {
        swap(deck[0] % 100,
          r(0, 1) ? r(1, (deck[0] % 100) - 1) : r((deck[0] % 100) + 1, 51));
    }
    swap(51, r(1, 50)); 

    deck[0] = (deck[0] - 51) / 100;
    swap(r(1, 51), 0);
}

int main(int a, char** c)
{
    srand(time(0));

    for (int i = 0; i < 52; i++)
        deck[i] = i;

    s(deck);
    s(deck);

    for (int i = 0; i < 52; i++)
        std::cout << deck[i] << " ";
}

避免与元素本身交换元素,因此必须调用两次以随机进行。


swap(deck[rand(1, 51)], (deck[0] - 51) / 100);怎么swap知道第二个值放在哪里?您还缺少一个)
贾斯汀·摩根

糟糕,谢谢。我在修订过程中开始移动该零件,在完成之前一定要分心:P
Matthew阅读了

顺便说一句,Downvote不是来自我。我会在可能的时候进行测试。
贾斯汀·摩根

好。通过提供完整的程序,我使测试变得更加容易。

1
非常聪明。我自己的解决方案使用deck[0],但没有您采用的方式。
贾斯汀·摩根

1

d

shuffle(int[] d){
    while(d.length){
        if([rand(0,d.length-1)!=0)swap(d[0],d[rand(1,d.length-1)]);
        d=d[1..$];
    }
}

1

另一个Perl解决方案,实际上产生均匀分布的输出:

sub shuffle_integers {
    map int, sort {$a-int $a <=> $b-int $b} map $_+rand, @_;
}

say join " ", shuffle_integers 1 .. 52;

该解决方案使用Perl rand,返回一个随机数x范围内 x <1。将这种随机数添加到输入中的每个整数,根据数字的小数部分对它们进行排序,最后再次去除这些小数部分。

(我相信使用特殊变量$_$a并且$b属于挑战精神,因为这些是perl将输入传递给map和的方式。sort,并且在代码中它们并未用于任何其他目的。无论如何,我相信他们实际上别名输入值,而不是独立的副本这实际上不是就地洗牌,虽然;既mapsort在栈上创建输入的副本)。


1

爪哇

令我惊讶的是,没有人说清楚:(我假设swap(x,x)什么也不做。

    static void shuffle(){
        swap(1,rand(0,1));
        swap(2,rand(0,2));
        swap(3,rand(0,3));
        swap(4,rand(0,4));
        swap(5,rand(0,5));
        swap(6,rand(0,6));
        swap(7,rand(0,7));
        swap(8,rand(0,8));
        swap(9,rand(0,9));
        swap(10,rand(0,10));
        swap(11,rand(0,11));
        swap(12,rand(0,12));
        swap(13,rand(0,13));
        swap(14,rand(0,14));
        swap(15,rand(0,15));
        swap(16,rand(0,16));
        swap(17,rand(0,17));
        swap(18,rand(0,18));
        swap(19,rand(0,19));
        swap(20,rand(0,20));
        swap(21,rand(0,21));
        swap(22,rand(0,22));
        swap(23,rand(0,23));
        swap(24,rand(0,24));
        swap(25,rand(0,25));
        swap(26,rand(0,26));
        swap(27,rand(0,27));
        swap(28,rand(0,28));
        swap(29,rand(0,29));
        swap(30,rand(0,30));
        swap(31,rand(0,31));
        swap(32,rand(0,32));
        swap(33,rand(0,33));
        swap(34,rand(0,34));
        swap(35,rand(0,35));
        swap(36,rand(0,36));
        swap(37,rand(0,37));
        swap(38,rand(0,38));
        swap(39,rand(0,39));
        swap(40,rand(0,40));
        swap(41,rand(0,41));
        swap(42,rand(0,42));
        swap(43,rand(0,43));
        swap(44,rand(0,44));
        swap(45,rand(0,45));
        swap(46,rand(0,46));
        swap(47,rand(0,47));
        swap(48,rand(0,48));
        swap(49,rand(0,49));
        swap(50,rand(0,50));
        swap(51,rand(0,51));
    }

OK,好的,它可以更短:

package stackexchange;

import java.util.Arrays;

public class ShuffleDry1
{
    static int[] deck = new int[52];

    static void swap(int i, int j){
        if( deck[i]!=deck[j] ){
            deck[i] ^= deck[j];
            deck[j] ^= deck[i];
            deck[i] ^= deck[j];
        }
    }

    static int rand(int min, int max){
        return (int)Math.floor(Math.random()*(max-min+1))+min;
    }

    static void initialize(){
        for( int i=0 ; i<deck.length ; i++ ){
            deck[i] = i;
            swap(i,rand(0,i));
        }
    }

    static void shuffle(){
        while( deck[0]!=0 ) swap(0,deck[0]);
        for( deck[0]=52; deck[0]-->1 ; ) swap(deck[0],rand(deck[0],51));
        swap(0,rand(0,51));
    }

    public static void main(String[] args) {
        initialize();
        System.out.println("init: " + Arrays.toString(deck));
        shuffle();
        System.out.println("rand: " + Arrays.toString(deck));
    }

}

1

滑稽的

您实际上要的是整数列表的随机排列?r@将为我们提供所有排列,我们只选择一个随机排列。

blsq ) {1 2 3}r@sp
1 2 3
2 1 3
3 2 1
2 3 1
3 1 2
1 3 2
blsq ) {1 2 3}r@3!!BS
2 3 1

由于我们需要真正的随机性,因此Burlesque无法执行某些操作,因为Burlesque没有I / O功能,您需要通过STDIN提供一些随机性源。

这可能是我将在以后的版本中解决的问题(即在启动时生成随机种子并将其推送到辅助堆栈之类的东西,但是Burlesque Interpreter本身没有I / O)。


0

Java脚本

我不确定它是否在“作弊”,但是我的解决方案使用的是函数参数的本机本地数组。我包括我的自制功能rand() swap()filldeck()。有趣的是,它可以与任何尺寸的甲板一起使用。

    var deck = [];

    function shuffle(){
        main(deck.length);
    }

    function main(){
        arguments[0] && swap( arguments[0]-=1, rand(0, deck.length-1) ), main(arguments[0]);
    }

        function rand(min, max){
            return Math.floor( Math.random()*(max-min+1) )+min;
        }

        function swap(x, y){
            var _x = deck[x], _y = deck[y];
            deck[x] = _y, deck[y] = _x;
        }


        function filldeck(dL){
            for(var i=0; i<dL; i++){
                var ran = rand(1,dL);
                while( deck.indexOf(ran) >= 0 ){
                    ran = rand(1,dL);
                }
                deck[i] = ran;
            }
        }

    filldeck(52);
    shuffle();

我认为这是作弊。但是,这是非常聪明的作弊,做得很好。
贾斯汀·摩根

0

Tcl,32字节

滥用time功能可用来衡量脚本运行的时间,但也可以用作循环机制而无需声明任何变量。

time {lswap $D [rand] [rand]} 52

在线尝试!


我是否只执行52次随机交换,对吗?仅仅进行真正的洗牌还不够。我跑了几次,平均还算有8张牌仍处于起始位置,真正洗牌几率约为9x10 ^ -6
贾斯汀·摩根

@JustinMorgan:能否请您更好地解释概率计算?
sergiol

-1

perl-如注释中所述,这不是正确的改组!

my @deck = (0..51);
@deck = sort {rand() <=> rand()} @deck;
print join("\n",@deck);

我想我没有使用任何东西作为交换等等,这是问题的一部分吗?


4
如果通过随机函数进行排序是产生均匀随机分布的一种方式,那将起作用。但是事实并非如此。-1
aaaaaaaaaaaa11年

为什么不呢?你能给我一个阅读链接吗?
sogart,2011年

2
结果的质量将根据排序算法而有很大的不同,但是在几乎所有情况下,结果都将与均等分布随机函数相差很远。这是一篇有关该主题的文章:sroucheray.org/blog/2009/11/…–
aaaaaaaaaaaaaa

-1

JavaScript 4行

function shuffle() {
  while(deck[0]!=0)swap(deck[0],rand(1,51))
  while(deck[0]++!=104)swap(deck[0]%51+1,rand(1,51))
  deck[0]=0
  swap(0,rand(0,51))
}

原始答案不够随机。不能保证交换会触及甲板上的每个项目。

// shuffle without locals
function shuffle() {
  deck.map(function(){swap(deck[rand(0,51)],deck[rand(0,51)])});
}

不会产生真正的随机混洗。这是可视化工具测试:jsfiddle.net/muk1bthm。我shuffle略微更改了您的代码以匹配我的swap实现,但这是逐字记录:jsfiddle.net/m7km4u6g
贾斯汀·摩根

为了明确起见,以上注释适用于新版本,但仍不是随机的。
贾斯汀·摩根
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.