K öğelerinin tüm kombinasyonlarını n'den döndüren algoritma


571

Bağımsız değişken olarak bir dizi harf ve seçmek için bu harflerin sayısını alan bir işlev yazmak istiyorum.

8 harflik bir dizi sağladığınızı ve bundan 3 harf seçmek istediğinizi varsayalım. O zaman şunları elde etmelisiniz:

8! / ((8 - 3)! * 3!) = 56

Her biri 3 harften oluşan diziler (veya kelimeler).


2
Programlama dili tercihiniz var mı?
Jonathan Tran

7
Yinelenen harflerle nasıl başa çıkmak istiyorsunuz?
wcm

Dil tercihi yok, onu yakut olarak kodlayacağım, ancak hangi algoritmaların kullanılacağına dair genel bir fikir iyi olurdu. Aynı değere sahip iki harf olabilir, ancak aynı harf iki kez olamaz.
Fredrik


Php, aşağıdaki hile yapmalıdır: stackoverflow.com/questions/4279722/…
Kemal Dağ

Yanıtlar:


413

Bilgisayar Programlama Sanatı Cilt 4: Fascicle 3'te , kendi durumunuza anlattığımdan daha iyi uyan bir ton var.

Gri Kodlar

Karşılaşacağınız bir konu elbette bellek ve oldukça hızlı bir şekilde, setinizde 20 elementle ilgili sorunlarınız olacak - 20 C 3 = 1140. Ve seti tekrarlamak istiyorsanız, modifiye edilmiş bir gri kullanmak en iyisidir kod algoritması sayesinde hepsini bellekte tutmuyorsunuz. Bunlar bir öncekinden bir sonraki kombinasyonu oluşturur ve tekrarlardan kaçınır. Farklı kullanımlar için bunların birçoğu vardır. Ardışık kombinasyonlar arasındaki farkları en üst düzeye çıkarmak istiyor muyuz? küçültmek? ve benzeri.

Gri kodları açıklayan bazı orijinal makaleler:

  1. Bazı Hamilton Yolları ve Minimal Değişim Algoritması
  2. Bitişik Kavşak Kombinasyonu Üretimi Algoritması

İşte konuyu kapsayan diğer bazı makaleler:

  1. Eades'in Etkili Bir Uygulaması, Hickey, Komşu Değişen Kombinasyon Üretimi Algoritmasını Oku (PDF, Pascal'da kod ile)
  2. Kombinasyon Jeneratörleri
  3. Kombinatoryal Gri Kodların Araştırılması (PostScript)
  4. Gri Kodlar için Bir Algoritma

Chase's Twiddle (algoritma)

Phillip J Chase, `` Algoritma 382: N Nesnelerinden M'nin Kombinasyonları '' (1970)

C'deki algoritma ...

Sözlükbilimsel Sıradaki Kombinasyonlar Dizini (Buckles Algorithm 515)

Ayrıca bir kombinasyona dizinine göre (sözlük sırasına göre) başvurabilirsiniz. Endekse dayalı olarak endeksin sağdan sola bir miktar değişiklik olması gerektiğini fark ederek, bir kombinasyonu kurtarması gereken bir şey oluşturabiliriz.

Yani, bir küme {1,2,3,4,5,6} ... var ve üç element istiyoruz. Diyelim ki {1,2,3} elementler arasındaki farkın bir, düzenli ve minimum olduğunu söyleyebiliriz. {1,2,4} bir değişikliğe sahiptir ve sözlükbilimsel olarak 2 numaradır. Dolayısıyla, son yerdeki 'değişikliklerin' sayısı sözlükbilimsel düzende bir değişiklikten sorumludur. {1.3,4} değerindeki bir değişikliğe sahip ikinci sırada bir değişiklik olur, ancak ikinci sırada olduğundan daha fazla değişiklik olur (orijinal kümedeki öğe sayısıyla orantılı).

Anlattığım yöntem, göründüğü gibi, setten indekse, tersine yapmamız gerekiyor - ki bu çok daha karmaşık. Bu nasıl Toka sorunu çözer. Onları hesaplamak için küçük değişiklikler yaparak biraz C yazdım - seti temsil etmek için bir sayı aralığı yerine kümelerin dizinini kullandım, bu yüzden her zaman 0 ... n'den çalışıyoruz. Not:

  1. Kombinasyonlar sırasız olduğundan, {1,3,2} = {1,2,3} - sözlükbilimsel olmalarını isteriz.
  2. Bu yöntem, ilk farkın kümesini başlatmak için örtük 0 değerine sahiptir.

Sözlükbilimsel Sıradaki Kombinasyonlar Dizini (McCaffrey)

Orada başka bir yolu da kavram kavramak ve programa daha kolaydır, ancak Tokalar optimizasyonlar olmadan var. Neyse ki, aynı zamanda yinelenen kombinasyonlar üretmez:

N'de x_k ... x_1Maksimize eden set i = C (x_1, k) + C (x_2, k-1) + ... + C (x_k, 1), neredeC (n, r) = {n r'yi seçin} .

Örnek olarak: 27 = C(6,4) + C(5,3) + C(2,2) + C(1,1). Yani, dört şeyin 27. sözlükbilimsel kombinasyonu şöyledir: {1,2,5,6}, bakmak istediğiniz setin dizinleri. Aşağıdaki örnek (OCaml), chooseokuyucuya bırakılan işlev gerektirir :

(* this will find the [x] combination of a [set] list when taking [k] elements *)
let combination_maccaffery set k x =
    (* maximize function -- maximize a that is aCb              *)
    (* return largest c where c < i and choose(c,i) <= z        *)
    let rec maximize a b x =
        if (choose a b ) <= x then a else maximize (a-1) b x
    in
    let rec iterate n x i = match i with
        | 0 -> []
        | i ->
            let max = maximize n i x in
            max :: iterate n (x - (choose max i)) (i-1)
    in
    if x < 0 then failwith "errors" else
    let idxs =  iterate (List.length set) x k in
    List.map (List.nth set) (List.sort (-) idxs)

