DJ playlist에서 Shuffle기능이 추가되었으면 좋겠다는 피드백을 받아 무작위 재생 기능을 어떻게 구현하면 좋을지 알아보게되면서 Fisher–Yates shuffle 라는 것을 알게되었다.
The Fisher–Yates shuffle is an algorithm for generating a random permutation of a finite sequence—in plain terms, the algorithm shuffles the sequence. The algorithm effectively puts all the elements into a hat; it continually determines the next element by randomly drawing an element from the hat until no elements remain. The algorithm produces an unbiased permutation: every permutation is equally likely.
위키피디아에 의하면 Fisher–Yates shuffle은 유한한 시퀀스의 랜덤 순열을 생성하기 위한 알고리즘으로 편향되지 않는 순열을 생성한다고 한다.
-- To shuffle an array a of n elements (indices 0..n-1):
for i from n−1 downto 1 do
j ← random integer such that 0 ≤ j ≤ i
exchange a[j] and a[i]
pseudo code는 위와 같으며 이제 이걸 코드로 구현하면 다음과 같이 나타낼 수 있다.
const shufflePlaylist = (oriPlaylist: IMusic[]) => {
const copiedPlaylist = [...oriPlaylist];
for (let i = copiedPlaylist.length - 1; i > 0; i -= 1) {
const j = Math.floor(Math.random() * (i + 1));
[copiedPlaylist[i], copiedPlaylist[j]] = [
copiedPlaylist[j],
copiedPlaylist[i],
];
}
return copiedPlaylist;
};
그리고 적절하게 섞여지는지 확인하기 위해 약 10만번 테스트해보았을때 현재 재생중인 노래를 제외하고 편향되지 않게 노래가 선택되는 것을 확인할 수 있었다.
그렇다면 이제 구현한 셔플기능을 기존의 코드에 적용해주면 되는데 shuffle 기능이 켜져있는지에 따라서 기존의 플레이리스트를 사용할지 셔플된 플레이리스트를 사용하면 될지만 결정해주면 되었다.
const handleChangeMusic = ({ music, isNext }: IMusicChange) => {
const playlistLen = playlist.length - 1;
const newPlaylist = shuffle ? shufflePlaylist(playlist) : playlist;
const curPlayMusicIdx = getCurrentMusicIdx({
newPlaylist,
curMusic: music,
});
if (curPlayMusicIdx === NOT_INCLUDE_DJPLAYLIST) return;
const nextMusic = isNext
? newPlaylist[curPlayMusicIdx === playlistLen ? 0 : curPlayMusicIdx + 1]
: newPlaylist[curPlayMusicIdx === 0 ? playlistLen : curPlayMusicIdx - 1];
setPlayer((prev) => ({
...prev,
selectedMusic: nextMusic,
}));
};