Bir listenin tüm permütasyonlarını bitişik eşit öğeler olmadan oluşturun


87

Bir listeyi sıraladığımızda,

a = [1,2,3,3,2,2,1]
sorted(a) => [1, 1, 2, 2, 2, 3, 3]

eşit öğeler her zaman sonuç listesinde bitişiktir.

Bunun zıttı görevi nasıl başarabilirim - listeyi karıştırın, böylece eşit öğeler asla (veya mümkün olduğunca nadiren) bitişik olmaz?

Örneğin, yukarıdaki liste için olası çözümlerden biri şudur:

p = [1,3,2,3,2,1,2]

Daha resmi olarak, bir liste verildiğinde , çiftlerin sayısını en aza indiren abir permütasyon oluşturur p.p[i]==p[i+1] .

Listeler büyük olduğundan, tüm permütasyonların oluşturulması ve filtrelenmesi bir seçenek değildir.

Bonus soru: tüm bu tür permütasyonlar verimli bir şekilde nasıl oluşturulur?

Çözümleri test etmek için kullandığım kod bu: https://gist.github.com/gebrkn/9f550094b3d24a35aebd

UPD: Burada bir kazanan seçmek zor bir seçimdi çünkü birçok kişi mükemmel yanıtlar yayınladı. @VincentvanderWeele , @David Eisenstat , @Coady , @ enrico.bacis ve @srgerg , mümkün olan en iyi permütasyonu kusursuz bir şekilde üreten işlevler sağladı. @tobias_k ve David de bonus sorusunu yanıtladı (tüm permütasyonları oluştur ). Doğruluk kanıtı için David'e ek noktalar.

@ VincentvanderWeele'den gelen kod en hızlı gibi görünüyor.


1
Yani sadece eşitliği mi önemsiyorsun ? gibi bir şey , kriterinize göre [1, 2, 1, 3, 1, 4, 1, 5]tamamen aynı [1, 3, 1, 2, 1, 4, 1, 5]mı?
Bakuriu

1
"Etkili" bir algoritma olamaz. Böyle bir liste al [1, 1, 1, ..., 2, 3, 4, ..., N]ile 2Nelemanları. İyi bir permütasyon elde etmek için n > 1her ardışık çift arasına bir sayı koyabilirsiniz 1. Sonra N/2öğeleri permütasyon ve tüm geçerli permütasyonları elde edersiniz (yani hiçbiri kötü değildir, ancak daha fazlası olabilir). Bu tür permütasyonların sayısı O (N ^ 2), dolayısıyla O (N ^ 2) 'den daha iyisini yapamazsınız. Yine de naif yaklaşımın O (N ^ 3) 'ten daha iyi.
Bakuriu

6
@Bakuriu: İki şey: (1) Açık olmak gerekirse, örneğiniz bonus sorusu için etkili bir algoritmanın olamayacağını gösteriyor . (2) Örneğiniz için tüm optimal çözümleri numaralandırmak O ((N / 2)!), Ki bu O (N ^ 2) 'den çok daha kötüdür (yani, örneğiniz fark ettiğinizden çok daha güçlü :-)
j_random_hacker

11
@msw: Bir web sitesi yapıyorum ve farklı sağlayıcılardan reklam bloklarının olduğu bir satır var. Onları, aynı sağlayıcıdan hiçbir blok yan yana durmayacak şekilde düzenlemek istiyorum.
georg

2
Bunun "bir kopyaya yakın bile olmadığını" söyleyemem, ancak iddia edilen kopya farklı bir sorudur, çünkü aynı öğeler arasındaki mesafe dikkate alınır. WhyCry'nin yorumundan sonra kapatmaya oy veren kişiler: lütfen gelecekte daha fazla dikkat edin.
David Eisenstat

Yanıtlar:


30

Bu, Thijser'in şu anda tamamlanmamış sözde kodunun çizgileri boyunca. Buradaki fikir, yeni alınmadıkça kalan öğe türlerinden en sık olanını almaktır. (Ayrıca Coady'nin bu algoritma uygulamasına bakın .)

import collections
import heapq


class Sentinel:
    pass


