A tarefa é contar o número de substrings distintos de comprimento k, para k = 1,2,3,4, .....
Resultado
Você deve imprimir uma linha por k
que consiga concluir com um número por linha de saída. Sua saída deve estar em ordem de aumento k
até você ficar sem tempo.
Ponto
Sua pontuação é a maior nota que você pode obter no meu computador em menos de 1 minuto.
Você deve usar http://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/chr2.fa.gz como sua entrada e ignorar as novas linhas. Seu código, no entanto, diferencia maiúsculas de minúsculas.
Você pode descomprimir a entrada antes de iniciar o tempo.
O código (ineficiente) a seguir conta o número de 4 metros distintos.
awk -v substr_length=4 '(len=length($0))>=substr_length{for (i=1; (i-substr_length)<len; i++) substrs[substr($0,i,substr_length)]++}; END{for (i in substrs) print substrs[i], i}' file.txt|wc
Limites de memória
Para fazer com que seu código se comporte bem no meu computador e para tornar a tarefa mais desafiadora, limitarei a RAM que você pode usar para 2 GB usando
ulimit -v 2000000
antes de executar seu código. Estou ciente de que essa não é uma maneira sólida de limitar o uso de RAM; portanto, não use maneiras imaginativas para contornar esse limite, por exemplo, gerando novos processos. Claro que você pode escrever código multiencadeado, mas se alguém o fizer, terei que aprender a limitar o total de RAM usada para isso.
Desempate
No caso de um empate por um tempo máximo k
, cronometrarei quanto tempo leva para produzir os resultados k+1
e o mais rápido ganha. No caso em que eles correm ao mesmo tempo em até um segundo k+1
, a primeira submissão vence.
Línguas e bibliotecas
Você pode usar qualquer idioma que tenha um compilador / intérprete disponível gratuitamente / etc. para Linux e quaisquer bibliotecas que também estão disponíveis gratuitamente para Linux.
Minha máquina Os horários serão executados na minha máquina. Esta é uma instalação padrão do ubuntu em um processador AMD FX-8350 de oito núcleos em uma placa-mãe Asus M5A78L-M / USB3 (soquete AM3 +, 8GB DDR3). Isso também significa que eu preciso poder executar seu código. Como conseqüência, use apenas software livre facilmente disponível e inclua instruções completas sobre como compilar e executar seu código.
Saída de teste
O código do FUZxxl gera o seguinte (mas não todos em 1 minuto), que acredito estar correto.
14
92
520
2923
15714
71330
265861
890895
2482912
5509765
12324706
29759234
tabela do Campeonato
- k> = 4000 FUZxxl (C)
- k = 16 por Keith Randall (C ++)
- k = 10 por FUZxxl (C)
Quanto você pode especializar seu código na entrada?
- Claramente, isso arruinaria a concorrência se você precomputasse as respostas e o seu código as produzisse. Não faça isso.
- Idealmente, qualquer coisa que seu código precise aprender sobre os dados que ele usará para executar mais rapidamente, em tempo de execução.
- No entanto, você pode assumir que a entrada será semelhante aos dados nos arquivos * .fa em http://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/ .
J
, uma solução ingênua seria simplesmente `[: ~.]` Mas acho que isso não será suficiente.Respostas:
C (≥ 4000)
Esse código não usa menos de 2 GB de RAM (usa um pouco mais) nem produz qualquer saída no primeiro minuto. Mas se você esperar cerca de seis minutos, ela imprimirá todas as contagens de k- mer de uma só vez.
Uma opção está incluída para limitar o k mais alto pelo qual contamos os k- meros. Quando k é limitado a um intervalo razoável, o código termina em menos de um minuto e usa menos de 2 GiB de RAM. A OP classificou esta solução e termina em menos de um minuto para um limite não superior a 4000.
Como funciona?
O algoritmo possui quatro etapas.
Classifique com sufixo uma matriz de índices no buffer de entrada. Por exemplo, os sufixos da string
mississippi
são:Essas seqüências classificadas em ordem lexicográfica são:
É fácil ver que todas as substrings iguais de comprimento k para todos os k são encontradas nas entradas adjacentes da matriz classificada por sufixo.
É preenchido um array inteiro no qual armazenamos em cada índice k o número de k- meros distintos . Isso é feito de maneira um pouco complicada para acelerar o processo. Considere duas entradas adjacentes a matriz classificada com sufixo.
p denota o comprimento do prefixo comum mais longo das duas entradas, l denota o comprimento da segunda entrada. Para esse par, encontramos uma nova substring distinta de comprimento k para p < k ≤ l . Como p ≪ l geralmente é válido, não é prático incrementar um grande número de entradas de matriz para cada par. Em vez disso, armazenamos a matriz como uma matriz de diferenças, em que cada entrada k indica a diferença entre o número de k -mers e o número de ( k - 1) -mers. Isso ativa uma atualização do formulário
em uma atualização muito mais rápida do formulário
Observando cuidadosamente que l é sempre diferente e, de fato, cada 0 < l < n aparecerá exatamente uma vez, podemos omitir as subtrações e, em vez disso, subtrair 1 de cada diferença ao converter as diferenças em quantidades.
O código fonte
Essa fonte usa o libdivsufsort para classificar matrizes de sufixos. O código gera saída de acordo com a especificação quando compilado com esta chamada.
alternativamente, o código pode gerar saída binária quando compilado com a seguinte chamada.
Para limitar o k mais alto para o qual k- meros devem ser contados, forneça -DICAP = k onde k é o limite:
Compile com
-O3
se o seu compilador fornecer essa opção.O formato do arquivo binário pode ser convertido no formato de saída legível por humanos com o seguinte programa:
saída de amostra
Exemplo de saída no formato binário para o arquivo
chr22.fa
pode ser encontrado aqui . Descompactebzip2 -d
primeiro. A saída é fornecida no formato binário apenas porque compacta muito melhor (3,5 kB x 260 MB) do que a saída no formato legível por humanos. Cuidado, porém, que a saída de referência tem um tamanho de 924 MB quando descompactada. Você pode querer usar um pipe como este:fonte
cat
. Use o redirecionamento de shell como em./dsskmer <~/Downloads/chr2.fs
. O código precisa saber quanto tempo o arquivo de entrada é e isso não é possível com um canal.C ++, k = 16, 37 segundos
Calcula todos os 16 mers na entrada. Cada 16-mer é compactado em 4 bits em um símbolo em uma palavra de 64 bits (com um padrão de bit reservado para EOF). As palavras de 64 bits são classificadas. A resposta para cada k pode ser lida observando-se com que frequência os 4 * k bits superiores das palavras classificadas são alterados.
Para a entrada de teste, uso cerca de 1,8 GB, logo abaixo do fio.
Tenho certeza que a velocidade de leitura pode ser melhorada.
Resultado:
Programa:
Compilar
g++ -O3 kmer.cc -o kmer
e executar com./kmer chr2.fa
.fonte
C ++ - uma melhoria em relação à solução FUZxxl
Eu não mereço absolutamente nenhum crédito pelo próprio método de computação e, se nenhuma abordagem melhor aparecer nesse meio tempo, a recompensa deve ir para a FUZxxl à direita.
Simplesmente usei Kasai et al. algoritmo para calcular LCPs em O (n).
O resto é uma mera adaptação do código FUZxxl, usando recursos C ++ mais concisos aqui e ali.
Deixei o código de computação original para permitir comparações.
Como os processos mais lentos são a construção de SA e a contagem de LCP, removi a maioria das outras otimizações para evitar sobrecarregar o código para obter ganhos negligenciáveis.
Estendi a tabela SA para incluir o prefixo de comprimento zero. Isso facilita a computação do LCP.
Não forneci uma opção de limitação de comprimento, sendo o processo mais lento agora o cálculo do SA que não pode ser reduzido (ou pelo menos não vejo como poderia ser).
Também removi a opção de saída binária e a exibição limitada aos 10 primeiros valores.
Suponho que esse código seja apenas uma prova de conceito; portanto, não é necessário desorganizar as telas ou saturar os discos.
Construindo o executável
Eu tive que compilar todo o projeto (incluindo a versão lite
divsufsort
) do x64 para superar o limite de alocação do Win32 2Gb.divsufsort
O código lança vários avisos devido ao uso intenso deint
s em vez desize_t
s, mas isso não será um problema para entradas abaixo de 2 GB (que exigiriam 26 GB de RAM de qualquer maneira: D).Compilação Linux
compile
main.cpp
edivsufsort.c
use os comandos:Suponho que a
divsufsort
biblioteca regular funcione bem no Linux nativo, desde que você possa alocar um pouco mais do que 3Gb.Performances
O algoritmo Kasai requer a tabela SA inversa, que consome mais 4 bytes por caractere, totalizando 13 (em vez de 9 com o método FUZxxl).
O consumo de memória para entrada de referência é, portanto, acima de 3Gb.
Por outro lado, o tempo de computação é dramaticamente aprimorado e todo o algoritmo agora está em O (n):
Outras melhorias
A construção da SA é agora o processo mais lento.
Alguns bits do
divsufsort
algoritmo devem ser paralelos a qualquer recurso interno de um compilador desconhecido para mim, mas, se necessário, o código deve ser fácil de se adaptar a multithreading mais clássico ( à exemplo do C ++ 11).A lib também possui uma carga de parâmetros, incluindo vários tamanhos de balde e o número de símbolos distintos na cadeia de entrada. Eu só dei uma olhada superficial neles, mas suspeito que comprimir o alfabeto pode valer a pena tentar se suas cordas forem infinitas permutações de ACTG ( e você estiver desesperado por apresentações).
Também existem alguns métodos paralelizáveis para calcular o LCP a partir do SA, mas como o código deve ser executado em menos de um minuto em um processador um pouco mais poderoso que o meu insignificante [email protected] e todo o algoritmo está em O (n), duvido disso. valeria a pena o esforço.
fonte
C (pode resolver até 10 em um minuto na minha máquina)
Esta é uma solução muito simples. Ele constrói uma árvore dos k -mers encontrados e os conta. Para economizar memória, os caracteres são primeiro convertidos em números inteiros de 0 a n - 1, em que n é o número de caracteres diferentes na entrada, portanto, não precisamos fornecer espaço para caracteres que nunca aparecem. Além disso, menos memória é alocada para as folhas do que para outros nós, para economizar mais memória. Esta solução usa cerca de 200 MiB de RAM durante o tempo de execução na minha máquina. Ainda estou melhorando, então talvez na iteração seja ainda mais rápido!
Para compilar, salve o código abaixo em um arquivo nomeado
kmers.c
e execute em um sistema operacional semelhante ao POSIX:Você pode querer substituir
-O3
para-O
se suas sustentações do compilador que. Para executar, primeiro descompactarchr2.fa.gz
emchr2.fa
e execute:Isso produz a saída passo a passo. Você pode limitar o tempo e o espaço. Use algo como
reduzir os recursos conforme necessário.
Melhorias
fonte
timeout 60s
funciona bem para mim, portanto não há necessidade de criar uma maneira de eliminar o código após 1 minuto.ulimit -t 60
.