Küçük ve basit kombinasyonlar yineleyicisi

Didaktik amaçlar için aşağıdaki iki algoritma sağlanmıştır. Bir yineleyici ve (daha genel) klasör genel kombinasyonları uygularlar. O ( n C k ) karmaşıklığına sahip olabildiğince hızlıdırlar . Bellek tüketimi buna bağlıdır k.

Her kombinasyon için kullanıcı tarafından sağlanan bir işlevi çağıran yineleyici ile başlayacağız

let iter_combs n k f =
  let rec iter v s j =
    if j = k then f v
    else for i = s to n - 1 do iter (i::v) (i+1) (j+1) done in
  iter [] 0 0

Daha genel bir sürüm, başlangıç ​​durumundan başlayarak durum değişkeni ile birlikte kullanıcı tarafından sağlanan işlevi çağıracaktır. Durumu farklı durumlar arasında geçirmemiz gerektiğinden, for-loop'u kullanmıyoruz, bunun yerine özyineleme kullanıyoruz,

let fold_combs n k f x =
  let rec loop i s c x =
    if i < n then
      loop (i+1) s c @@
      let c = i::c and s = s + 1 and i = i + 1 in
      if s < k then loop i s c x else f c x
    else x in
  loop 0 0 [] x

1
Bu, setin eşit elemanlar içermesi durumunda yinelenen kombinasyonlar üretecek mi?
Thomas Ahle

2
Evet, Thomas olacak. Dizideki verilere karşı agnostiktir. İstediğiniz efektse veya başka bir algoritma seçerek önce her zaman kopyaları filtreleyebilirsiniz.
nlucaroni

19
Müthiş cevap. Lütfen algoritmaların her biri için çalışma süresi ve bellek analizi özetini verebilir misiniz?
uncaught_exceptions

2
Oldukça iyi bir cevap. 20C3 1140'tır, ünlem işareti faktöriyel gibi göründüğü için kafa karıştırıcıdır ve faktöriyeller kombinasyonları bulmak için formüle girerler. Bu nedenle ünlem işaretini düzenleyeceğim.
CashCow

3
Alıntıların çoğunun bir ödeme duvarının arkasında olması berbat. Ödeme duvarı dışındaki bağlantıları ekleme veya kaynaklardan alıntı yapılabilir snippet'leri ekleme olasılığı var mı?
Terrance

195

C # dilinde:

public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int k)
{
  return k == 0 ? new[] { new T[0] } :
    elements.SelectMany((e, i) =>
      elements.Skip(i + 1).Combinations(k - 1).Select(c => (new[] {e}).Concat(c)));
}

Kullanımı:

var result = Combinations(new[] { 1, 2, 3, 4, 5 }, 3);

Sonuç:

123
124
125
134
135
145
234
235
245
345

2
Bu çözüm "küçük" setler için iyi çalışır ancak daha büyük setler için biraz bellek kullanır.
Artur Carvalho

1
doğrudan ilgili değil ama, kod çok ilginç / okunabilir ve hangi yapıları / yöntemleri c # sürümü merak ediyorum? (Ben sadece c # v1.0 kullandım ve o kadar değil).
LBarret

Kesinlikle zarif, ama IEnumerable birçok kez numaralandırılacak . Bu önemli bir operasyonla destekleniyorsa ...
Drew Noakes

2
çünkü bu, kullanım hattınızın okuyabileceği bir uzatma yöntemidir:var result = new[] { 1, 2, 3, 4, 5 }.Combinations(3);
Dave Cousineau


81

Kısa Java çözümü:

import java.util.Arrays;

public class Combination {
    public static void main(String[] args){
        String[] arr = {"A","B","C","D","E","F"};
        combinations2(arr, 3, 0, new String[3]);
    }

    static void combinations2(String[] arr, int len, int startPosition, String[] result){
        if (len == 0){
            System.out.println(Arrays.toString(result));
            return;
        }       
        for (int i = startPosition; i <= arr.length-len; i++){
            result[result.length - len] = arr[i];
            combinations2(arr, len-1, i+1, result);
        }
    }       
}

Sonuç olacak

[A, B, C]
[A, B, D]
[A, B, E]
[A, B, F]
[A, C, D]
[A, C, E]
[A, C, F]
[A, D, E]
[A, D, F]
[A, E, F]
[B, C, D]
[B, C, E]
[B, C, F]
[B, D, E]
[B, D, F]
[B, E, F]
[C, D, E]
[C, D, F]
[C, E, F]
[D, E, F]

bu O (n ^ 3) gibi görünüyor değil mi? Bunu yapmak için daha hızlı bir algoritma olduğunu merak ediyorum.
LZH

Ben 20 seçim 10 ile çalışıyorum ve bu benim için yeterince hızlı görünüyor (1 saniyeden az)
demongolem

4
@NanoHead yanılıyorsunuz. Bu tekrar olmadan bir kombinasyon. Ve davanız tekrarlı.
Jack The Ripper

Bu kod parçasını web'de bulmak daha kolay olmalı ... tam da aradığım şey bu!
Manuel S.

Sadece bunu ve diğer 7 java uygulamasını test ettim - bu çok hızlıydı. En hızlı ikinci, daha yavaş bir büyüklük sırasının üzerindeydi.
stuart

77

Özyinelemeli Python çözümümü bu soruna sunabilir miyim?

def choose_iter(elements, length):
    for i in xrange(len(elements)):
        if length == 1:
            yield (elements[i],)
        else:
            for next in choose_iter(elements[i+1:len(elements)], length-1):
                yield (elements[i],) + next
def choose(l, k):
    return list(choose_iter(l, k))

Örnek kullanım:

>>> len(list(choose_iter("abcdefgh",3)))
56

Basitliği için beğendim.


16
len(tuple(itertools.combinations('abcdefgh',3)))Python'da aynı şeyi daha az kodla elde edecek.
hgus1294