def david_eisenstat(lst):
    counts = collections.Counter(lst)
    heap = [(-count, key) for key, count in counts.items()]
    heapq.heapify(heap)
    output = []
    last = Sentinel()
    while heap:
        minuscount1, key1 = heapq.heappop(heap)
        if key1 != last or not heap:
            last = key1
            minuscount1 += 1
        else:
            minuscount2, key2 = heapq.heappop(heap)
            last = key2
            minuscount2 += 1
            if minuscount2 != 0:
                heapq.heappush(heap, (minuscount2, key2))
        output.append(last)
        if minuscount1 != 0:
            heapq.heappush(heap, (minuscount1, key1))
    return output

Doğruluğun kanıtı

K1 ve k2 sayımlarına sahip iki öğe türü için, optimum çözüm k1 <k2 ise k2 - k1 - 1, k1 = k2 ise 0 ve k1> k2 ise k1 - k2 - 1 kusurlarına sahiptir. = Durum açıktır. Diğerleri simetriktir; azınlık unsurunun her bir örneği, olası toplam k1 + k2 - 1'den en fazla iki kusuru önler.

Bu açgözlü algoritma, aşağıdaki mantıkla en uygun çözümleri döndürür. Optimal bir çözüme genişlemesi halinde güvenli bir önek (kısmi çözüm) diyoruz . Açıktır ki boş önek güvenlidir ve eğer güvenli bir önek tam bir çözümse, o zaman bu çözüm en uygunudur. Her açgözlü adımın güvenliği koruduğunu endüktif olarak göstermek yeterlidir.

Açgözlü bir adımın bir kusuru getirmesinin tek yolu, yalnızca bir öğe türünün kalmasıdır, bu durumda devam etmenin tek bir yolu vardır ve bu yol güvenlidir. Aksi takdirde, P, ele alınan adımdan hemen önceki (güvenli) önek olsun, P 'hemen sonra önek olsun ve S, P'yi genişleten optimal bir çözüm olsun. S de P'yi genişletirse, işimiz bitti demektir. Aksi takdirde, P '= Px ve S = PQ ve Q = yQ' olsun, burada x ve y öğelerdir ve Q ve Q 'dizilerdir.

Önce P'nin y ile bitmediğini varsayalım. Algoritmanın seçimine göre, x en az Q'da y kadar sıktır. Yalnızca x ve y içeren maksimum Q alt dizelerini düşünün. İlk alt dize en az y'ler kadar x'e sahipse, o zaman x ile başlamak için ek kusurlara neden olmadan yeniden yazılabilir. İlk alt dizede x'ten daha fazla y varsa, diğer bazı alt dizelerde y'den daha fazla x bulunur ve bu alt dizeleri ek kusurlar olmadan yeniden yazabiliriz, böylece x önce gelir. Her iki durumda da, gerektiğinde P '' yi genişleten optimal bir çözüm T buluyoruz.

Şimdi P'nin y ile bittiğini varsayalım. X'in ilk oluşumunu öne taşıyarak Q'yu değiştirin. Bunu yaparken, en fazla bir kusur ortaya koyuyoruz (x'in olduğu yerde) ve bir kusuru ortadan kaldırıyoruz (yy).

Tüm çözümleri üretmek

Bu, tobias_k'ın yanıtı artı şu anda değerlendirilen seçimin bir şekilde küresel olarak kısıtlandığını tespit etmek için etkili testlerdir. Asimptotik çalışma süresi optimumdur, çünkü üretimin ek yükü çıktının uzunluğuna göre sıralanır. En kötü durum gecikmesi ne yazık ki ikinci dereceden; daha iyi veri yapıları ile doğrusal (optimal) düzeye indirilebilir.

from collections import Counter
from itertools import permutations
from operator import itemgetter
from random import randrange


def get_mode(count):
    return max(count.items(), key=itemgetter(1))[0]


def enum2(prefix, x, count, total, mode):
    prefix.append(x)
    count_x = count[x]
    if count_x == 1:
        del count[x]
    else:
        count[x] = count_x - 1
    yield from enum1(prefix, count, total - 1, mode)
    count[x] = count_x
    del prefix[-1]


def enum1(prefix, count, total, mode):
    if total == 0:
        yield tuple(prefix)
        return
    if count[mode] * 2 - 1 >= total and [mode] != prefix[-1:]:
        yield from enum2(prefix, mode, count, total, mode)
    else:
        defect_okay = not prefix or count[prefix[-1]] * 2 > total
        mode = get_mode(count)
        for x in list(count.keys()):
            if defect_okay or [x] != prefix[-1:]:
                yield from enum2(prefix, x, count, total, mode)


