Mengurutkan poin dalam urutan searah jarum jam?

156

Diberikan array x, poin y, bagaimana cara saya mengurutkan poin array ini dalam urutan searah jarum jam (sekitar titik tengah rata-rata keseluruhan)? Tujuan saya adalah meneruskan poin ke fungsi pembuatan garis untuk menghasilkan sesuatu yang terlihat "solid", cembung mungkin tanpa garis yang berpotongan.

Untuk apa nilainya, saya menggunakan Lua, tetapi kodesemu apa pun akan dihargai.

Pembaruan: Untuk referensi, ini adalah kode Lua berdasarkan pada jawaban Ciamej yang sangat baik (abaikan awalan "aplikasi" saya):

function appSortPointsClockwise(points)
    local centerPoint = appGetCenterPointOfPoints(points)
    app.pointsCenterPoint = centerPoint
    table.sort(points, appGetIsLess)
    return points
end

function appGetIsLess(a, b)
    local center = app.pointsCenterPoint

    if a.x >= 0 and b.x < 0 then return true
    elseif a.x == 0 and b.x == 0 then return a.y > b.y
    end

    local det = (a.x - center.x) * (b.y - center.y) - (b.x - center.x) * (a.y - center.y)
    if det < 0 then return true
    elseif det > 0 then return false
    end

    local d1 = (a.x - center.x) * (a.x - center.x) + (a.y - center.y) * (a.y - center.y)
    local d2 = (b.x - center.x) * (b.x - center.x) + (b.y - center.y) * (b.y - center.y)
    return d1 > d2
end