59
@ hgus1294 Doğru, ama bu hile olur. Op belirli bir programlama diline bağlı bir "sihirli" yöntem değil, bir algoritma istedi.
MestreLion

1
İlk döngü aralığı kesinlikle konuşmamalı for i in xrange(len(elements) - length + 1):mı? Dilim dizininden çıkmak incelikle işlendiği için python önemli değil ama doğru algoritma.
Stephan Dollberg

62

Diyelim ki harf diziniz şöyle görünüyor: "ABCDEFGH". Geçerli sözcük için hangi harfleri kullanacağınızı gösteren üç dizininiz (i, j, k) var.

ABCDEFGH
^ ^ ^
ijk

İlk önce k'yi değiştirirsiniz, böylece bir sonraki adım şöyle görünür:

ABCDEFGH
^ ^ ^
ijk

Sonuna ulaşırsanız, devam edip j ve sonra k'yi tekrar değiştirirsiniz.

ABCDEFGH
^ ^ ^
ijk

ABCDEFGH
^ ^ ^
ijk

J, G'ye ulaştığınızda, i'yi de değiştirmeye başlarsınız.

ABCDEFGH
  ^ ^ ^
  ijk

ABCDEFGH
  ^ ^ ^
  ijk
...

Kodda yazılı bu böyle bir şey

void print_combinations(const char *string)
{
    int i, j, k;
    int len = strlen(string);

    for (i = 0; i < len - 2; i++)
    {
        for (j = i + 1; j < len - 1; j++)
        {
            for (k = j + 1; k < len; k++)
                printf("%c%c%c\n", string[i], string[j], string[k]);
        }
    }
}

115
Bu yaklaşımdaki sorun, parametre 3'ü koda sıkıca bağlamasıdır. (Ya 4 karakter istenirse?) Soruyu anladığım gibi, hem karakter dizisi hem de seçilecek karakter sayısı sağlanacaktır. Tabii ki, bu sorunun bir yolu, açıkça yuvalanmış döngüler özyineleme ile değiştirmektir.
joel.neely

10
@ Dr.PersonPersonII Ve neden OP ile ilgili üçgenler var?
MestreLion

7
Bu çözümü her zaman rasgele parametre ile özyinelemeli haline dönüştürebilirsiniz.
Rok Kralj

5
@RokKralj, "bu çözümü rasgele parametre ile özyinelemeli hale nasıl dönüştürürüz"? Benim için imkansız görünüyor.
Aaron McDaid

3
Nasıl yapılacağını sezgisel güzel bir açıklama
Yonatan Simson

53

Aşağıdaki özyinelemeli algoritma, sıralı bir kümedeki tüm k-elemanı kombinasyonlarını alır:

  • ikombinasyonunuzun ilk öğesini seçin
  • ' den daha büyük elemanlar kümesinden özyineli olarak seçilen eleman ikombinasyonlarının her biri ile birleştirin .k-1i

Setteki her biri için yukarıdakileri yineleyin i.