def enum(seq):
    count = Counter(seq)
    if count:
        yield from enum1([], count, sum(count.values()), get_mode(count))
    else:
        yield ()


def defects(lst):
    return sum(lst[i - 1] == lst[i] for i in range(1, len(lst)))


def test(lst):
    perms = set(permutations(lst))
    opt = min(map(defects, perms))
    slow = {perm for perm in perms if defects(perm) == opt}
    fast = set(enum(lst))
    print(lst, fast, slow)
    assert slow == fast


for r in range(10000):
    test([randrange(3) for i in range(randrange(6))])

23

Sözde kod:

  1. Listeyi sırala
  2. Sıralanan listenin ilk yarısında döngü yapın ve sonuç listesinin tüm çift dizinlerini doldurun
  3. Sıralanan listenin ikinci yarısında döngü yapın ve sonuç listesinin tüm tek endekslerini doldurun

Sadece p[i]==p[i+1]girdinin yarısından fazlası aynı elemandan oluşuyorsa sahip olacaksınız , bu durumda aynı elemanı ardışık noktalara koymaktan başka seçeneğiniz yoktur (pidgeon deliği prensibine göre).


Yorumlarda belirtildiği gibi, bu yaklaşımın, unsurlardan birinin en az n/2kez (veya n/2+1tek için n; bu (n+1)/2)hem çift hem de tek için genelleştirilmesi) olması durumunda çok fazla bir çatışması olabilir . Bu türden en fazla iki öğe vardır ve iki tane varsa algoritma gayet iyi çalışır. Tek sorunlu durum, zamanın en az yarısında meydana gelen bir unsurun olmasıdır. Bu sorunu, önce elementi bularak ve onunla ilgilenerek çözebiliriz.

Python hakkında bunu doğru bir şekilde yazmak için yeterince bilgim yok, bu yüzden OP'nin önceki bir sürümün uygulamasını github'dan kopyalama özgürlüğüne sahip oldum:

# Sort the list
a = sorted(lst)

# Put the element occurring more than half of the times in front (if needed)
n = len(a)
m = (n + 1) // 2
for i in range(n - m + 1):
    if a[i] == a[i + m - 1]:
        a = a[i:] + a[:i]
        break

result = [None] * n

# Loop over the first half of the sorted list and fill all even indices of the result list
for i, elt in enumerate(a[:m]):
    result[2*i] = elt

# Loop over the second half of the sorted list and fill all odd indices of the result list
for i, elt in enumerate(a[m:]):
    result[2*i+1] = elt

return result

Anladığım kadarıyla @jojo'nun yaptığı şey bu - her zaman optimal değil.
georg

10
Bu , 0 tabanlı veya 1 tabanlı endeksler kullanmanıza bağlı olarak [0, 1, 1]veya için başarısız olur [0, 0, 1].
flornquake

