如題,我使用 Excel 2013版本,
目前嘗試的作法是開啟 excel的反覆運算功能,使用函數
=IF(COUNTIF($A$1:$J$1000000,A1)>1,RANDBETWEEN(0,10000000)/10000000,A1) ,填滿工作表欄位 A1:J1000000,共一千萬個儲存格範圍。

如圖,用前述方式已經跑了超過12小時仍無結果,
想請問各位大神,excel有沒有其它比較有效率的方式,
例如:使用 Excel VBA?可以產生一千萬個介於0~1之間的不重複亂數?
感謝大家的幫忙!祝平安喜樂。
有時間限制嗎?
Excel我猜可能沒辦法太快,
先產生1~10,000,000的數字、亂數排序,
然後除以10,000,001
我比較好奇excel不會一直崩潰嗎![]()
小魚 感謝回答。
您提到的作法是不是就像我目前用的方式?=IF(COUNTIF($A$1:$J$1000000,A1)>1,RANDBETWEEN(0,10000000)/10000000,A1)
我是有預期應該不會太快,畢竟運算速度還是得看硬體效能。
但擺著超過12小時實在難耐,所以想問問有沒有方法?或者使用VBA的方式?可以更有效率產生需要的結果。

dragonH Excel是還沒崩潰,但鼠標就是一直轉圈圈超過12小時,轉到都快吐了還沒個結果...
1千萬筆本來就不小,
可以想像時間也要花不少,
不過如果不是用Excel VBA,
你要怎麼處理 不重複的資料 呢?
之前有人問的問題
亂數不重複
基本上是差不多的概念,
只要記憶體不爆掉,
總是跑得完的.
小魚 我不懂 excel vba的 code要如何寫?才能達成這目的。
我現在是開啟 excel的反覆運算功能,用 COUNTIF函數檢查每個儲存格內 RANDBETWEEN函數產生的亂數,在 A1:J1000000範圍內有沒有重複?
再搭配 IF函數,若亂數有重複就重新產生亂數,若無則保留原本的數值。在每個儲存格如此反覆運算最多100次。
我做了一個範例,
不會很難研究看看吧,
Private Sub test()
    Dim mybag As New Collection ' 宣告一個放球的袋子,類型是Collection集合
    
    totalRow = 9 '列數
    totalNum = 72 '總數
    For i = 1 To totalNum
        mybag.Add (i / (totalNum + 1)) ' 依序放入1到49的號碼球
    Next
    
    For i = 1 To totalNum
        R = Int(mybag.count * Rnd()) + 1 ' R 就是包包裡的球數*0~1之間的隨機數,集合跟陣列一樣,元素從1開始
        Row = ((i - 1) Mod totalRow) + 1
        Column = Int((i - 1) / totalRow) + 1
        Cells(Row, Column).Value = mybag.Item(R) ' 按c值在相對的列上填入球號
        mybag.Remove (R) ' 抽到的球就不要放回袋子(集合)裡了,直接拿掉
    Next
End Sub
這是執行結果
1千萬筆你就自己慢慢跑,
不奉陪了![]()
就是因為檢查所以會花很多時間,
用抽撲克牌的方式就不需要檢查.
小魚好的,那我先關掉那個到現在還沒結果的工作進度了...![]()
我試試看要怎麼改寫這段 vba的內容,感謝。
其實你只要改寫兩個地方,
不過你還是先研究清楚邏輯吧,
另外Excel VBA也可以 下中斷點(F9) 跟 逐步執行(F8) 來Debug.
小魚的方式好聰明啊!學起來
小魚我依照您提供的範例,改寫編碼如後,請問這樣正確嗎?
Private Sub test()
    Dim mybag As New Collection ' 宣告一個放球的袋子,類型是Collection集合
    
    totalRow = 1000000 '列數
    totalNum = 10000000 '總數
    For i = 1 To totalNum
        mybag.Add (i / (totalNum + 1)) ' 依序放入(1到10000000的號碼球/號碼+1)
    Next
    
    For i = 1 To totalNum
        R = Int(mybag.count * Rnd()) + 1 ' R 就是包包裡的球數*0~1之間的隨機數,集合跟陣列一樣,元素從1開始
        Row = ((i - 1) Mod totalRow) + 1
        Column = Int((i - 1) / totalRow) + 1
        Cells(Row, Column).Value = mybag.Item(R) ' 按c值在相對的列上填入球號
        mybag.Remove (R) ' 抽到的球就不要放回袋子(集合)裡了,直接拿掉
    Next
