Berikut adalah bacokan pada suatu algoritma. Itu tidak sempurna, dan tergantung pada berapa banyak waktu yang ingin Anda habiskan untuk memurnikannya, mungkin ada beberapa keuntungan kecil yang akan dibuat.
Anggap Anda memiliki daftar tugas yang harus dilakukan oleh empat antrian. Anda tahu jumlah pekerjaan yang terkait dengan melakukan setiap tugas, dan Anda ingin keempat antrian mendapatkan jumlah pekerjaan yang hampir sama, sehingga semua antrian akan selesai pada waktu yang sama.
Pertama, saya akan mempartisi tugas menggunakan modulous, dipesan berdasarkan ukurannya, dari kecil ke besar.
SELECT [time], ROW_NUMBER() OVER (ORDER BY [time])%4 AS grp, 0
The ROW_NUMBER()
pesanan setiap baris dengan ukuran, kemudian memberikan nomor baris, mulai dari 1. nomor baris ini diberikan sebuah "kelompok" (the grp
kolom) secara round-robin. Baris pertama adalah grup 1, baris kedua adalah grup 2, lalu 3, keempat mendapat grup 0, dan seterusnya.
time ROW_NUMBER() grp
---- ------------ ---
1 1 1
10 2 2
12 3 3
15 4 0
19 5 1
22 6 2
...
Untuk kemudahan penggunaan, saya menyimpan time
dan grp
kolom dalam variabel tabel bernama @work
.
Sekarang, kita dapat melakukan beberapa perhitungan pada data ini:
WITH cte AS (
SELECT *, SUM([time]) OVER (PARTITION BY grp)
-SUM([time]) OVER (PARTITION BY (SELECT NULL))/4 AS _grpoffset
FROM @work)
...
Kolom _grpoffset
adalah jumlah total time
per grp
berbeda dari rata-rata "ideal". Jika total time
semua tugas adalah 1000 dan ada empat kelompok, idealnya ada total 250 dalam setiap kelompok. Jika grup berisi total 268, grup itu _grpoffset=18
.
Idenya adalah untuk mengidentifikasi dua baris terbaik, satu di kelompok "positif" (dengan terlalu banyak pekerjaan) dan satu di kelompok "negatif" (dengan terlalu sedikit pekerjaan). Jika kita dapat bertukar grup pada dua baris itu, kita dapat mengurangi absolut _grpoffset
kedua grup.
Contoh:
time grp total _grpoffset
---- --- ----- ----------
3 1 222 40
46 1 222 40
73 1 222 40
100 1 222 40
6 2 134 -48
52 2 134 -48
76 2 134 -48
11 3 163 -21
66 3 163 -21
86 3 163 -21
45 0 208 24
71 0 208 24
92 0 208 24
----
=727
Dengan total total 727, setiap kelompok harus memiliki skor sekitar 182 untuk distribusinya menjadi sempurna. Perbedaan antara skor grup dan 182 adalah apa yang kami tempatkan di _grpoffset
kolom.
Seperti yang Anda lihat sekarang, di dunia terbaik, kita harus memindahkan sekitar 40 poin nilai baris dari grup 1 ke grup 2 dan sekitar 24 poin dari grup 3 ke grup 0.
Berikut kode untuk mengidentifikasi baris kandidat tersebut:
SELECT TOP 1 pos._row AS _pos_row, pos.grp AS _pos_grp,
neg._row AS _neg_row, neg.grp AS _neg_grp
FROM cte AS pos
INNER JOIN cte AS neg ON
pos._grpoffset>0 AND
neg._grpoffset<0 AND
--- To prevent infinite recursion:
pos.moved<4 AND
neg.moved<4
WHERE --- must improve positive side's offset:
ABS(pos._grpoffset-pos.[time]+neg.[time])<=pos._grpoffset AND
--- must improve negative side's offset:
ABS(neg._grpoffset-neg.[time]+pos.[time])<=ABS(neg._grpoffset)
--- Largest changes first:
ORDER BY ABS(pos.[time]-neg.[time]) DESC
) AS x ON w._row IN (x._pos_row, x._neg_row);
Saya menggabungkan diri dengan ekspresi tabel umum yang kami buat sebelumnya,: cte
Di satu sisi, grup dengan positif _grpoffset
, di sisi lain grup dengan yang negatif. Untuk lebih lanjut menyaring baris mana yang seharusnya cocok satu sama lain, swap dari baris sisi positif dan negatif harus ditingkatkan _grpoffset
, yaitu membuatnya lebih dekat ke 0.
The TOP 1
dan ORDER BY
memilih "terbaik" pertandingan swap pertama.
Sekarang, yang perlu kita lakukan hanyalah menambahkan UPDATE
, dan memutarnya sampai tidak ada lagi optimasi yang ditemukan.
TL; DR - inilah pertanyaannya
Berikut kode lengkapnya:
DECLARE @work TABLE (
_row int IDENTITY(1, 1) NOT NULL,
[time] int NOT NULL,
grp int NOT NULL,
moved tinyint NOT NULL,
PRIMARY KEY CLUSTERED ([time], _row)
);
WITH cte AS (
SELECT 0 AS n, CAST(1+100*RAND(CHECKSUM(NEWID())) AS int) AS [time]
UNION ALL
SELECT n+1, CAST(1+100*RAND(CHECKSUM(NEWID())) AS int) AS [time]
FROM cte WHERE n<100)
INSERT INTO @work ([time], grp, moved)
SELECT [time], ROW_NUMBER() OVER (ORDER BY [time])%4 AS grp, 0
FROM cte;
WHILE (@@ROWCOUNT!=0)
WITH cte AS (
SELECT *, SUM([time]) OVER (PARTITION BY grp)
-SUM([time]) OVER (PARTITION BY (SELECT NULL))/4 AS _grpoffset
FROM @work)
UPDATE w
SET w.grp=(CASE w._row
WHEN x._pos_row THEN x._neg_grp
ELSE x._pos_grp END),
w.moved=w.moved+1
FROM @work AS w
INNER JOIN (
SELECT TOP 1 pos._row AS _pos_row, pos.grp AS _pos_grp,
neg._row AS _neg_row, neg.grp AS _neg_grp
FROM cte AS pos
INNER JOIN cte AS neg ON
pos._grpoffset>0 AND
neg._grpoffset<0 AND
--- To prevent infinite recursion:
pos.moved<4 AND
neg.moved<4
WHERE --- must improve positive side's offset:
ABS(pos._grpoffset-pos.[time]+neg.[time])<=pos._grpoffset AND
--- must improve negative side's offset:
ABS(neg._grpoffset-neg.[time]+pos.[time])<=ABS(neg._grpoffset)
--- Largest changes first:
ORDER BY ABS(pos.[time]-neg.[time]) DESC
) AS x ON w._row IN (x._pos_row, x._neg_row);