@georg Aslında bu, cevabımdaki yaklaşımın aynısı. (Heuster'in benden önce cevap verdiğine dikkat edin!). Benim kodumda 2. ve 3. adımlar birleştirilerek verimliliği optimize ediyor.
jojo

3
@flornquake İyi yakaladın! Korkarım bu eski güzel bir hata. Bu nedenle, çok fazla 1 çatışmaya sahip olabileceğinden bu yaklaşım optimal değildir.
Vincent van der Weele

1
@Heuster: tüm ışıklar yeşil! "0 hata".
georg

10

Önceki öğe olmayan kalan en yaygın öğeyi alma algoritması zaten doğru. En yaygın olanı izlemek için en uygun şekilde bir yığın kullanan basit bir uygulama.

import collections, heapq
def nonadjacent(keys):
    heap = [(-count, key) for key, count in collections.Counter(a).items()]
    heapq.heapify(heap)
    count, key = 0, None
    while heap:
        count, key = heapq.heapreplace(heap, (count, key)) if count else heapq.heappop(heap)
        yield key
        count += 1
    for index in xrange(-count):
        yield key

>>> a = [1,2,3,3,2,2,1]
>>> list(nonadjacent(a))
[2, 1, 2, 3, 1, 2, 3]

Python'da algoritmaların nasıl yazılmayacağına dair güzel bir örnek. Basit ama sözdizimini sindirmek için 30 dakika gibi gerekli.
alex904

8

Bu üretebilir tüm yinelemeli backtracking algoritması kullanılarak (bitişik konumlarda herhangi iki eşit elemanları vardır) 'mükemmel sıralanmamış' permütasyon. Aslında, tüm permütasyonları oluşturmanın tek farkı, son sayıyı takip etmeniz ve bazı çözümleri buna göre hariç tutmanızdır:

def unsort(lst, last=None):
    if lst:
        for i, e in enumerate(lst):
            if e != last:
                for perm in unsort(lst[:i] + lst[i+1:], e):
                    yield [e] + perm
    else:
        yield []

Bu formda, çok sayıda alt liste oluşturduğundan işlevin çok verimli olmadığını unutmayın. Ayrıca, önce en kısıtlı sayılara (en yüksek sayıya sahip olanlar) bakarak bunu hızlandırabiliriz. İşte sadece countssayıların kullanıldığı çok daha verimli bir versiyon .

def unsort_generator(lst, sort=False):
    counts = collections.Counter(lst)
    def unsort_inner(remaining, last=None):
        if remaining > 0:
            # most-constrained first, or sorted for pretty-printing?
            items = sorted(counts.items()) if sort else counts.most_common()
            for n, c in items:
                if n != last and c > 0:
                    counts[n] -= 1   # update counts
                    for perm in unsort_inner(remaining - 1, n):
                        yield [n] + perm
                    counts[n] += 1   # revert counts
        else:
            yield []
    return unsort_inner(len(lst))

Bunu sadece nextmükemmel permütasyonu veya listhepsini tutmak için kullanabilirsiniz. Ama dikkat, varsa o hiçbir mükemmel sıralanmamış permütasyon, o zaman bu jeneratör dolayısıyla verecektir hiçbir sonuç.

>>> lst = [1,2,3,3,2,2,1]
>>> next(unsort_generator(lst))
[2, 1, 2, 3, 1, 2, 3]
>>> list(unsort_generator(lst, sort=True))
[[1, 2, 1, 2, 3, 2, 3], 
 ... 36 more ...
 [3, 2, 3, 2, 1, 2, 1]]
>>> next(unsort_generator([1,1,1]))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Bu sorunu aşmak için, bunu diğer yanıtlarda önerilen algoritmalardan biriyle birlikte geri dönüş olarak kullanabilirsiniz. Bu, eğer varsa, mükemmel bir şekilde sıralanmamış permütasyon veya aksi takdirde iyi bir yaklaşım döndürmeyi garanti edecektir.

def unsort_safe(lst):
    try:
        return next(unsort_generator(lst))
    except StopIteration:
        return unsort_fallback(lst)

Bu O (N ^ 2) hafızasını kullanır ... permütasyondaki her eleman için özyinelemeli çağrı için listenin bir kopyasını yapıyorsunuz. Ayrıca özyinelemeli olduğundan "küçük" uzunluklarda başarısız olur.
Bakuriu

@Bakuriu Kabul edildi, "verimlilik için optimize edilmemiş" derken kastettiğim buydu ... O (n ^ 2) alanı dışında bırakmadığımı itiraf etmeliyim, ama haklısın ... onu geliştirmeye çalışacağım .
tobias_k

O (N ^ 2) gibi bir özgeçmişiniz olduğunda her zaman arkada kalır T(n+1) = something + T(n).
Bakuriu

@tobias_k: test için sadece bir perma için bir fonksiyon gönderebilir misiniz?
georg

@georg Elbette: next(unsort2(collections.Counter(a)));-) Ama bu algo tüm olasılıkları oluşturduğuna göre, neden hepsini kontrol etmiyorsun? 7 elementli test listesi için sadece 38 tanesi.
tobias_k

5

Python'da aşağıdakileri yapabilirsiniz.

Sıralı bir listeniz olduğunu düşünün l, şunları yapabilirsiniz:

length = len(l)
odd_ind = length%2
odd_half = (length - odd_ind)/2
for i in range(odd_half)[::2]:
    my_list[i], my_list[odd_half+odd_ind+i] = my_list[odd_half+odd_ind+i], my_list[i]

Bunlar sadece yerinde işlemlerdir ve bu nedenle oldukça hızlı olmalıdır ( O(N)). Eğer gelen geçeceğini Not l[i] == l[i+1]için l[i] == l[i+2]ne kadar son sipariş böylece şey ama rastgele, ama ben soruyu anlamadım nasıl dan o aradığınız randomness değildir.