iTekrarlamayı önlemek için öğelerin geri kalanını daha büyük seçmeniz önemlidir . Bu şekilde [3,5] iki kez yerine [3] [5] ile birleştirildiği için yalnızca bir kez seçilecektir (durum [5] + [3] 'yi ortadan kaldırmaktadır). Bu koşul olmadan kombinasyonlar yerine varyasyonlar elde edersiniz.


12
Birçok cevap tarafından kullanılan algoritmanın İngilizce olarak çok iyi bir açıklaması
MestreLion

ikincisi yukarıdaki; özellikle de bu, user935714'ün getirdiği çözümü anlamama yardımcı oldu. her ikisi de mükemmel.
jacoblambert

25

C ++ 'da aşağıdaki rutin [first, last) aralığı arasındaki tüm uzunluk mesafesi kombinasyonlarını (first, k) üretir:

#include <algorithm>

template <typename Iterator>
bool next_combination(const Iterator first, Iterator k, const Iterator last)
{
   /* Credits: Mark Nelson http://marknelson.us */
   if ((first == last) || (first == k) || (last == k))
      return false;
   Iterator i1 = first;
   Iterator i2 = last;
   ++i1;
   if (last == i1)
      return false;
   i1 = last;
   --i1;
   i1 = k;
   --i2;
   while (first != i1)
   {
      if (*--i1 < *i2)
      {
         Iterator j = k;
         while (!(*i1 < *j)) ++j;
         std::iter_swap(i1,j);
         ++i1;
         ++j;
         i2 = k;
         std::rotate(i1,j,last);
         while (last != j)
         {
            ++j;
            ++i2;
         }
         std::rotate(k,i2,last);
         return true;
      }
   }
   std::rotate(first,k,last);
   return false;
}

Bu şekilde kullanılabilir:

#include <string>
#include <iostream>

int main()
{
    std::string s = "12345";
    std::size_t comb_size = 3;
    do
    {
        std::cout << std::string(s.begin(), s.begin() + comb_size) << std::endl;
    } while (next_combination(s.begin(), s.begin() + comb_size, s.end()));

    return 0;
}

Bu aşağıdakileri basacaktır:

123
124
125
134
135
145
234
235
245
345

1
Başlangıç ​​nedir, bu durumda son nedir? Bu işleve iletilen tüm değişkenler değere göre iletilirse gerçekte bir şey nasıl döndürülebilir?
Sergej Andrejev

6
@Sergej Andrejev: yerine beingve beginbirlikte s.begin()ve endbirlikte s.end(). Kod next_permutation, burada daha ayrıntılı olarak açıklanan STL algoritmasını yakından takip eder .
Anthony Labarre

5
ne oluyor? i1 = son; --i1; i1 = k;
Manoj R

24

Bu konuyu yararlı buldum ve Firebug içine pop olabilir bir Javascript çözümü eklemek düşündüm. JS motorunuza bağlı olarak, başlangıç ​​dizgisinin büyük olması biraz zaman alabilir.

function string_recurse(active, rest) {
    if (rest.length == 0) {
        console.log(active);
    } else {
        string_recurse(active + rest.charAt(0), rest.substring(1, rest.length));
        string_recurse(active, rest.substring(1, rest.length));
    }
}
string_recurse("", "abc");

Çıktı aşağıdaki gibi olmalıdır:

abc
ab
ac
a
bc
b
c

4
@NanoHead Bu yanlış değil . Çıktıda zaten "ac" - ve "ca", "ac" ile aynı birleşim gösteriyor . "Ac" ın "ca" ile aynı olmayacağı permütasyonlardan (matematikte) bahsediyorsunuz .
Jakob Jenkov

1
Bu n değil k seçin.
shinzou

20
static IEnumerable<string> Combinations(List<string> characters, int length)
{
    for (int i = 0; i < characters.Count; i++)
    {
        // only want 1 character, just return this one
        if (length == 1)
            yield return characters[i];

        // want more than one character, return this one plus all combinations one shorter
        // only use characters after the current one for the rest of the combinations
        else
            foreach (string next in Combinations(characters.GetRange(i + 1, characters.Count - (i + 1)), length - 1))
                yield return characters[i] + next;
    }
}

Güzel çözüm. Bu son soruyu cevaplarken referans verdim: stackoverflow.com/questions/4472036/…
wageoghe

Bu işlevle ilgili tek sorun özyinelemedir. PC'de çalışan yazılımlar için genellikle iyi olsa da, daha fazla kaynak kısıtlı bir platformla (örneğin gömülü) çalışıyorsanız, şansınız
kalmaz

Ayrıca çok sayıda Liste ayıracak ve dizinin öğelerini her bir yenisine kopyalamak için çok fazla iş yapacaktır. Bana öyle geliyor ki bu listeler tüm numaralandırma tamamlanana kadar tahsil edilemez.
Niall Connaughton

Bu kaygan. Bir algoritma buldunuz mu yoksa sıfırdan mı?
paparazzo

20

Python'daki kısa örnek:

def comb(sofar, rest, n):
    if n == 0:
        print sofar
    else:
        for i in range(len(rest)):
            comb(sofar + rest[i], rest[i+1:], n-1)

>>> comb("", "abcde", 3)
abc
abd
abe
acd
ace
ade
bcd
bce
bde
cde

Açıklama için, özyinelemeli yöntem aşağıdaki örnekle açıklanmıştır:

Örnek: ABCDE
3'ün tüm kombinasyonları şöyle olacaktır:

  • A, diğerlerinden 2'nin tüm kombinasyonlarıyla (BCDE)
  • B, kalan 2'nin tüm kombinasyonlarıyla (CDE)
  • C, diğerlerinden 2'nin tüm kombinasyonlarıyla (DE)

17

Haskell'de basit özyinelemeli algoritma

import Data.List

combinations 0 lst = [[]]
combinations n lst = do
    (x:xs) <- tails lst
    rest   <- combinations (n-1) xs
    return $ x : rest

İlk olarak özel durumu tanımlarız, yani sıfır eleman seçmek. Boş bir liste (yani boş bir liste içeren bir liste) olan tek bir sonuç üretir.

N> 0 xiçin listenin her elemanından geçer ve xsher elemanx .

restalır n - 1öğeleri xsiçin yinelemeli çağrı kullanarak combinations. Fonksiyonunun son sonuç her bir elemanı olan bir listesi olan x : restsahiptir (yani, bir liste xkafası olarak ve resther farklı değeri için kuyruk gibi) xve rest.

> combinations 3 "abcde"
["abc","abd","abe","acd","ace","ade","bcd","bce","bde","cde"]

Ve elbette, Haskell tembel olduğu için, liste gerektiği gibi kademeli olarak üretilir, böylece üstel olarak büyük kombinasyonları kısmen değerlendirebilirsiniz.

> let c = combinations 8 "abcdefghijklmnopqrstuvwxyz"
> take 10 c
["abcdefgh","abcdefgi","abcdefgj","abcdefgk","abcdefgl","abcdefgm","abcdefgn",
 "abcdefgo","abcdefgp","abcdefgq"]

13

Ve işte büyükbabam COBOL, en kötü huylu dil.

Her biri 8 baytlık 34 öğeden oluşan bir dizi kabul edelim (tamamen keyfi seçim.) Fikir, olası tüm 4 elemanlı kombinasyonları numaralandırmak ve bunları bir diziye yüklemek.

4 grubundaki her pozisyon için birer tane olmak üzere 4 endeks kullanıyoruz

Dizi şu şekilde işlenir:

    idx1 = 1
    idx2 = 2
    idx3 = 3
    idx4 = 4

İdx4 değerini 4'ten sonuna kadar değiştiriyoruz. Her idx4 için dört kişilik benzersiz bir kombinasyon elde ederiz. İdx4 dizinin sonuna geldiğinde idx3'ü 1 arttırır ve idx4 değerini idx3 + 1 olarak ayarlarız. Sonra idx4'ü tekrar sonuna kadar çalıştırıyoruz. Bu şekilde, idx1 konumu dizinin sonundan 4'ten küçük olana kadar sırasıyla idx3, idx2 ve idx1 değerlerini artırıyoruz. Bu algoritmayı bitirir.

1          --- pos.1
2          --- pos 2
3          --- pos 3
4          --- pos 4
5
6
7
etc.

İlk iterasyonlar:

1234
1235
1236
1237
1245
1246
1247
1256
1257
1267
etc.

COBOL örneği:

01  DATA_ARAY.
    05  FILLER     PIC X(8)    VALUE  "VALUE_01".
    05  FILLER     PIC X(8)    VALUE  "VALUE_02".
  etc.
01  ARAY_DATA    OCCURS 34.
    05  ARAY_ITEM       PIC X(8).

01  OUTPUT_ARAY   OCCURS  50000   PIC X(32).

01   MAX_NUM   PIC 99 COMP VALUE 34.

01  INDEXXES  COMP.
    05  IDX1            PIC 99.
    05  IDX2            PIC 99.
    05  IDX3            PIC 99.
    05  IDX4            PIC 99.
    05  OUT_IDX   PIC 9(9).

01  WHERE_TO_STOP_SEARCH          PIC 99  COMP.

* Stop the search when IDX1 is on the third last array element:

COMPUTE WHERE_TO_STOP_SEARCH = MAX_VALUE - 3     

MOVE 1 TO IDX1

PERFORM UNTIL IDX1 > WHERE_TO_STOP_SEARCH
   COMPUTE IDX2 = IDX1 + 1
   PERFORM UNTIL IDX2 > MAX_NUM
      COMPUTE IDX3 = IDX2 + 1
      PERFORM UNTIL IDX3 > MAX_NUM
         COMPUTE IDX4 = IDX3 + 1
         PERFORM UNTIL IDX4 > MAX_NUM
            ADD 1 TO OUT_IDX
            STRING  ARAY_ITEM(IDX1)
                    ARAY_ITEM(IDX2)
                    ARAY_ITEM(IDX3)
                    ARAY_ITEM(IDX4)
                    INTO OUTPUT_ARAY(OUT_IDX)
            ADD 1 TO IDX4
         END-PERFORM
         ADD 1 TO IDX3
      END-PERFORM
      ADD 1 TO IDX2
   END_PERFORM
   ADD 1 TO IDX1
END-PERFORM.

ama neden {} {} {} {}
shinzou

9

İşte 99 Scala Problemi'nde açıklandığı gibi Scala'da zarif ve genel bir uygulama .

object P26 {
  def flatMapSublists[A,B](ls: List[A])(f: (List[A]) => List[B]): List[B] = 
    ls match {
      case Nil => Nil
      case sublist@(_ :: tail) => f(sublist) ::: flatMapSublists(tail)(f)
    }

  def combinations[A](n: Int, ls: List[A]): List[List[A]] =
    if (n == 0) List(Nil)
    else flatMapSublists(ls) { sl =>
      combinations(n - 1, sl.tail) map {sl.head :: _}
    }
}

9

SQL sözdizimini kullanabiliyorsanız, örneğin, bir yapının veya dizinin alanlarına erişmek için LINQ kullanıyorsanız veya "Alfabe" adlı bir veritabanına yalnızca bir karakter alanı "Harf" içeren bir veritabanına doğrudan erişiyorsanız, aşağıdakileri uyarlayabilirsiniz: kod:

SELECT A.Letter, B.Letter, C.Letter
FROM Alphabet AS A, Alphabet AS B, Alphabet AS C
WHERE A.Letter<>B.Letter AND A.Letter<>C.Letter AND B.Letter<>C.Letter
AND A.Letter<B.Letter AND B.Letter<C.Letter

Bu, "Alfabe" tablosunda kaç harf olmasına rağmen (3, 8, 10, 27 vb. Olabilir) 3 harflik tüm kombinasyonları döndürür.

İstediğiniz kombinasyonlar yerine tüm permütasyonlarsa (yani "ACB" ve "ABC" nin sadece bir kez görünmek yerine farklı olarak sayılmasını istiyorsunuz), son satırı (AND bir) silmeniz yeterlidir.

Düzenleme Sonrası: Soruyu yeniden okuduktan sonra, 3 öğenin seçilmesi için sadece belirli bir algoritma değil, gerekli olanın genel algoritma olduğunu anlıyorum . Adam Hughes'un cevabı tam, ne yazık ki oy kullanamıyorum (henüz). Bu cevap basit ama sadece tam 3 öğe istediğiniz zaman işe yarar.


7

Kombinasyon indekslerinin tembel üretimi ile başka bir C # sürümü. Bu sürüm, tüm değerlerin listesi ile geçerli kombinasyonun değerleri arasında bir eşleme tanımlamak için tek bir dizin dizisi tutar, yani tüm çalışma süresi boyunca sürekli olarak O (k) ek alan kullanır . Kod, O (k) zamanında , birincisi de dahil olmak üzere bireysel kombinasyonlar üretir .

public static IEnumerable<T[]> Combinations<T>(this T[] values, int k)
{
    if (k < 0 || values.Length < k)
        yield break; // invalid parameters, no combinations possible

    // generate the initial combination indices
    var combIndices = new int[k];
    for (var i = 0; i < k; i++)
    {
        combIndices[i] = i;
    }

    while (true)
    {
        // return next combination
        var combination = new T[k];
        for (var i = 0; i < k; i++)
        {
            combination[i] = values[combIndices[i]];
        }
        yield return combination;

        // find first index to update
        var indexToUpdate = k - 1;
        while (indexToUpdate >= 0 && combIndices[indexToUpdate] >= values.Length - k + indexToUpdate)
        {
            indexToUpdate--;
        }

        if (indexToUpdate < 0)
            yield break; // done

        // update combination indices
        for (var combIndex = combIndices[indexToUpdate] + 1; indexToUpdate < k; indexToUpdate++, combIndex++)
        {
            combIndices[indexToUpdate] = combIndex;
        }
    }
}

Test kodu:

foreach (var combination in new[] {'a', 'b', 'c', 'd', 'e'}.Combinations(3))
{
    System.Console.WriteLine(String.Join(" ", combination));
}

Çıktı:

a b c
a b d
a b e
a c d
a c e
a d e
b c d
b c e
b d e
c d e

Bu sıralamayı korur. Sonuç kümesinin de içermediğini içermesini bekliyorum c b a.
Dmitri Nesteruk

Görev, n'den k'ya kadar olan tüm kombinasyonları oluşturmaktır. Binom katsayıları, sabit bir n eleman kümesinden k elemanlarının düzensiz bir alt kümesini seçmenin kaç yolu olduğu sorusunu cevaplar . Bu nedenle önerilen algoritma ne gerekiyorsa onu yapar.
Christoph

6

https://gist.github.com/3118596

JavaScript için bir uygulama var. K-kombinasyonlarını ve herhangi bir nesnenin bir dizisinin tüm kombinasyonlarını almak için işlevlere sahiptir. Örnekler:

k_combinations([1,2,3], 2)
-> [[1,2], [1,3], [2,3]]

combinations([1,2,3])
-> [[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]]

6

Burada algoritma C # kodlanmış tembel bir değerlendirilmiş sürümü var:

    static bool nextCombination(int[] num, int n, int k)
    {
        bool finished, changed;

        changed = finished = false;

        if (k > 0)
        {
            for (int i = k - 1; !finished && !changed; i--)
            {
                if (num[i] < (n - 1) - (k - 1) + i)
                {
                    num[i]++;
                    if (i < k - 1)
                    {
                        for (int j = i + 1; j < k; j++)
                        {
                            num[j] = num[j - 1] + 1;
                        }
                    }
                    changed = true;
                }
                finished = (i == 0);
            }
        }

        return changed;
    }

    static IEnumerable Combinations<T>(IEnumerable<T> elements, int k)
    {
        T[] elem = elements.ToArray();
        int size = elem.Length;

        if (k <= size)
        {
            int[] numbers = new int[k];
            for (int i = 0; i < k; i++)
            {
                numbers[i] = i;
            }

            do
            {
                yield return numbers.Select(n => elem[n]);
            }
            while (nextCombination(numbers, size, k));
        }
    }

Ve test kısmı:

    static void Main(string[] args)
    {
        int k = 3;
        var t = new[] { "dog", "cat", "mouse", "zebra"};

        foreach (IEnumerable<string> i in Combinations(t, k))
        {
            Console.WriteLine(string.Join(",", i));
        }
    }

Umarım bu yardım eder!


6

Python'da proje euler için kullandığım permütasyon algoritması vardı:

def missing(miss,src):
    "Returns the list of items in src not present in miss"
    return [i for i in src if i not in miss]


def permutation_gen(n,l):
    "Generates all the permutations of n items of the l list"
    for i in l:
        if n<=1: yield [i]
        r = [i]
        for j in permutation_gen(n-1,missing([i],l)):  yield r+j

Eğer

n<len(l) 

tekrar etmeden ihtiyacınız olan tüm kombinasyonlara sahip olmalısınız, ihtiyacınız var mı?

Bir jeneratör, bu yüzden böyle bir şeyde kullanıyorsunuz:

for comb in permutation_gen(3,list("ABCDEFGH")):
    print comb 

5
Array.prototype.combs = function(num) {

    var str = this,
        length = str.length,
        of = Math.pow(2, length) - 1,
        out, combinations = [];

    while(of) {

        out = [];

        for(var i = 0, y; i < length; i++) {

            y = (1 << i);

            if(y & of && (y !== of))
                out.push(str[i]);

        }

        if (out.length >= num) {
           combinations.push(out);
        }

        of--;
    }

    return combinations;
}

5

Clojure sürümü:

(defn comb [k l]
  (if (= 1 k) (map vector l)
      (apply concat
             (map-indexed
              #(map (fn [x] (conj x %2))
                    (comb (dec k) (drop (inc %1) l)))
              l))))