function appGetCenterPointOfPoints(points)
    local pointsSum = {x = 0, y = 0}
    for i = 1, #points do pointsSum.x = pointsSum.x + points[i].x; pointsSum.y = pointsSum.y + points[i].y end
    return {x = pointsSum.x / #points, y = pointsSum.y / #points}
end

Philipp Lenssen
sumber
1
Pikirkan tentang menghitung sudut garis radial melalui titik itu. Kemudian urutkan berdasarkan sudut.
Presiden James K. Polk
Jika Anda tidak tahu, lua memiliki fungsi ipairs(tbl)bawaan yang mengulangi indeks dan nilai tbl dari 1 hingga #tbl. Jadi untuk perhitungan penjumlahan, Anda dapat melakukan ini, yang menurut kebanyakan orang terlihat lebih bersih:for _, p in ipairs(points) do pointsSum.x = pointsSum.x + p.x; pointsSum.y = pointsSum.y + p.y end
Ponkadoodle
2
@ Wallalloloo Itu sangat bisa diperdebatkan. Juga, dalam vanili Lua ipairssecara signifikan lebih lambat daripada numerik untuk loop.
Alexander Gladysh
Saya harus membuat beberapa perubahan kecil untuk membuatnya berfungsi untuk kasus saya (hanya membandingkan dua poin relatif terhadap pusat) gist.github.com/personalnadir/6624172 Semua perbandingan dengan 0 dalam kode tampaknya mengasumsikan bahwa poin didistribusikan di sekitar titik asal, sebagai lawan dari titik arbitrer. Saya juga berpikir bahwa kondisi pertama akan mengurutkan poin di bawah titik pusat salah. Terima kasih untuk kodenya, sudah sangat membantu!
personalnadir

Jawaban:

192

Pertama, hitung titik pusatnya. Kemudian urutkan titik menggunakan algoritme pengurutan apa pun yang Anda suka, tetapi gunakan rutin perbandingan khusus untuk menentukan apakah satu titik kurang dari yang lain.

Anda dapat memeriksa apakah satu titik (a) adalah ke kiri atau ke kanan yang lain (b) dalam kaitannya dengan pusat dengan perhitungan sederhana ini:

det = (a.x - center.x) * (b.y - center.y) - (b.x - center.x) * (a.y - center.y)

jika hasilnya nol, maka mereka berada di garis yang sama dari pusat, jika itu positif atau negatif, maka itu di satu sisi atau yang lain, jadi satu titik akan mendahului yang lain. Dengan menggunakannya, Anda dapat membuat relasi yang kurang dari itu untuk membandingkan poin dan menentukan urutan kemunculannya dalam array yang diurutkan. Tetapi Anda harus menentukan di mana awal dari urutan itu, maksud saya sudut apa yang akan menjadi permulaan (misalnya setengah positif dari sumbu x).

Kode untuk fungsi perbandingan dapat terlihat seperti ini:

bool less(point a, point b)
{
    if (a.x - center.x >= 0 && b.x - center.x < 0)
        return true;
    if (a.x - center.x < 0 && b.x - center.x >= 0)
        return false;
    if (a.x - center.x == 0 && b.x - center.x == 0) {
        if (a.y - center.y >= 0 || b.y - center.y >= 0)
            return a.y > b.y;
        return b.y > a.y;
    }

    // compute the cross product of vectors (center -> a) x (center -> b)
    int det = (a.x - center.x) * (b.y - center.y) - (b.x - center.x) * (a.y - center.y);
    if (det < 0)
        return true;
    if (det > 0)
        return false;

    // points a and b are on the same line from the center
    // check which point is closer to the center
    int d1 = (a.x - center.x) * (a.x - center.x) + (a.y - center.y) * (a.y - center.y);
    int d2 = (b.x - center.x) * (b.x - center.x) + (b.y - center.y) * (b.y - center.y);
    return d1 > d2;
}

Ini akan memesan titik searah jarum jam mulai dari jam 12. Poin pada "jam" yang sama akan dipesan mulai dari yang jauh dari pusat.

Jika menggunakan tipe integer (yang tidak benar-benar ada dalam Lua) Anda harus memastikan bahwa variabel det, d1 dan d2 adalah tipe yang akan dapat menampung hasil perhitungan yang dilakukan.

Jika Anda ingin mencapai sesuatu yang terlihat solid, cembung mungkin, maka saya kira Anda sedang mencari Convex Hull . Anda dapat menghitungnya menggunakan Graham Scan . Dalam algoritma ini, Anda juga harus mengurutkan titik searah jarum jam (atau berlawanan arah jarum jam) mulai dari titik pivot khusus. Kemudian Anda mengulangi langkah loop sederhana setiap kali memeriksa apakah Anda belok kiri atau kanan menambahkan poin baru ke cembung cembung, pemeriksaan ini didasarkan pada produk silang seperti pada fungsi perbandingan di atas.

Edit:

Menambahkan satu lagi jika pernyataan if (a.y - center.y >= 0 || b.y - center.y >=0)untuk memastikan bahwa titik yang memiliki x = 0 dan negatif y diurutkan mulai dari yang lebih jauh dari pusat. Jika Anda tidak peduli dengan urutan poin pada 'jam' yang sama, Anda dapat menghilangkan pernyataan if ini dan selalu kembali a.y > b.y.

Mengoreksi pernyataan if pertama dengan menambahkan -center.xdan -center.y.

Menambahkan pernyataan if kedua (a.x - center.x < 0 && b.x - center.x >= 0). Itu adalah pengawasan yang jelas bahwa itu hilang. Pernyataan if bisa ditata ulang sekarang karena beberapa cek berlebihan. Misalnya, jika kondisi pertama dalam pernyataan pertama jika salah, maka kondisi pertama dari kedua jika harus benar. Saya memutuskan, bagaimanapun, untuk meninggalkan kode karena demi kesederhanaan. Sangat mungkin bahwa kompiler akan mengoptimalkan kode dan tetap menghasilkan hasil yang sama.

ciamej
sumber
25
+1: Tidak atan(), tidak ada akar kuadrat, dan bahkan tidak ada divisi. Ini adalah contoh pemikiran grafis komputer yang bagus. Culling semua case mudah sesegera mungkin, dan bahkan dalam hard case, hitung sesedikit mungkin untuk mengetahui jawaban yang diperlukan.
RBerteig
Tapi itu membutuhkan membandingkan semua poin dengan yang lainnya. Apakah ada metode sederhana untuk memasukkan poin baru?
Iterator
2
jika himpunan poin diketahui apriori, hanya diperlukan perbandingan O (n * log n) Jika Anda ingin menambahkan poin sementara itu maka Anda harus menyimpannya dalam set yang diurutkan seperti pohon pencarian biner seimbang. Dalam kasus seperti itu menambahkan titik baru membutuhkan perbandingan O (log n) dan itu persis sama untuk solusi yang melibatkan koordinat kutub.
ciamej
2
Apakah ini tidak ada kasusnya: if (ax - center.x <0 && bx - center.x> = 0) mengembalikan false;
Tom Martin
2
Hai yang disana. Sudah cukup tua, tetapi: "Ini akan memesan titik searah jarum jam mulai dari jam 12." Mengapa jam 12 dan bagaimana saya bisa mengubahnya menjadi 6? Adakah yang bisa memberitahuku?
Ismoh
20

Pendekatan alternatif yang menarik untuk masalah Anda adalah menemukan perkiraan minimum untuk Traveling Salesman Problem (TSP), yaitu. rute terpendek yang menghubungkan semua titik Anda. Jika poin Anda membentuk bentuk cembung, itu harus menjadi solusi yang tepat, jika tidak, itu akan tetap terlihat bagus (bentuk "padat" dapat didefinisikan sebagai salah satu yang memiliki rasio perimeter / area rendah, yang kami optimalkan di sini) .

Anda dapat menggunakan implementasi pengoptimal apa pun untuk TSP, yang saya yakin Anda dapat menemukan satu ton dalam bahasa pilihan Anda.

static_rtti
sumber
Astaga. "Menarik" adalah pernyataan yang meremehkan. :)
Iterator
@Iterator: Saya cukup senang dengan ide saya, saya cukup kecewa untuk downvoted untuk itu: - / Apakah Anda pikir itu valid?
static_rtti
1
Saya menyarankan untuk menggunakan salah satu dari banyak perkiraan cepat, bukan algoritma asli NP-complete, tentu saja.
static_rtti
6
Saya menghargai sudut tambahan! Untuk memiliki beberapa jawaban yang valid, jika jawaban yang sangat berbeda, mungkin akan sangat membantu jika seseorang di masa depan tersandung pada utas ini yang mencari pilihan untuk bertukar pikiran.
Philipp Lenssen
1
Perhatikan bahwa pendekatan saya mungkin lebih lambat, tetapi lebih benar dalam kasus kompleks: bayangkan kasus di mana titik untuk "8", misalnya. Koordinat kutub tidak akan membantu Anda dalam hal ini, dan hasil yang akan Anda peroleh akan sangat bergantung pada pusat yang Anda pilih. Solusi TSP tidak tergantung pada parameter "heuristik" apa pun.
static_rtti
19