Buradaki fikir, sıralanmış listeyi ortada bölmek ve ardından iki bölümdeki diğer tüm öğeleri değiştirmektir.

İçin l= [1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5]bu potansiyell = [3, 1, 4, 2, 5, 1, 3, 1, 4, 2, 5]

Yöntem l[i] == l[i + 1], bir elementin bolluğu listenin uzunluğunun yarısına eşit veya daha büyük olduğunda hepsinden kurtulmayı başaramaz .

Yukarıdakiler, en sık kullanılan öğenin bolluğu listenin yarısından daha küçük olduğu sürece iyi çalışsa da, aşağıdaki işlev aynı zamanda sınır durumlarını da (ünlü tek tek sorun) ele alır. birincisi en bol olanı olmalıdır:

def no_adjacent(my_list):
    my_list.sort()
    length = len(my_list)
    odd_ind = length%2
    odd_half = (length - odd_ind)/2
    for i in range(odd_half)[::2]:
        my_list[i], my_list[odd_half+odd_ind+i] = my_list[odd_half+odd_ind+i], my_list[i]

    #this is just for the limit case where the abundance of the most frequent is half of the list length
    if max([my_list.count(val) for val in set(my_list)]) + 1 - odd_ind > odd_half:
        max_val = my_list[0]
        max_count = my_list.count(max_val)
        for val in set(my_list):
            if my_list.count(val) > max_count:
               max_val = val
               max_count = my_list.count(max_val)
        while max_val in my_list:
            my_list.remove(max_val)
        out = [max_val]
        max_count -= 1
        for val in my_list:
            out.append(val)
            if max_count:
                out.append(max_val)
                max_count -= 1
        if max_count:
            print 'this is not working'
            return my_list
            #raise Exception('not possible')
        return out
    else:
        return my_list

Teşekkürler! Bu başarısız [3, 2, 1, 2, 1, 3, 2](iadeler [2, 1, 3, 1, 2, 2, 3], olmalı (3, 2, 1, 2, 1, 3, 2)) - özüne bakın
georg

@georg üzgünüm, hatamı unuttum a +1. Şimdi tekrar deneyin.
jojo

Hala sorunlar, [1, 3, 3, 3, 3, 1, 1]=>[3, 1, 3, 3, 1, 3, 1]
georg

@georg, belirttiğim gibi, en bol olan liste uzunluğunun yarısından daha az olduğu sürece işe yarıyor, bu örnekte durum böyle değil.
jojo

@georg Bu yüzden tek tek hatayı işleyen kısmı ekledim. Bu bölüm özellikle hızlı değildir (Thijser tarafından önerilen algoritma ile yaklaşık olarak aynı), ancak yalnızca nadir durumlarda çalıştırılacaktır.
jojo

5

İşte iyi bir algoritma:

  1. Her şeyden önce, tüm sayıları ne sıklıkla meydana geldiklerini sayın. Cevabı bir haritaya yerleştirin.

  2. Bu haritayı, en sık meydana gelen sayıların ilk sırada gelmesi için sıralayın.

  3. Cevabınızın ilk rakamı sıralanan haritadaki ilk rakamdır.

  4. Birincisi şimdi daha küçük olacak şekilde haritaya bakın.

Verimliliği artırmak istiyorsanız, ayırma adımının verimliliğini artırmanın yollarını arayın.


Evet, @tobias_k bunu yaptı. İyi çalışıyor gibi görünüyor!
georg

@georg Biraz farklı ... Sayacı yalnızca alan karmaşıklığını azaltmak için kullanıyorum, ancak sayıları belirli bir sıraya göre test etmiyorum (bunun başka bir hızlandırma olabileceğini düşündüm). Farklı olan benim çözümü her zaman, hepsi 'mükemmel' permütasyon verim olmasıdır eğer varsa bu en iyi (?) Solüsyonu (mükemmel veya değil) verim gerekirken,.
tobias_k

3
Bu sözde kod tam olarak doğru değil; madde sayıları 5 x, 2 y, 2 z ise gereksiz yere x'leri bir araya getirecektir. Bkz Cevabımı bir düzeltme için.
David Eisenstat

1
Kabul. Örneğin [1,1,1,2,3] için bu, [1,2,1,3,1] yerine örneğin [1,1,2,1,3] üretecektir.
tobias_k

