2015-04-27 14 views
7

Dla przykładu rozważmy liczbę 96. To może być napisany w następujący sposób:R Algorytm generowania wszystkich możliwych factorizations z szeregu

1. 96 
2. 48 * 2 
3. 24 * 2 * 2 
4. 12 * 2 * 2 * 2 
5. 6 * 2 * 2 * 2 * 2 
6. 3 * 2 * 2 * 2 * 2 * 2 
7. 4 * 3 * 2 * 2 * 2 
8. 8 * 3 * 2 * 2 
9. 6 * 4 * 2 * 2 
10. 16 * 3 * 2 
11. 4 * 4 * 3 * 2 
12. 12 * 4 * 2 
13. 8 * 6 * 2 
14. 32 * 3 
15. 8 * 4 * 3 
16. 24 * 4 
17. 6 * 4 * 4 
18. 16 * 6 
19. 12 * 8 

wiem, że to jest związane z partycjami za każdym numerem wpisanym jako moc , n, pojedyncza liczba podstawowa, p, to po prostu liczba sposobów na zapisanie n. Na przykład, aby znaleźć wszystkie faktoryzacje 2^5, musimy znaleźć wszystkie sposoby na napisanie 5. Są to:

  1. 1 + 1 + 1 + 1 + 1 == >> 2^1 * 2^1 * 2^1 * 2^1 * 2^1
  2. 1 + 1 + 1 + 2 == >> 2^1 * 2^1 * 2^1 * 2^2
  3. 1 + 1 + 3 == >> 2^1 * 2^1 * 2^3
  4. 1 + 2 + 2 == >> 2^1 * 2^2 * 2^2
  5. 1 + 4 == >> 2^1 * 2^4
  6. 2 + 3 == >> 2^2 * 2^3
  7. 5 == >> 2^5

Znalazłem wspaniały artykuł autorstwa Jerome'a ​​Kellehera na temat algorytmów generowania partycji here. Mam dostosowany jedną ze swoich algorytmów Python R. Kod jest poniżej:

library(partitions) ## using P(n) to determine number of partitions of an integer 
IntegerPartitions <- function(n) { 
    a <- 0L:n 
    k <- 2L 
    a[2L] <- n 
    MyParts <- vector("list", length=P(n)) 
    count <- 0L 
    while (!(k==1L)) { 
     x <- a[k-1L]+1L 
     y <- a[k]-1L 
     k <- k-1L 
     while (x<=y) {a[k] <- x; y <- y-x; k <- k+1L} 
     a[k] <- x+y 
     count <- count+1L 
     MyParts[[count]] <- a[1L:k] 
    } 
    MyParts 
} 

próbowałem przedłużyć tę metodę do numerów z więcej niż jednego czynnika jedna premiera, ale mój kod dostał bardzo niezgrabne. Po walce z tym pomysłem przez chwilę postanowiłem spróbować innej trasy. Mój nowy algorytm nie ma zastosowania do generowania partycji. Jest to bardziej "lookback" algorytm, który korzysta z faktoryzacji, które zostały już wygenerowane. Kod znajduje się poniżej:

FactorRepresentations <- function(n) { 

MyFacts <- EfficientFactorList(n) 
MyReps <- lapply(1:n, function(x) x) 

    for (k in 4:n) { 
     if (isprime(k)) {next} 
     myset <- MyFacts[[k]] 
     mylist <- vector("list") 
     mylist[[1]] <- k 
     count <- 1L 
      for (j in 2:ceiling(length(myset)/2)) { 
       count <- count+1L 
       temp <- as.integer(k/myset[j]) 
       myvec <- sort(c(myset[j], temp), decreasing=TRUE) 
       mylist[[count]] <- myvec 
       MyTempRep <- MyReps[[temp]] 

       if (isprime(temp) || temp==k) {next} 

       if (length(MyTempRep)>1) { 
        for (i in 1:length(MyTempRep)) { 
         count <- count+1L 
         myvec <- sort(c(myset[j], MyTempRep[[i]]), decreasing=TRUE) 
         mylist[[count]] <- myvec 
        } 
       } 
      } 
     MyReps[[k]] <- unique(mylist) 
    } 
    MyReps 
} 

Pierwsza funkcja w powyższym kodzie jest po prostu funkcją, która generuje wszystkie czynniki. Oto kod, jeśli jesteś ciekaw:

EfficientFactorList <- function(n) { 
    MyFactsList <- lapply(1:n, function(x) 1) 
    for (j in 2:n) { 
     for (r in seq.int(j, n, j)) {MyFactsList[[r]] <- c(MyFactsList[[r]], j)} 
    } 
    MyFactsList 
} 

