Solidity
洗牌數組/映射
是否可以打亂地址的數組或映射?我嘗試了這個解決方案,但它總是返回相同的結果
for (uint256 i = 0; i < arrayAddress.length; i++) { uint256 n = i + uint256(keccak256(abi.encodePacked(block.timestamp))) % (arrayAddress.length - i); address temp = arrayAddress[n]; arrayAddress[n] = arrayAddress[i]; arrayAddress[i] = temp; }
我為此創建了一個簡單的解決方案。我使用數字而不是地址來保持簡單和易於視覺化。
- 使用
block.timestamp
andblock.number
時,我們需要小心,因為如果您使用這些值的函式(或交易)沒有修改狀態,那麼block.timestamp
andblock.number
將是最後一個修改狀態的區塊的時間戳和區塊號. 只有當您的函式修改狀態block.timestamp
並block.number
實際引用不同的值時。這可能是您多次執行函式時看到相同結果的原因,因為block.timestamp
總是相同的(如果您沒有進行任何其他修改狀態的事務)並且keccak256
函式正在創建相同的雜湊,因此結果相同.- 也許不需要
keccak256
在循環的每次迭代中計算散列。for
我們可以計算一個雜湊值,然後從中讀取每個字節,並將其用作值來使用%
我們需要的運算符創建索引。除非我們的數組長度超過 256 個元素,否則我們可以讀取 2 個字節,這將為我們提供大約 65536 個值,依此類推。我建議這樣做是因為如果我們的數組永遠不會超過 256 個元素,假設我們的數組有 256 個元素,我們只需要計算keccak256
散列大約 256 / 32 次,即 8 次,而不是更糟case 256 次,效率更高。- 在循環中修改狀態可能不是一個好主意,我們可能會得到一個 out of gas 異常。為了解決這個問題,我們可以將狀態數據複製為
memory
,對其進行修改,然後在循環結束後再次將其保存到儲存中。- 如果你的函式不修改狀態,那麼你不能依賴
block.timestamp
orblock.number
。然後,您需要以某種方式為您的keccak256
雜湊提供“種子”以使其唯一。可能會向您的函式發送一個額外的參數、一個隨機數等,以便您使用它而不是block.timestamp
.我寫了一些程式碼並在下面放了一些註釋和結果,以便您查看、使用和修改它:
// SPDX-License-Identifier: MIT pragma solidity 0.8.16; // Autor: Jeremy Then contract Shuffle { uint[] public uints = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]; function getInts() public view returns(uint[] memory) { return uints; } // This shuffle function works by creating a keccak256 hash of the current block.timestamp + a counter // to produce a different hash every time we need one. // Then, we use each byte as our value to generate the index we are going to swap the ith element with // by using the modulo (%) operator. // A keccak256 hash only has 32 bytes, so, in case the array we are trying to shuffle is bigger than // 32 elements, then we create a different keccak256 hash with the counter, and start taking each // byte of this new hash one by one to derive the index to swap the ith element with.. // This function asumes that the array to shuffle is no bigger than 255 elements. If so, // then we would need to read 2 bytes instead of one. Because 1 byte only provides us 256 (2^8) values // to use as index, but 2 bytes would provide us with 65536 (2^16) values to use as index, which // we then cut it down to the range of our array using the % operator. function shuffle() public returns(uint[] memory) { // Making a copy of the uints array, since modifying it from storage is really expensive // and we could get a out of gas exception uint[] memory uintsCopy = uints; uint counter = 0; uint j = 0; bytes32 b32 = keccak256(abi.encodePacked(block.timestamp + counter)); uint length = uintsCopy.length; for (uint256 i = 0; i < uintsCopy.length; i++) { if(j > 31) { b32 = keccak256(abi.encodePacked(block.timestamp + ++counter)); j = 0; } uint8 value = uint8(b32[j++]); uint256 n = value % length; uint temp = uintsCopy[n]; uintsCopy[n] = uintsCopy[i]; uintsCopy[i] = temp; } // Now, modifying the state uints array, once as a whole. uints = uintsCopy; return uintsCopy; } // Since this function does not modify the state and does not pay gas, // the `block.timestamp` will be the timestamp of the last block that modified the state // of this contract. // If another function is called that cause a state change, then that is an actual // transaction that was mined, paid gas and then block.timestamp will be the timestamp of the // block of that last transaction. function getTimestamp() public view returns(uint) { return block.timestamp; } // Since this function does not modify the state and does not pay gas, // the `block.number` will be the block number of the last block that modified the state // of this contract function getBlocknumber() public view returns(uint) { return block.number; } } // Running the shuffle function multiple times, it produced shuffled values like these: // 15,12,5,2,19,10,16,1,3,11,20,9,7,13,8,6,18,4,17,14 // 11,1,4,9,16,3,17,10,14,19,2,6,13,8,7,15,12,5,18,20 // 7,1,17,6,16,18,20,2,15,5,10,11,3,13,14,12,19,8,9,4 // 18,4,1,6,11,14,10,20,7,2,3,5,13,12,16,8,15,19,17,9