3. Adım aslında verimsizdir. Bir sayı yaygınsa (bir sonraki en sık sayıdan en az iki giriş daha varsa), 3. adım bu sayıyı herhangi bir gerekçe olmaksızın arka arkaya iki kez kullanacaktır.
MSalters

5

Bonus sorusuna cevap olarak: Bu, bitişik elemanların aynı olamayacağı bir kümenin tüm permütasyonlarını bulan bir algoritmadır. Bunun kavramsal olarak en verimli algoritma olduğuna inanıyorum (ancak diğerleri daha basit koda çevrildikleri için pratikte daha hızlı olabilir). Kaba kuvvet kullanmaz, yalnızca benzersiz permütasyonlar oluşturur ve çözümlere götürmeyen yollar en erken noktada kesilir.

Diğer tüm elemanların birleşiminden daha sık meydana gelen bir kümedeki bir eleman için "bol eleman" terimini ve bol element sayısı eksi diğer elementlerin sayısı için "bolluk" terimini kullanacağım.
örneğin seti abachiçbir bol elemanı, setleri vardır abacave aabcaavar abol element olarak ve bolluğu 1 ve 2 sırasıyla.

  1. Aşağıdaki gibi bir setle başlayın:

aaabbcd

  1. İlk oluşumları tekrarlardan ayırın:

ilkler: abcd
tekrarlar: aab

  1. Varsa tekrarlardaki bol elementi bulun ve bolluğu hesaplayın:

bol element:
bolluk: 1

  1. Bol elemandan sonraki eleman sayısının bolluktan az olmadığı ilklerin tüm permütasyonlarını oluşturun: (yani örnekte "a" en son olamaz)

abcd, abdc, acbd, acdb, adbc, adcb, bacd, badc, bcad , bcda, bdac ,
bdca, cabd, cadb, cbad , cbda , cdab, cdba, dabc, dacb , abac, dbca , dcab, dcba

  1. Her permütasyon için, aşağıdaki kuralları izleyerek, tekrarlanan karakter kümesini tek tek ekleyin:

5.1. Kümenin bolluğu, şimdiye kadar permütasyondaki bol elementin son oluşumundan sonra elementlerin sayısından büyükse, bir sonraki permütasyona geçin.
Örneğin, permütasyon şimdiye kadar olduğu zaman abc, bol elemanlı bir küme ayalnızca bolluk 2 veya daha az ise eklenebilir, bu yüzden aaaabcsorun yok, aaaaabcdeğil.

5.2. Permütasyondaki son oluşumu önce gelen kümeden öğeyi seçin.
Örneğin, permütasyon şimdiye kadar olduğu abcbave ayarlandığı zaman ab, seçinb

5.3. Seçilen öğeyi permütasyondaki son oluşumunun sağına en az 2 konum ekleyin.
örneğin bpermütasyona eklerken babca, sonuçlar babcbavebabcab

5.4. Her sonuçta elde edilen permütasyon ve setin geri kalanıyla 5. adımı tekrarlayın.

EXAMPLE:
set = abcaba
firsts = abc
repeats = aab

perm3  set    select perm4  set    select perm5  set    select perm6

abc    aab    a      abac   ab     b      ababc  a      a      ababac  
                                                               ababca  
                                          abacb  a      a      abacab  
                                                               abacba  
                     abca   ab     b      abcba  a      -
                                          abcab  a      a      abcaba  
acb    aab    a      acab   ab     a      acaba  b      b      acabab  
                     acba   ab     b      acbab  a      a      acbaba  
bac    aab    b      babc   aa     a      babac  a      a      babaca  
                                          babca  a      -
                     bacb   aa     a      bacab  a      a      bacaba  
                                          bacba  a      -  
bca    aab    -
cab    aab    a      caba   ab     b      cabab  a      a      cababa  
cba    aab    -

Bu algoritma, benzersiz permütasyonlar üretir. Toplam permütasyon sayısını bilmek istiyorsanız (burada abaa'ları değiştirebileceğiniz için iki kez sayılır), benzersiz permütasyonların sayısını bir faktörle çarpın:

F = N 1 ! * N 2 ! * ... * N n !

burada N, kümedeki her bir öğenin oluşum sayısıdır. Bir set için abcdabcababu 4 olur! * 3! * 2! * 1! veya 288, bu da bir algoritmanın sadece benzersiz olanlar yerine tüm permütasyonları üreten bir algoritmanın ne kadar verimsiz olduğunu gösterir. Bu durumda tüm permütasyonları listelemek için, benzersiz permütasyonları 288 kez listeleyin :-)