End Sub
另外,請問您提到的中斷點(F9)和逐步執行(F8),該如何寫入在 Excel vba的編碼內?
看起來應該是吧,
主要是改totalRow和totalNum,
其他就會自己處理了.
中斷點(F9)和逐步執行(F8)不是寫在Code裡面的,
是你在找問題的一些技巧,
你可以上網查查應該會有一些資料,
小魚好的,感謝回答。我正在嘗試用 Excel vab執行前述代碼,省去反覆檢查的功夫應該可以比較快完成,再來看看多久可以跑完...
小魚用抽撲克牌方式好聰明,不稱讚一下也不成。
10,000,000 = 250 x 40,000
A..IP = 250 Column
1..40000 = 40000 Row
A1..IP40000
公式只要寫 =RAND() 就好
海綿寶寶 抱歉,我看不懂您回應的內容,可以請您淺白點具體說明作法嗎?感謝。
你原本的寫法不太可能會不重複吧...
然後這個用excel做也太沒效率了,10000000次連要我用python寫(不用numpy/pandas)我覺得都會花很久。
golang,應該不用10分鐘吧:
package main
import (
	. "fmt"
	"math/rand"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"time"
	"github.com/360EntSecGroup-Skylar/excelize"
)
func main() {
	ex, err := os.Executable()
	if err != nil {
		panic(err)
	}
	exPath := filepath.Dir(ex)
	book := path.Join(exPath, "Book1.xlsx")
	f := excelize.NewFile()
	f.SetActiveSheet(1)
	columns := [10]string{
		"A",
		"B",
		"C",
		"D",
		"E",
		"F",
		"G",
		"H",
		"I",
		"J"}
	rand.Seed(time.Now().UnixNano())
	p := rand.Perm(10000000)
	for i := 1; i <= 1000000; i++ {
		_p := p[i*10-10 : i*10]
		for index, elm := range columns {
			cell := elm + strconv.Itoa(i)
			value := float64(_p[index]) / float64(10000000)
			Println(cell, value)
			f.SetCellValue("Sheet1", cell, value)
		}
	}
	Println(book)
	err = f.SaveAs(book)
	if err != nil {
		Println(err)
	}
}
https://drive.google.com/file/d/1iPOh_HqrH3Ysu2NzqE_1fwOhsDScg-wu/view?usp=sharing
我是順便試跨平台編譯,在mac上編的,能不能跑我不知道。
froce感謝回答!
我在 Win7 64bit + Excel 2013 64Bit的系統平台上執行程式,自 CMD視窗開啟到結束,大約需時 18分鐘,之後會產生一個大小為 0KB的 BOOK1.xlsx,且檔案無法由 Excel開啟,錯誤訊息如圖。

更改副檔名為 .xls後可開啟檔案。可惜內容為空白表格,否則這速度真是神快啊...![]()
有更新執行檔了,試試看。
原本的在mac上可以跑,在windows下則會因為路徑關係造成問題。
然後我的office 2013 32bit可以跑。
報告 froce,我使用更新後的執行檔試了 2次,最後停留狀態如圖:

不曉得是不是因為記憶體不足,所以導致問題?
目前用另一台實體記憶體 16GB,使用 Office 2016 64bit的電腦在試;也找一台電腦重新安裝 Office 2013 32bit版本,再試試看。
froce有結果了!
前面的情況應該是硬體效能不足導致。
使用 Win7 64 bit + Office 2016 64 bit的電腦跑出結果如圖:

真的是一千萬個亂數啊!![]()
這是目前得到最有效率的做法了!
真是太感謝您了!
我的記憶體都12G以上,不過因為這樣寫會在記憶體裡先建個xslx,然後都寫在記憶體裡,的確蠻吃記憶體的,在我的機器(win7 x64)吃到4.7G,難怪原本編32 bit的會有問題。
真的不行的話就找時間幫你改寫CSV版的好了。
froce 若您空檔時間有餘裕,也願意幫忙,改寫成 CSV版能減輕執行運算的硬體需求,當然再好不過了,感謝勞力!
package main
import (
	"encoding/csv"
	. "fmt"
	"math/rand"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"time"
)
func main() {
	start := time.Now().UnixNano()
	ex, err1 := os.Executable()
	if err1 != nil {
		panic(err1)
	}
	exPath := filepath.Dir(ex)
	sheet := path.Join(exPath, "Book1.csv")
	csvFile, err2 := os.Create(sheet)
	if err2 != nil {
		panic(err2)
	}
	defer csvFile.Close()
	csvWriter := csv.NewWriter(csvFile)
	rand.Seed(time.Now().UnixNano())
	p := rand.Perm(10000000)
	rp := [][]string{}
	for i := 0; i < 1000000; i++ {
		_p := make([]string, 10, 10)
		for index, elm := range p[i*10 : (i+1)*10] {
			value := strconv.FormatFloat(float64(elm)/float64(10000000), 'f', -1, 64)
			_p[index] = value
		}
		rp = append(rp, _p)
	}
	err3 := csvWriter.WriteAll(rp)
	if err3 != nil {
		panic(err3)
	} else {
		csvWriter.Flush()
	}
	end := time.Now().UnixNano()
	Println("花費時間:", float64(end-start)/float64(1000000000), "秒")
}
https://drive.google.com/file/d/1as9vj6j3d-T4qL4B__CoP5WzVmgof6Og/view?usp=sharing
CSV版記憶體佔用不到1G...
python:
import csv
import random as rn
import time
start = time.time()
_ = [str(i/10000000) for i in range(10000000)]
rn.shuffle(_)
with open("./test.csv", "w", encoding="utf8") as csvfile:
    writer = csv.writer(csvfile)
    for i in range(1000000):
        writer.writerow(_[i*10:(i+1)*10])
end = time.time()
print("花費時間:", end-start, "秒")
我錯怪python了...還蠻快的。Orz
不輸出中間結果(print)大概18秒,golang 4秒。
記憶體倒是都吃差不多,700MB左右。
froce 我滿懷感激地收下 randomToCSV.exe檔案,感謝大力相助!![]()
你是運氣好剛好遇到我最近想好好的來學GO,要不然我也懶得寫。XD
然後寫了python版才覺得python真的很好用,除了慢和沒有編譯後執行檔以外,寫code的限制少很多,開發更是快到不行。python版的我開始寫不到10分鐘就寫完了。
GO得一直轉型,限制也多,重點是沒有map、filter這類的語意式函式,讓歷遍只能寫迴圈去做。
不過GO的好處就是跨平台編譯簡單、不用顧lib的相依性、執行速度快、還有還沒學到的非同步編程。
我用 C# 跑,用EPPLUS產生EXCEL檔
1、不考慮亂數重覆的前題,以下程式產生不到2分鐘
(i5-3470處理器、8GB DDR3,SSD、Windows10)
using OfficeOpenXml; //EPPLUS
using System;
using System.Diagnostics;
using System.IO;
namespace bigRandom
{
    class Program
    {
        static void Main(string[] args)
        {
            if (File.Exists("export.xlsx")) File.Delete("export.xlsx");
            ExcelPackage excel = new ExcelPackage(new System.IO.FileInfo("export.xlsx"));
            ExcelWorksheet ews = excel.Workbook.Worksheets.Add("輸出結果");
            int QRow = 1;
            int QCol = 1;
            int xx = 1;
            for(int i = 0;i<10000000;i++)
            {
                Random Rng = new Random(Guid.NewGuid().GetHashCode());
                int x = Rng.Next(0, 1000000000); //產生亂數
                ews.Cells[QRow, QCol].Value = (double)x / 1000000000;
                QCol++; //從A跑到J
                if (QCol > 10) //如果下一行超過 J
                {
                    QCol = 1;//回到A
                    QRow++;  //換行
                }
            }
            excel.Save();
            if (File.Exists("export.xlsx")) Process.Start("export.xlsx");
        }
    }
}

