但我覺得随機播放絕對要好實作些,用Math.random()産生一個介于1到歌曲數目之間的随機數便可,然後player.play(随機數)。
而清單的打亂情況要不一樣點,一是要呈現到界面,歌曲順序要随機排,二是播放順序不變,該哪是哪,隻是該位置上的歌曲可能已經變成其他曲目了。抽象出來就是數組元素的重排,那麼具體算法就值得探究了。
原文給出了三個循序漸近的例子,下面來看。
原文引入的現實情境是這樣的,假如你要洗牌,那麼最随機的做法無疑是從牌堆裡随便抽一張出來,然後放在一邊,之後從剩下的牌裡重複之前的操作,直到所有牌都被抽出來放到了另一堆中。抽象到代碼世界,按相同的做法,就是随機從數組裡取出一個元素,儲存到另一個數組,然後重複之,直到原數組中所有元素都處理掉。
下面是按這個思路的一個實作:
我們建立了一個copy數組,然後周遊目标數組,将其元素複制到copy數組裡,同時将該元素從目标數組中删除,這樣下次周遊的時候就可以跳過這個序号。而這一實作的問題正在于此,即使一個序号上的元素已經被處理過了,由于随機函數産生的數是随機的,所有這個被處理過的元素序号可能在之後的循環中不斷出現,一是效率問題,另一個就是邏輯問題了,存在一種可能是永遠運作不完!
Note:
Math.random()産生[0,1)的小數
delete 操作隻将數組元素的值删除,但不影響數組長度,删除後原來位置的值變為undefined
上面的分析已經看出問題的所在了,是以改進的做法就是處理完一個元素後,我們用Array的splice()方法将其從目标數組中移除同時也更新了目标數組的長度,如此一來下次周遊的時候是從新的長度開始,不會重複處理的情況了。
上面的做法已經可以了,但上面的改進依然還有提升空間。因為調用splice來删除數組元素會導緻删除位置之後的所有元素要做shift操作來向前補充,進而達到将數組長度減小的目的,當然這是在背景自動完成的,但這無疑增加了算法的複雜度。
注意到我們要做的僅僅是将數組元素重新排序,已經取出來的元素和剩下的元素之和一定是等于數組原來的總元素個數的。是以可以考慮不建立新的數組來儲存已經抽取的元素,可以這樣,随機從數組中抽出一個元素,然後與最後個元素交換,相當于把這個随機抽取的元素放到了數組最後面去,表示它已經是被随機過了,同時被換走的那個元素跑到前面去了,會在後續的重複操作中被随機掉。一輪操作過後,下一輪我們隻在剩下的n-1個元素也就是數組的前n-1個元素中進行相同的操作,直到進行到第一個。
上面介紹的便是在各語言中都廣為實作的Fisher-Yates亂序算法。但具體到JavaScript,我們其實可以結合數組自帶的sort()方法編寫出更簡潔的代碼來達到目的。中間變量以及值交換什麼的都省了,雖然背景實作肯定還是會進行值交換的,但我們不關心,一切交給sort()讓它自己處理。但這種方法也隻是簡潔而以,效果是不如上面介紹的算法的,因為随着數組元素越多,其随機性會變差。