陣列如何把它的順序打亂,作出類似洗牌的效果,我一直都很頭痛,搞得非常的複雜。至從用了Ruby,Array物件包含shuffle
方法之後,我就沒思考過陣列洗牌的問題了,反正Ruby幫我處理得好好的。
Ruby
# 52張牌的牌堆
poker = (1..52).to_a # => [1, 2, 3, ... , 52]
# 洗牌打亂
shuffled = poker.shuffle # => [22, 32, 12, ...
# 也可以直接打亂原來的陣列
poker.shuffle! # => [39, 47, 3, ...
Javascript
Javascript我就頭痛了,我得自己寫洗牌的方法。我在網路上找到了這個演算法,仔細看了之後,才知道原來洗牌可以這麼簡單:
// 原本for迴圈是一行程式,太難理解,這邊改寫成多行
function shuffle(o){
for(var j, x, i = o.length; i;){
j = Math.floor(Math.random() * i);
// javascript的array是0-base
// 所以迴圈第一次進入,--i後表示陣列最後一個位置。
x = o[--i];
o[i] = o[j];
o[j] = x;
// 以上三行代表以x為temp, o[i], o[j]做交換
}
return o; //回傳陣列,我一開始也看錯看成回傳0
};
變數說明
- 引數o: 將被洗牌的陣列
- for迴圈內
- i : 將會從陣列的最後一個位置,慢慢往前移到第一個位置(但移到第一個位置時for迴圈不執行,因為Javascript的數值0也代表false,會離開迴圈。0代表flase這點跟Ruby不一樣)
- j : 將會被亂數選擇,選到要被交換的位置
- x : 用來暫存o[i]的數值,幫助o[i]與o[j]做數值交換
就這樣從最後一個位置開始,依次往前隨機挑選一個位置與它交換(可能挑到自己,表示不交換),來達到洗牌的效果,陣列多大,就執行幾次,時間複雜度O(n)
,效率也不錯。
注意這邊傳入的陣列是call by reference
,會修改原來呼叫方法時傳入的陣列,所以直接:
// 5張牌的牌堆
poker = [1, 2, 3, 4, 5];
shuffle(poker);
// poker 就直接被打亂了
如果不想打亂原來的牌堆,就稍微改寫一下,不要動到原來的陣列:
function shuffle(o){ //v1.0
var ary = o.slice(0);
for(var j, x, i = ary.length; i;){
j = Math.floor(Math.random() * i);
x = ary[--i], ary[i] = ary[j];
ary[j] = x;
}
return ary;
};
這邊用到了一個小技巧slice(start, end)
,這是javascript用來複製陣列的一個區塊,指令開始與結束的位置,小技巧是如果傳入0,就直接複製整個陣列。如果你想說:
啊直接ary = o 不是更快
呵呵,我建議你從C的指標在開始複習一下。
C語言
使用C語言,改些小地方,C語言的rand()
產生的是0 ~ RAND_MAX
的一個數字,所以直接j = rand() % i;
就行了。還有C語言的0也表示false的意思,大概只有Ruby跟大家不同,目前我只見過Ruby的0表示true。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int *shuffle(int *o, int length){
int i,j,x;
for(j, x, i=length; i;){
j = rand() % i;
x = o[--i];
o[i] = o[j];
o[j] = x;
}
return o;
}
int main(void) {
srand(time(NULL));
int i, poker[] = {1, 2, 3, 4, 5};
shuffle(poker, 5);
for(i=0; i<5; i++)
printf("%d ", poker[i]);
return 0;
}
這邊我是用call by address
的寫法,如果你不想改變原來的陣列。C語言有一堆記憶體的函式讓你用,你可以宣告一塊記憶體空間(malloc
),然後記憶體複製(memcpy
),之後傳入shuffle去洗牌
不建議在shuffle函式裡面宣告記憶體區塊,因為什麼時候要free
掉記憶體很麻煩,交給呼叫他的函式去決定什麼時候free
掉比較正確,shuffle函式就專心洗牌就好。
JQuery
在網頁上面,要把一些HTML的元件打亂,可以直接在JQuery裡面加個物件方法給他:
jQuery.fn.shuffleElements = function () {
var o = $(this);
// 這邊的for迴圈,使用原本的一行寫法
for (var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
return o;
};
裡面那個for迴圈洗牌的演算法沒變。
至於一些基本的jQuery用法,jQuery.fn
,可以直接取得jQuery的prototype,關於javascript的prototype是什麼,請至函式 prototype 特性觀看。這邊使用jQuery.fn
就可以直接在jQuery的protoype裡面加入shuffleElements
方法,然後就直接傳入一堆HTML元件讓他去洗牌,譬如你的網頁有一堆圖片,簡單一點就這樣:
var img = $("img").shuffleElements();
$("body").append(img);
這麼寫的話,可以簡單地把你網頁裡面的圖片全部洗牌之後,再全部放回<body>
最下面。這邊主要是講洗牌,你可以改一改把它變成<ul>
的順序之類的,或其他的應用。
沒有留言:
張貼留言