2、考慮不可重覆.................
我用LINUX + PYTHON + LIST及C# + LIST作抽球實驗,效能真的都很差,每跑10000組隨機抽球就需時1分鐘以上,跑完1000萬組,一天一定跑不掉
如果有興趣實驗效能的話,還是留下程式給各位玩玩,多了一個LIST,此例效能實在不好,執行完需時3+小時
using OfficeOpenXml;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
namespace bigRandom
{
    class Program
    {
        static void Main(string[] args)
        {
            if (File.Exists("export.xlsx")) File.Delete("export.xlsx");
            ExcelPackage excel = new ExcelPackage(new System.IO.FileInfo("export.xlsx"));
            ExcelWorksheet ews = excel.Workbook.Worksheets.Add("輸出結果");
            List<int> ballbag = new List<int>();// 球袋
            for (int b = 1; b < 10000000; b++)
                ballbag.Add(b); //產生1000萬個號碼球並放入球袋裡
            int QRow = 1;
            int QCol = 1;
            while (ballbag.Count > 0) // 一直跑到球袋抽空為止
            {
                Random Rng = new Random(Guid.NewGuid().GetHashCode());
                int x = Rng.Next(0,ballbag.Count); //從袋中取出號碼球
                ews.Cells[QRow, QCol].Value = (double)ballbag[x] / 100000000; // 抽出的球所代表的數值除以1000萬01
                ballbag.RemoveAt(x);
                QCol++; //從A跑到J
                if (QCol > 10) //如果下一行超過J,則換行
                {
                    QCol = 1; //
                    QRow++;
                }
            }
            excel.Save();
            if (File.Exists("export.xlsx")) Process.Start("export.xlsx");
        }
    }
}
                    python慢是一定的,只是沒想到C#也要弄到3小時...
不過python用了numpy和pandas的話大概不一定會很久,因為底層是C。
都是慢在LIST的REMOVE操作上......
C#沒有直接對list做shuffle的函式嗎?
有耶,但不知道效能是不是一樣糟而已
private static Random rng = new Random();  
public static void Shuffle<T>(this IList<T> list)  
{  
    int n = list.Count;  
    while (n > 1) {  
        n--;  
        int k = rng.Next(n + 1);  
        T value = list[k];  
        list[k] = list[n];  
        list[n] = value;  
    }  
}
//用法:
List<Product> products = GetProducts();
products.Shuffle();
                    我自已用php來寫。因為有卡到記憶體用量問題。(說真的,一千萬筆就算只有一個字元,也需要暫用到10m的用量。但字元數一組最多也需要約8個字元。少說要用到100mb的用量左右。
1.所以我用如下的先各自生成100萬筆的數據10組。每組的數值範圍不同。
如第一組是1~100萬。第二組是100萬零1起~200萬。每組產生的值先各自存到不同的文件上。共10個文件。
2.再產生資料前會先亂數處理後再儲存。
其實我的想法跟分層抽樣的做法也是一樣的。如果不是因為「Allowed memory size」的問題
其實是能直接用如下的方式就可以處理了
for ($i = 1; $i <= 10000000; $i++) {
          $a[] = $i;
    }
shuffle($a);
運行不需要1秒。我有試著做寫檔約在10秒有右。
不過這樣做很消耗記憶體。我是將變數容量一直開到2G才可以跑的。
我後續試著寫成CSV的資料。是可以在10秒內完成寫檔。
我後續有試著改成不調整記憶容量的做法。用50萬一組做一個分層寫入20個檔案。
感覺反而變快了。運行時間約5秒就可以完成。
只是在試著各自20個文件讀取後再插入CSV檔。又會報「Allowed memory size」的錯誤。
所以作罷