Aşağıda Javascript'te (oldukça beceriksiz) bir uygulama bulunmaktadır; Python gibi bir dilin bu tür şeyler için daha uygun olabileceğinden şüpheleniyorum. "Abracadabra" nın ayrılmış permütasyonlarını hesaplamak için kod pasajını çalıştırın.

// FIND ALL PERMUTATONS OF A SET WHERE NO ADJACENT ELEMENTS ARE IDENTICAL
function seperatedPermutations(set) {
    var unique = 0, factor = 1, firsts = [], repeats = [], abund;

    seperateRepeats(set);
    abund = abundance(repeats);
    permutateFirsts([], firsts);
    alert("Permutations of [" + set + "]\ntotal: " + (unique * factor) + ", unique: " + unique);

    // SEPERATE REPEATED CHARACTERS AND CALCULATE TOTAL/UNIQUE RATIO
    function seperateRepeats(set) {
        for (var i = 0; i < set.length; i++) {
            var first, elem = set[i];
            if (firsts.indexOf(elem) == -1) firsts.push(elem)
            else if ((first = repeats.indexOf(elem)) == -1) {
                repeats.push(elem);
                factor *= 2;
            } else {
                repeats.splice(first, 0, elem);
                factor *= repeats.lastIndexOf(elem) - first + 2;
            }
        }
    }

    // FIND ALL PERMUTATIONS OF THE FIRSTS USING RECURSION
    function permutateFirsts(perm, set) {
        if (set.length > 0) {
            for (var i = 0; i < set.length; i++) {
                var s = set.slice();
                var e = s.splice(i, 1);
                if (e[0] == abund.elem && s.length < abund.num) continue;
                permutateFirsts(perm.concat(e), s, abund);
            }
        }
        else if (repeats.length > 0) {
            insertRepeats(perm, repeats);
        }
        else {
            document.write(perm + "<BR>");
            ++unique;
        }
    }

    // INSERT REPEATS INTO THE PERMUTATIONS USING RECURSION
    function insertRepeats(perm, set) {
        var abund = abundance(set);
        if (perm.length - perm.lastIndexOf(abund.elem) > abund.num) {
            var sel = selectElement(perm, set);
            var s = set.slice();
            var elem = s.splice(sel, 1)[0];
            for (var i = perm.lastIndexOf(elem) + 2; i <= perm.length; i++) {
                var p = perm.slice();
                p.splice(i, 0, elem);
                if (set.length == 1) {
                    document.write(p + "<BR>");
                    ++unique;
                } else {
                    insertRepeats(p, s);
                }
            }
        }
    }

    // SELECT THE ELEMENT FROM THE SET WHOSE LAST OCCURANCE IN THE PERMUTATION COMES FIRST
    function selectElement(perm, set) {
        var sel, pos, min = perm.length;
        for (var i = 0; i < set.length; i++) {
            pos = perm.lastIndexOf(set[i]);
            if (pos < min) {
                min = pos;
                sel = i;
            }
        }
        return(sel);
    }

    // FIND ABUNDANT ELEMENT AND ABUNDANCE NUMBER
    function abundance(set) {
        if (set.length == 0) return ({elem: null, num: 0});
        var elem = set[0], max = 1, num = 1;
        for (var i = 1; i < set.length; i++) {
            if (set[i] != set[i - 1]) num = 1
            else if (++num > max) {
                max = num;
                elem = set[i];
            }
        }
        return ({elem: elem, num: 2 * max - set.length});
    }
}

seperatedPermutations(["a","b","r","a","c","a","d","a","b","r","a"]);


1
Bunun için teşekkürler! bunun javascript'te biraz kısaltılıp kısaltılamayacağını göreceğiz.
stt106

4

Buradaki fikir, öğeleri en yaygın olandan en az yaygın olana doğru sıralamak, en yaygın olanı almak, sayısını azaltmak ve azalan sırayı koruyarak listeye geri koymaktır (ancak mümkün olduğunda tekrarları önlemek için son kullanılan öğeyi ilk sıraya koymaktan kaçınmak) .

Bu, Counterve kullanılarak uygulanabilir bisect:

from collections import Counter
from bisect import bisect