Mój algorytm jest po prostu w porządku jeśli chodzi tylko o numerach mniej niż 10.000 (generuje wszystkie factorizations dla każdej liczby < = 10000 w ciągu około 17 sekund), ale zdecydowanie nie skaluje się dobrze. Chciałbym znaleźć algorytm, który ma tę samą przesłankę generowania listy wszystkich faktoryzacji dla każdej liczby mniejszej lub równej n, ponieważ niektóre z aplikacji, które mam na myśli, będą odwoływać się do danej faktoryzacji wiele razy, a więc mieć ją na liście powinna być szybsza niż generowanie jej w locie za każdym razem (wiem, że jest tu koszt pamięci).

+1

To nie jest prosty problem (oczywiście), ale w przypadku, gdy jeszcze go nie znalazłeś, oto odpowiedni wpis z internetowej encyklopedii ciągów liczb całkowitych: https://oeis.org/A001055 –

+0

To jest bardzo pomocne, choć daje to całkowitą liczbę faktoryzacji, a nie faktyczne same faktoryzacje. Na przykład, dla n = 96 jak wyżej, daje 19. –

Odpowiedz

5

Twoja funkcja EfficientFactorList dobrze radzi sobie z efektywnym przechwytywaniem zestawu wszystkich czynników dla każdej liczby od 1 do n, więc pozostaje tylko zgromadzenie zestawu wszystkich współczynników. Jak sugerujesz, użycie współczynników o mniejszych wartościach do obliczania faktoryzacji dla większych wartości wydaje się być wydajne.

Weź pod uwagę liczbę k, o współczynnikach k_1, k_2, ..., k_n. Naiwnym podejściem byłoby połączenie współczynników k/k_1, k/k_2, ..., k/k_n, dodanie k_i do każdej faktoryzacji k/k_i, aby uzyskać faktoryzację k. Jako sprawdzony przykład rozważ obliczenie faktoryzacji 16 (które ma nietrywialne czynniki 2, 4 i 8).2 ma faktoryzację {2}, 4 ma współczynniki {4, 2 * 2}, a 8 ma współczynniki {8, 4 * 2, 2 * 2 * 2}, więc obliczilibyśmy pełny zestaw faktoryzacji przez pierwsze obliczenia {2 * 8, 4 * 4, 2 * 2 * 4, 8 * 2, 4 * 2 * 2, 2 * 2 * 2 * 2}, a następnie biorąc unikalne czynniki, {8 * 2, 4 * 4, 4 * 2 * 2, 2 * 2 * 2 * 2}. Dodanie 16 daje ostateczną odpowiedź.

Bardziej efektywnym podejściem jest zauważenie, że nie musimy dołączać k_i do wszystkich faktoryzacji k/k_i. Na przykład, nie musieliśmy dodawać 2 * 2 * 4 z faktoryzacji 4, ponieważ jest to już uwzględnione z faktoryzacji 8. Podobnie, nie musieliśmy dodawać 2 * 8 z faktoryzacji 2, ponieważ to jest już uwzględnione z faktoryzacji 8. Ogólnie, musimy tylko uwzględnić faktoryzację z k/k_i, jeśli wszystkie wartości w faktoryzacji wynoszą k_i lub więcej.

w postaci kodu:

library(gmp) 
all.fact <- function(n) { 
    facts <- EfficientFactorList(n) 
    facts[[1]] <- list(1) 
    for (x in 2:n) { 
    if (length(facts[[x]]) == 2) { 
     facts[[x]] <- list(x) # Prime number 
    } else { 
     x.facts <- facts[[x]][facts[[x]] != 1 & facts[[x]] <= (x^0.5+0.001)] 
     allSmaller <- lapply(x.facts, function(pf) lapply(facts[[x/pf]], function(y) { 
     if (all(y >= pf)) { 
      return(c(pf, y)) 
     } else { 
      return(NULL) 
     } 
     })) 
     allSmaller <- do.call(c, allSmaller) 
     facts[[x]] <- c(x, allSmaller[!sapply(allSmaller, function(y) is.null(y))]) 
    } 
    } 
    return(facts) 
} 

to sporo szybciej niż oddelegowanego kodu:

system.time(f1 <- FactorRepresentations(10000)) 
# user system elapsed 
# 13.470 0.159 13.765 
system.time(f2 <- all.fact(10000)) 
# user system elapsed 
# 1.602 0.028 1.641 

Jako kontrola poprawności, ale także zwraca taką samą liczbę factorizations dla każdego numeru:

lf1 <- sapply(f1, length) 
lf2 <- sapply(f2, length) 
all.equal(lf1, lf2) 
# [1] TRUE 
+0

Naprawdę ładne wykonanie R! Jedna drobna obserwacja: w funkcji do.call w pobliżu dna, "c" powinno być ciągiem tj. Do.call ("c", allSmaller) –

+0

Ten kod również jest lepszy niż mój. all.fact (20000) zajmuje tylko około 3 sekund, podczas gdy moja trwała prawie 50 sekund. Niesamowite!! –

+1