5

Diyelim ki harf diziniz şöyle görünüyor: "ABCDEFGH". Geçerli sözcük için hangi harfleri kullanacağınızı gösteren üç dizininiz (i, j, k) var.

ABCDEFGH
^ ^ ^
ijk

İlk önce k'yi değiştirirsiniz, böylece bir sonraki adım şöyle görünür:

ABCDEFGH
^ ^ ^
ijk

Sonuna ulaşırsanız, devam edip j ve sonra k'yi tekrar değiştirirsiniz.

ABCDEFGH
^ ^ ^
ijk

ABCDEFGH
^ ^ ^
ijk

J, G'ye ulaştığınızda, i'yi de değiştirmeye başlarsınız.

ABCDEFGH
  ^ ^ ^
  ijk

ABCDEFGH
  ^ ^ ^
  ijk
...
function initializePointers($cnt) {
    $pointers = [];

    for($i=0; $i<$cnt; $i++) {
        $pointers[] = $i;
    }

    return $pointers;     
}

function incrementPointers(&$pointers, &$arrLength) {
    for($i=0; $i<count($pointers); $i++) {
        $currentPointerIndex = count($pointers) - $i - 1;
        $currentPointer = $pointers[$currentPointerIndex];

        if($currentPointer < $arrLength - $i - 1) {
           ++$pointers[$currentPointerIndex];

           for($j=1; ($currentPointerIndex+$j)<count($pointers); $j++) {
                $pointers[$currentPointerIndex+$j] = $pointers[$currentPointerIndex]+$j;
           }

           return true;
        }
    }

    return false;
}