Yang Anda minta adalah sistem yang dikenal sebagai koordinat kutub . Konversi dari Cartesian ke koordinat polar mudah dilakukan dalam bahasa apa pun. Rumus dapat ditemukan di bagian ini .

Setelah mengkonversi ke koordinat kutub, cukup urutkan berdasarkan sudut, theta.

Iterator
sumber
4
Ini akan berhasil, tetapi itu juga akan memiliki cacat melakukan lebih banyak perhitungan daripada yang dibutuhkan untuk menjawab pertanyaan pemesanan. Dalam praktiknya, Anda tidak benar-benar peduli tentang sudut aktual atau jarak radial, hanya urutan relatifnya. solusi ciamej lebih baik karena menghindari pembagian, akar kuadrat, dan trigonometri.
RBerteig
1
Saya tidak yakin apa kriteria Anda untuk "lebih baik". Misalnya, membandingkan semua poin satu sama lain adalah semacam pemborosan perhitungan. Trig bukanlah sesuatu yang menakutkan orang dewasa, bukan?
Iterator
3
Bukannya trigonometri itu menakutkan. Masalahnya adalah bahwa trigonometri mahal untuk dihitung, dan tidak diperlukan untuk menentukan urutan relatif sudut. Demikian pula, Anda tidak perlu mengambil akar kuadrat untuk meletakkan jari-jari. Konversi penuh dari Cartesian ke koordinat polar akan menghasilkan arc-tangent dan root kuadrat. Karenanya jawaban Anda benar, tetapi dalam konteks grafik komputer atau geometri komputasi, kemungkinan itu bukan cara terbaik untuk melakukannya.
RBerteig
Mengerti. Namun, OP tidak memposting sebagai comp-geo, itu adalah tag oleh orang lain. Namun, sepertinya solusi lain adalah jumlahnya banyak dalam # poin, atau apakah saya salah? Jika demikian, itu membakar lebih banyak siklus daripada trigonometri.
Iterator
Saya belum benar-benar memperhatikan tag comp-geo, saya hanya berasumsi bahwa satu-satunya aplikasi rasional untuk pertanyaan itu adalah yang satu atau yang lain. Bagaimanapun, pertanyaan kinerja menjadi diperdebatkan jika hanya ada beberapa poin, dan / atau operasi akan dilakukan cukup jarang. Pada saat itu, mengetahui cara melakukannya sama sekali menjadi penting dan itulah sebabnya saya setuju jawaban Anda benar. Ini menjelaskan cara menghitung gagasan tentang "urutan searah jarum jam" dalam istilah yang dapat dijelaskan kepada siapa saja.
RBerteig
3

Versi lain (mengembalikan true jika a datang sebelum b dalam arah berlawanan):

    bool lessCcw(const Vector2D &center, const Vector2D &a, const Vector2D &b) const
    {
        // Computes the quadrant for a and b (0-3):
        //     ^
        //   1 | 0
        //  ---+-->
        //   2 | 3

        const int dax = ((a.x() - center.x()) > 0) ? 1 : 0;
        const int day = ((a.y() - center.y()) > 0) ? 1 : 0;
        const int qa = (1 - dax) + (1 - day) + ((dax & (1 - day)) << 1);

        /* The previous computes the following:

           const int qa =
           (  (a.x() > center.x())
            ? ((a.y() > center.y())
                ? 0 : 3)
            : ((a.y() > center.y())
                ? 1 : 2)); */

        const int dbx = ((b.x() - center.x()) > 0) ? 1 : 0;
        const int dby = ((b.y() - center.y()) > 0) ? 1 : 0;
        const int qb = (1 - dbx) + (1 - dby) + ((dbx & (1 - dby)) << 1);

        if (qa == qb) {
            return (b.x() - center.x()) * (a.y() - center.y()) < (b.y() - center.y()) * (a.x() - center.x());
        } else {
            return qa < qb;
       } 
    }