@JosephWood powtórz swój pierwszy komentarz, czy jest jakiś powód, dla którego to sugerujesz? 'do.call (c, list (1, 2, 3))' zwraca to samo co 'do.call (" c ", list (1, 2, 3))' i zapisuje dwa naciśnięcia klawiszy. Z "? Do.call" widzę, że używają obu, ale wydaje się, że ma to znaczenie tylko wtedy, gdy określasz środowiska (których tutaj nie robimy). – josliber

0

Jeśli ktoś jest zainteresowany generowaniem partycji multiplikatywnych dla jednej liczby r n, poniżej są dwa algorytmy, które nie tylko, że (funkcja IntegerPartition pochodzi z pytania powyżej):

library(gmp) 
library(partitions) 
get_Factorizations1 <- function(MyN) { 
    pfs <- function (x1) { 
     n1 <- length(x1) 
     y1 <- x1[-1L] != x1[-n1] 
     i <- c(which(y1), n1) 
     list(lengths = diff(c(0L, i)), values = x1[i], uni = sum(y1)+1L) 
    } 

    if (MyN==1L) return(MyN) 
    else { 
     pfacs <- pfs(as.integer(factorize(MyN))) 
     unip <- pfacs$values 
     pv <- pfacs$lengths 
     n <- pfacs$uni 
     mySort <- order(pv, decreasing = TRUE) 
     pv <- pv[mySort] 
     unip <- unip[mySort] 
     myReps <- lapply(IntegerPartitions(pv[1L]), function(y) unip[1L]^y) 
     if (n > 1L) { 
      mySet <- unlist(lapply(2L:n, function(x) rep(unip[x],pv[x]))) 
      for (p in mySet) { 
       myReps <- unique(do.call(c, 
        lapply(myReps, function(j) { 
         dupJ <- duplicated(j) 
         nDupJ <- !dupJ 
         SetJ <- j[which(nDupJ)] 
         lenJ <- sum(nDupJ) 
         if (any(dupJ)) {v1 <- j[which(dupJ)]} else {v1 <- vector(mode="integer")} 
         tList <- vector("list", length=lenJ+1L) 
         tList[[1L]] <- sort(c(j,p)) 

         if (lenJ > 1L) {c2 <- 1L 
          for (a in 1:lenJ) {tList[[c2 <- c2+1L]] <- sort(c(v1,SetJ[-a],SetJ[a]*p))} 
         } else { 
          tList[[2L]] <- sort(c(v1,p*SetJ)) 
         } 
         tList 
        } 
       ))) 
      } 
     } 
    } 
    myReps 
} 

Poniżej josliber Kod z góry manipulowane obsłużyć pojedynczy przypadek. Funkcja MyFactors pochodzi z tego post (zwraca wszystkie czynniki danej liczby).

library(gmp) 
get_Factorizations2 <- function(n) { 
    myFacts <- as.integer(MyFactors(n)) 
    facts <- lapply(myFacts, function(x) 1L) 
    numFacs <- length(myFacts) 
    facts[[numFacs]] <- myFacts 
    names(facts) <- facts[[numFacs]] 
    for (j in 2L:numFacs) { 
     x <- myFacts[j] 
     if (isprime(x)>0L) { 
      facts[[j]] <- list(x) 
     } else { 
      facts[[j]] <- myFacts[which(x%%myFacts[myFacts <= x]==0L)] 
      x.facts <- facts[[j]][facts[[j]] != 1 & facts[[j]] <= (x^0.5+0.001)] 
      allSmaller <- lapply(x.facts, function(pf) lapply(facts[[which(names(facts)==(x/pf))]], function(y) { 
       if (all(y >= pf)) { 
        return(c(pf, y)) 
       } else { 
        return(NULL) 
       } 
      })) 
      allSmaller <- do.call(c, allSmaller) 
      facts[[j]] <- c(x, allSmaller[!sapply(allSmaller, function(y) is.null(y))]) 
     } 
    } 
    facts[[numFacs]] 
} 

Oto niektóre odniesienia:

set.seed(101) 
samp <- sample(10^7, 10^4) 
library(rbenchmark) 
benchmark(getFacs1=sapply(samp, get_Factorizations), 
      getFacs2=sapply(samp, get_Factorizations2), 
      replications=5, 
      columns = c("test", "replications", "elapsed", "relative"), 
      order = "relative") 
test replications elapsed relative 
1 getFacs1   5 117.68 1.000 
2 getFacs2   5 216.39 1.839 


system.time(t2 <- get_Factorizations(25401600)) 
user system elapsed 
10.89 0.03 10.97 
system.time(t2 <- get_Factorizations2(25401600)) 
user system elapsed 
21.08 0.00 21.12 

length(t1)==length(t2) 
[1] TRUE 

object.size(t1) 
28552768 bytes 
object.size(t2) 
20908768 bytes 

Nawet get_Factorizations1 jest szybsza, druga metoda jest bardziej intuicyjny (patrz josliber za doskonałe wyjaśnienie powyżej) i wytwarza mniejszy obiekt. Dla zainteresowanych czytelników, here to naprawdę dobry artykuł na ten temat.

Powiązane problemy