function getDataByPointers(&$arr, &$pointers) {
    $data = [];

    for($i=0; $i<count($pointers); $i++) {
        $data[] = $arr[$pointers[$i]];
    }

    return $data;
}

function getCombinations($arr, $cnt)
{
    $len = count($arr);
    $result = [];
    $pointers = initializePointers($cnt);

    do {
        $result[] = getDataByPointers($arr, $pointers);
    } while(incrementPointers($pointers, count($arr)));

    return $result;
}

$result = getCombinations([0, 1, 2, 3, 4, 5], 3);
print_r($result);

Dayanarak https://stackoverflow.com/a/127898/2628125 ama işaretçiler herhangi bir boyut için, daha soyut.


Bu korkunç dil nedir? Bash?
shinzou

1
php, ancak dil burada önemli değil, algoritma önemli
Oleksandr Knyga

Bu dili öğrenmeyi reddettiğim için çok mutluyum. Tercümanının / derleyicisinin değişkenleri tanımak için yardıma ihtiyacı olduğu bir dil 2018'de mevcut olmamalıdır.
shinzou

4

Tüm söylenen ve yapılan burada bunun için O'caml kodu geliyor. Algoritma kodda açıkça görülmektedir.

let combi n lst =
    let rec comb l c =
        if( List.length c = n) then [c] else
        match l with
        [] -> []
        | (h::t) -> (combi t (h::c))@(combi t c)
    in
        combi lst []