def unsorted(lst):
    # use elements (-count, item) so bisect will put biggest counts first
    items = [(-count, item) for item, count in Counter(lst).most_common()]
    result = []

    while items:
        count, item = items.pop(0)
        result.append(item)
        if count != -1:
            element = (count + 1, item)
            index = bisect(items, element)
            # prevent insertion in position 0 if there are other items
            items.insert(index or (1 if items else 0), element)

    return result

Misal

>>> print unsorted([1, 1, 1, 2, 3, 3, 2, 2, 1])
[1, 2, 1, 2, 1, 3, 1, 2, 3]

>>> print unsorted([1, 2, 3, 2, 3, 2, 2])
[2, 3, 2, 1, 2, 3, 2]

Bu, örneğin: [1, 1, 2, 3]gibi çözümlerin olduğu yerde başarısız olur [1, 2, 1, 3].
Bakuriu

Evet, özür dilerim
enrico.bacis

Teşekkürler! Bu her zaman en iyi sonucu vermez, örneğin [1, 2, 3, 2, 3, 2, 2]döndüğü için [2, 3, 1, 2, 3, 2, 2](1 hata), ideal olan (2, 1, 2, 3, 2, 3, 2)) - özüne bakın.
georg

@georg Doğru, güzel yakalama, kullandığı basit prensibi koruyarak güncelledim.
enrico.bacis

@ enrico.bacis: teşekkürler! Yeni sürüm kusursuz çalışıyor. Özü güncelledim. Ne yazık ki artık size oy veremem.
georg

2
  1. Listeyi sıralayın.
  2. Bu algoritmayı kullanarak listenin "en iyi karıştırmasını" oluşturun

Listedeki minimum öğeleri orijinal yerlerinde (öğe değerine göre) verecektir, böylece sizin örneğiniz için 1'leri, 2'leri ve 3'leri sıralanmış konumlarından uzağa koymaya çalışacaktır.


Denedim best_shuffleve üretti [1,1,1,2,3] -> [3, 1, 2, 1, 1]- ideal değil!
georg

2

N sıralı uzunluk listesiyle başlayın. M = n / 2 olsun. 0, sonra m, sonra 1, sonra m + 1, sonra 2, sonra m + 2'deki değerleri alın. Sayıların yarısından fazlası aynı olmadıkça, asla ardışık sırayla eşdeğer değerler elde edemezsiniz.


Fikir için teşekkürler. Sanırım @Heuster'ın uyguladığı buydu.
georg

2

Lütfen "ben de" tarzı cevabımı affedin, ancak Coady'nin cevabı buna basitleştirilemez mi?

from collections import Counter
from heapq import heapify, heappop, heapreplace
from itertools import repeat

def srgerg(data):
    heap = [(-freq+1, value) for value, freq in Counter(data).items()]
    heapify(heap)

    freq = 0
    while heap:
        freq, val = heapreplace(heap, (freq+1, val)) if freq else heappop(heap)
        yield val
    yield from repeat(val, -freq)

Düzenleme: İşte bir liste döndüren bir python 2 sürümü:

def srgergpy2(data):
    heap = [(-freq+1, value) for value, freq in Counter(data).items()]
    heapify(heap)

    freq = 0
    result = list()
    while heap:
        freq, val = heapreplace(heap, (freq+1, val)) if freq else heappop(heap)
        result.append(val)
    result.extend(repeat(val, -freq))
    return result

Evet, bu iyi çalışıyor gibi görünüyor (py2'de olmam ve işlevin bir liste döndürmesi dışında).
georg

@georg Tamam, bir liste döndüren bir python 2 sürümü ekledim.
srgerg

2
  1. Her bir değerin kaç kez göründüğünü sayın
  2. En sıktan en az sıklığa doğru sırayla değerleri seçin
  3. Her seferinde dizini 2 artırarak seçilen değeri son çıktıya ekleyin
  4. Dizin sınır dışıysa dizini 1'e sıfırla
from heapq import heapify, heappop
def distribute(values):
    counts = defaultdict(int)
    for value in values:
        counts[value] += 1
    counts = [(-count, key) for key, count in counts.iteritems()]
    heapify(counts)
    index = 0
    length = len(values)
    distributed = [None] * length
    while counts:
        count, value = heappop(counts)
        for _ in xrange(-count):
            distributed[index] = value
            index = index + 2 if index + 2 < length else 1
    return distributed
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.