Ini lebih cepat, karena kompiler (diuji pada Visual C ++ 2015) tidak menghasilkan lompatan untuk menghitung dax, hari, dbx, dby. Di sini rakitan keluaran dari kompiler:

; 28   :    const int dax = ((a.x() - center.x()) > 0) ? 1 : 0;

    vmovss  xmm2, DWORD PTR [ecx]
    vmovss  xmm0, DWORD PTR [edx]

; 29   :    const int day = ((a.y() - center.y()) > 0) ? 1 : 0;

    vmovss  xmm1, DWORD PTR [ecx+4]
    vsubss  xmm4, xmm0, xmm2
    vmovss  xmm0, DWORD PTR [edx+4]
    push    ebx
    xor ebx, ebx
    vxorps  xmm3, xmm3, xmm3
    vcomiss xmm4, xmm3
    vsubss  xmm5, xmm0, xmm1
    seta    bl
    xor ecx, ecx
    vcomiss xmm5, xmm3
    push    esi
    seta    cl

; 30   :    const int qa = (1 - dax) + (1 - day) + ((dax & (1 - day)) << 1);

    mov esi, 2
    push    edi
    mov edi, esi

; 31   : 
; 32   :    /* The previous computes the following:
; 33   : 
; 34   :    const int qa =
; 35   :        (   (a.x() > center.x())
; 36   :         ? ((a.y() > center.y()) ? 0 : 3)
; 37   :         : ((a.y() > center.y()) ? 1 : 2));
; 38   :    */
; 39   : 
; 40   :    const int dbx = ((b.x() - center.x()) > 0) ? 1 : 0;

    xor edx, edx
    lea eax, DWORD PTR [ecx+ecx]
    sub edi, eax
    lea eax, DWORD PTR [ebx+ebx]
    and edi, eax
    mov eax, DWORD PTR _b$[esp+8]
    sub edi, ecx
    sub edi, ebx
    add edi, esi
    vmovss  xmm0, DWORD PTR [eax]
    vsubss  xmm2, xmm0, xmm2

; 41   :    const int dby = ((b.y() - center.y()) > 0) ? 1 : 0;

    vmovss  xmm0, DWORD PTR [eax+4]
    vcomiss xmm2, xmm3
    vsubss  xmm0, xmm0, xmm1
    seta    dl
    xor ecx, ecx
    vcomiss xmm0, xmm3
    seta    cl

; 42   :    const int qb = (1 - dbx) + (1 - dby) + ((dbx & (1 - dby)) << 1);

    lea eax, DWORD PTR [ecx+ecx]
    sub esi, eax
    lea eax, DWORD PTR [edx+edx]
    and esi, eax
    sub esi, ecx
    sub esi, edx
    add esi, 2

; 43   : 
; 44   :    if (qa == qb) {

    cmp edi, esi
    jne SHORT $LN37@lessCcw

; 45   :        return (b.x() - center.x()) * (a.y() - center.y()) < (b.y() - center.y()) * (a.x() - center.x());

    vmulss  xmm1, xmm2, xmm5
    vmulss  xmm0, xmm0, xmm4
    xor eax, eax
    pop edi
    vcomiss xmm0, xmm1
    pop esi
    seta    al
    pop ebx

; 46   :    } else {
; 47   :        return qa < qb;
; 48   :    }
; 49   : }

    ret 0
$LN37@lessCcw:
    pop edi
    pop esi
    setl    al
    pop ebx
    ret 0
?lessCcw@@YA_NABVVector2D@@00@Z ENDP            ; lessCcw

Nikmati.

AGPX
sumber
1
Dua pernyataan pengembalian dalam saklar secara matematis setara. Apakah ada alasan untuk beralih?
unagi
0
  • vector3 a = vector3 baru (1, 0, 0) .................. Xtax
  • vector3 b = any_point - Tengah;
- y = |a * b|   ,   x =  a . b

- Atan2(y , x)...............................gives angle between -PI  to  + PI  in radians
- (Input % 360  +  360) % 360................to convert it from  0 to 2PI in radians
- sort by adding_points to list_of_polygon_verts by angle  we got 0  to 360

Akhirnya Anda mendapatkan Anticlockwize verts diurutkan

daftar. Balikkan () .................. Searah jarum jam

Pavan
sumber