;;

4

Rastgele uzunluk dizesinden belirtilen boyuttaki tüm kombinasyonları size veren bir yöntem. Quinmars'ın çözümüne benzer, ancak çeşitli girdi ve k için çalışır.

Kod etrafa sarılacak şekilde değiştirilebilir, yani 'abcd' wk = 3 girişinden 'dab'.

public void run(String data, int howMany){
    choose(data, howMany, new StringBuffer(), 0);
}


//n choose k
private void choose(String data, int k, StringBuffer result, int startIndex){
    if (result.length()==k){
        System.out.println(result.toString());
        return;
    }

    for (int i=startIndex; i<data.length(); i++){
        result.append(data.charAt(i));
        choose(data,k,result, i+1);
        result.setLength(result.length()-1);
    }
}

"Abcde" için çıktı:

abc abd abe acd ace ade bcd bce bde cde



3

İşte benim C ++ teklifim

Bu çözüm sadece ileri yineleyici varsayar ve bu bir const_iterator olabilir gibi yineleyici türü üzerinde küçük bir kısıtlama empoze etmeye çalıştı. Bu, herhangi bir standart kap ile çalışmalıdır. Argümanların mantıklı olmadığı durumlarda std :: invalid_argumnent atar

#include <vector>
#include <stdexcept>

template <typename Fci> // Fci - forward const iterator
std::vector<std::vector<Fci> >
enumerate_combinations(Fci begin, Fci end, unsigned int combination_size)
{
    if(begin == end && combination_size > 0u)
        throw std::invalid_argument("empty set and positive combination size!");
    std::vector<std::vector<Fci> > result; // empty set of combinations
    if(combination_size == 0u) return result; // there is exactly one combination of
                                              // size 0 - emty set
    std::vector<Fci> current_combination;
    current_combination.reserve(combination_size + 1u); // I reserve one aditional slot
                                                        // in my vector to store
                                                        // the end sentinel there.
                                                        // The code is cleaner thanks to that
    for(unsigned int i = 0u; i < combination_size && begin != end; ++i, ++begin)
    {
        current_combination.push_back(begin); // Construction of the first combination
    }
    // Since I assume the itarators support only incrementing, I have to iterate over
    // the set to get its size, which is expensive. Here I had to itrate anyway to  
    // produce the first cobination, so I use the loop to also check the size.
    if(current_combination.size() < combination_size)
        throw std::invalid_argument("combination size > set size!");
    result.push_back(current_combination); // Store the first combination in the results set
    current_combination.push_back(end); // Here I add mentioned earlier sentinel to
                                        // simplyfy rest of the code. If I did it 
                                        // earlier, previous statement would get ugly.
    while(true)
    {
        unsigned int i = combination_size;
        Fci tmp;                            // Thanks to the sentinel I can find first
        do                                  // iterator to change, simply by scaning
        {                                   // from right to left and looking for the
            tmp = current_combination[--i]; // first "bubble". The fact, that it's 
            ++tmp;                          // a forward iterator makes it ugly but I
        }                                   // can't help it.
        while(i > 0u && tmp == current_combination[i + 1u]);

        // Here is probably my most obfuscated expression.
        // Loop above looks for a "bubble". If there is no "bubble", that means, that
        // current_combination is the last combination, Expression in the if statement
        // below evaluates to true and the function exits returning result.
        // If the "bubble" is found however, the ststement below has a sideeffect of 
        // incrementing the first iterator to the left of the "bubble".
        if(++current_combination[i] == current_combination[i + 1u])
            return result;
        // Rest of the code sets posiotons of the rest of the iterstors
        // (if there are any), that are to the right of the incremented one,
        // to form next combination

        while(++i < combination_size)
        {
            current_combination[i] = current_combination[i - 1u];
            ++current_combination[i];
        }
        // Below is the ugly side of using the sentinel. Well it had to haave some 
        // disadvantage. Try without it.
        result.push_back(std::vector<Fci>(current_combination.begin(),
                                          current_combination.end() - 1));
    }
}

3

Burada Java'da yazdığım ve "outOf" öğelerinden "num" öğelerinin tüm birleşimlerini hesaplayan ve döndüren bir kod.

// author: Sourabh Bhat (heySourabh@gmail.com)

public class Testing
{
    public static void main(String[] args)
    {

// Test case num = 5, outOf = 8.

        int num = 5;
        int outOf = 8;
        int[][] combinations = getCombinations(num, outOf);
        for (int i = 0; i < combinations.length; i++)
        {
            for (int j = 0; j < combinations[i].length; j++)
            {
                System.out.print(combinations[i][j] + " ");
            }
            System.out.println();
        }
    }

    private static int[][] getCombinations(int num, int outOf)
    {
        int possibilities = get_nCr(outOf, num);
        int[][] combinations = new int[possibilities][num];
        int arrayPointer = 0;

        int[] counter = new int[num];

        for (int i = 0; i < num; i++)
        {
            counter[i] = i;
        }
        breakLoop: while (true)
        {
            // Initializing part
            for (int i = 1; i < num; i++)
            {
                if (counter[i] >= outOf - (num - 1 - i))
                    counter[i] = counter[i - 1] + 1;
            }

            // Testing part
            for (int i = 0; i < num; i++)
            {
                if (counter[i] < outOf)
                {
                    continue;
                } else
                {
                    break breakLoop;
                }
            }

            // Innermost part
            combinations[arrayPointer] = counter.clone();
            arrayPointer++;

            // Incrementing part
            counter[num - 1]++;
            for (int i = num - 1; i >= 1; i--)
            {
                if (counter[i] >= outOf - (num - 1 - i))
                    counter[i - 1]++;
            }
        }

        return combinations;
    }

    private static int get_nCr(int n, int r)
    {
        if(r > n)
        {
            throw new ArithmeticException("r is greater then n");
        }
        long numerator = 1;
        long denominator = 1;
        for (int i = n; i >= r + 1; i--)
        {
            numerator *= i;
        }
        for (int i = 2; i <= n - r; i++)
        {
            denominator *= i;
        }

        return (int) (numerator / denominator);
    }
}

3

Kısa bir Javascript çözümü:

Array.prototype.combine=function combine(k){    
    var toCombine=this;
    var last;
    function combi(n,comb){             
        var combs=[];
        for ( var x=0,y=comb.length;x<y;x++){
            for ( var l=0,m=toCombine.length;l<m;l++){      
                combs.push(comb[x]+toCombine[l]);           
            }
        }
        if (n<k-1){
            n++;
            combi(n,combs);
        } else{last=combs;}
    }
    combi(1,toCombine);
    return last;
}
// Example:
// var toCombine=['a','b','c'];
// var results=toCombine.combine(4);

3

Algoritma:

  • 1'den 2 ^ n'ye kadar sayın.
  • Her basamağı ikili gösterimine dönüştürün.
  • Her bir 'açık' biti konuma göre setinizin elemanlarına çevirin.

C # dilinde:

void Main()
{
    var set = new [] {"A", "B", "C", "D" }; //, "E", "F", "G", "H", "I", "J" };

    var kElement = 2;

    for(var i = 1; i < Math.Pow(2, set.Length); i++) {
        var result = Convert.ToString(i, 2).PadLeft(set.Length, '0');
        var cnt = Regex.Matches(Regex.Escape(result),  "1").Count; 
        if (cnt == kElement) {
            for(int j = 0; j < set.Length; j++)
                if ( Char.GetNumericValue(result[j]) == 1)
                    Console.Write(set[j]);
            Console.WriteLine();
        }
    }
}

Neden çalışıyor?

Bir n elemanı kümesinin alt kümeleri ile n bit dizileri arasında bir bijeksiyon vardır.

Bu, dizileri sayarak kaç alt kümenin olduğunu anlayabileceğimiz anlamına gelir.

örneğin, aşağıda ayarlanan dört eleman, {0,1} X {0, 1} X {0, 1} X {0, 1} (veya 2 ^ 4) farklı sekans ile temsil edilebilir.

Yani - tek yapmamız gereken tüm kombinasyonları bulmak için 1'den 2 ^ n'ye kadar saymaktır. (Boş kümeyi görmezden geliriz.) Sonra rakamları ikili gösterimine çevirin. Ardından setinizin elemanlarını 'on' bitleri ile değiştirin.

Yalnızca k öğesi sonuçları istiyorsanız, yalnızca k bitleri 'açık' olduğunda yazdırın.

(K uzunluk alt kümeleri yerine tüm alt kümeleri istiyorsanız, cnt / kElement parçasını kaldırın.)

(Kanıt için, bkz. MIT ücretsiz bilgisayar yazılımı için Matematik, Lehman ve diğerleri, bölüm 11.2.2. Https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-042j-mathematics- bilgisayar-bilim-sonbahar-2010 / okumalar / )


3

kısa python kodu, veren indeks pozisyonları

def yield_combos(n,k):
    # n is set size, k is combo size

    i = 0
    a = [0]*k

    while i > -1:
        for j in range(i+1, k):
            a[j] = a[j-1]+1
        i=j
        yield a
        while a[i] == i + n - k:
            i -= 1
        a[i] += 1

2

Ben binom katsayısı ile çalışmak için ortak fonksiyonları işlemek için bir sınıf yazdım, hangi sorunun altında kalıyor tipi. Aşağıdaki görevleri gerçekleştirir:

  1. Tüm K-dizinlerini herhangi bir N seçimi K için bir dosyaya güzel bir biçimde verir. K-endeksleri daha açıklayıcı dizeler veya harfler ile değiştirilebilir. Bu yöntem, bu tür problemleri çözmeyi oldukça önemsiz kılmaktadır.

  2. K-dizinlerini, sıralanmış binom katsayısı tablosundaki bir girdinin uygun dizinine dönüştürür. Bu teknik, yinelemeye dayanan eski yayınlanmış tekniklerden çok daha hızlıdır. Bunu Pascal Üçgeni'ne özgü bir matematiksel özellik kullanarak yapar. Makalem bundan bahsediyor. Bu tekniği ilk keşfeden ve yayınlayan ilk kişi olduğuma inanıyorum, ama yanılmış olabilirim.

  3. Sıralı bir binom katsayısı tablosundaki dizini karşılık gelen K-dizinlerine dönüştürür.

  4. Taşma olasılığı daha düşük olan ve daha büyük sayılarla çalışan binom katsayısını hesaplamak için Mark Dominus yöntemini kullanır .

  5. Sınıf .NET C # ile yazılmıştır ve genel bir liste kullanarak sorunla ilgili nesneleri (varsa) yönetmek için bir yol sağlar. Bu sınıfın yapıcısı, true olduğunda yönetilecek nesneleri tutmak için genel bir liste oluşturacak olan InitTable adlı bir bool değeri alır. Bu değer false olursa, tablo oluşturmaz. Yukarıdaki 4 yöntemi uygulamak için tablonun oluşturulması gerekmez. Tabloya erişmek için erişimci yöntemleri sunulmaktadır.

  6. Sınıfın ve yöntemlerinin nasıl kullanılacağını gösteren ilişkili bir test sınıfı vardır. 2 vaka ile kapsamlı bir şekilde test edilmiştir ve bilinen bir hata yoktur.

Bu sınıf hakkında bilgi edinmek ve kodu indirmek için, bkz . Binom Katsayısını Tablolama .

Bu sınıfı C ++ 'a dönüştürmek zor olmamalı.


Buna "Mark Dominus yöntemi" demek gerçekten doğru değil, çünkü bahsettiğim gibi en az 850 yaşında ve düşünmesi zor değil. Neden Lilavati yöntemi demiyorsun ?
MJD
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.