Sunday 30 July 2017

Clogure Média Móvel


Na semana passada, estava ansioso para ir ao dojo Scala. Scala é outra linguagem JVM moderna, mas muito mais como ML do que Lisp. Enquanto lá, eu traduzi o programa de árvore fractal de Clojure para Scala. Como você pode ver, eles são efetivamente o mesmo programa. As linhas de comando para executá-los são: clj fractaltree. clj scalac ScalaFractalTree. scala ampamp scala ScalaFractalTree Instantaneamente torna-se óbvio que a versão Scala é consideravelmente mais rápida. Eu tentei usar (definir warn-on-reflection true), o que me disse que eu precisava digitar insinuar os objetos gráficos no programa clojure, e também diz algo sobre o proxy JPanel. Eu não sei como consertar isso, mas eu não acho que ele é chamado frequentemente o suficiente para ser importante. As dicas de tipos de gráficos pareciam acelerar a versão de Clojure um pouco, mas ainda é visivelmente mais lenta que a Scala. Im presumindo que as chamadas recursivas para draw-tree são boxe e descompactando os parâmetros na versão Clojure. Alguém sabe se esse é o caso e, em caso afirmativo, o que pode ser feito sobre isso. Heres the scala version 4 comments: Eu tentei os dois e eles não parecem sensivelmente diferentes para mim. Especialmente quando você exclui o tempo de compilação. Você tem números específicos Clojure autoboxes tudo no limite da chamada de função, ou seja, todos os argumentos e o valor de retorno são todos encaixotados. Então, sim, as chamas recursivas draw-tree terão seus argumentos encaixotados. IIRC nada pode ser feito sobre o boxe em torno de chamadas de função per se. O que você pode fazer é marcar a função como inlineable ou transformá-la em uma macro, caso em que a chamada de função é compilada, mas eu não sei o quão bem isso funcionaria com uma função recursiva como draw-tree. A versão Clojure é muito mais rápida se você codificá-la como a versão Scala. Na sua versão Clojure, você cria dois bloqueios para cada chamada de draw-tree - new-length e new-angle, que são avaliados duas vezes de forma resprctiva. No código Scala você simplesmente criou os quatro valores necessários diretamente. Se eu codificar a versão Clojure dessa forma também é muito mais rápido - não é surpreendente porque já não precisa criar os fechamentos e chamá-los. Quando eu escrevo sugiro todos os argumentos para desenhar-árvore (ou seja, defn draw-tree Gráficos g2d Duplo ângulo duplo x duplo y Duplo comprimento Duplo ângulo-ângulo profundidade inteira). Ele se sente um pouco mais rápido, mas isso pode ser um pensamento ilusório. Demanda tendência de anúncios de emprego citando Clojure como uma proporção de todos os trabalhos de TI com uma correspondência na categoria Linguagens de Programação. Tendência Salarial Clojure Este gráfico fornece a média móvel de 3 meses para os salários citados em trabalhos permanentes de TI citando Clojure no Reino Unido. Histograma Salarial Clojure Este gráfico fornece um histograma salarial para trabalhos de TI citando Clojure nos 3 meses até 13 de janeiro de 2017 no Reino Unido. Clojure Top 30 Locais de trabalho A tabela abaixo analisa a demanda e fornece um guia para os salários médios citados em trabalhos de TI citando Clojure no Reino Unido durante os 3 meses até 13 de janeiro de 2017. A coluna de Mudança de Rank fornece uma indicação da mudança de demanda Dentro de cada local com base no mesmo período de 3 meses do ano passado. Alteração de classificação no mesmo período Último ano Correspondência de emprego permanente de TI O salário médio nos últimos 3 meses Este fim de semana eu decidi tentar minha mão em alguns Scala e Clojure. Eu proficiente com a programação orientada a objetos, e assim Scala foi fácil de escolher como uma linguagem, mas queria experimentar a programação funcional. Foi aí que ficou difícil. Eu simplesmente não consigo entender minha cabeça em um modo de funções de escrita. Como um programador funcional especialista, como você aborda um problema Dada uma lista de valores e um período de soma definido, como você geraria uma nova lista da média móvel simples da lista Por exemplo: Dado os valores da lista (2.0, 4.0 , 7.0, 6.0, 3.0, 8.0, 12.0, 9.0, 4.0, 1.0) e o período 4, a função deve retornar: (0.0, 0.0, 0.0, 4.75, 5.0, 6.0, 7.25, 8.0, 8.25, 6.5) Depois Passando um dia refletindo, o melhor que eu poderia encontrar em Scala era o seguinte: eu sei que isso é horrivelmente ineficiente. Eu prefiro fazer algo como: Agora, isso seria facilmente feito em um estilo imperativo, mas eu não posso para a vida De mim, saiba como expressar isso funcionalmente. Problema interessante. Posso pensar em muitas soluções, com vários graus de eficiência. Ter que adicionar coisas repetidamente não é realmente um problema de desempenho, mas vamos assumir que é. Além disso, os zeros no início podem ser antecipados mais tarde, então não nos preocupemos em produzi-los. Se o algoritmo fornece-los naturalmente, bem se não, corrigimos isso mais tarde. Começando com Scala 2.8, o seguinte daria o resultado para o período n gt usando o deslizamento para obter uma janela deslizante da Lista: No entanto, embora isso seja bastante elegante, ele não possui o melhor desempenho possível, porque não aproveita já Adições calculadas. Então, falando sobre eles, como podemos fazê-lo. Digamos que escrevemos isso: Temos uma lista da soma de cada dois pares. Vamos tentar usar esse resultado para calcular a média móvel de 4 elementos. A fórmula acima fez a seguinte computação: então, se tomarmos cada elemento e adicioná-lo ao segundo próximo elemento, obtemos a média móvel para 4 elementos: podemos fazê-lo assim: podemos calcular a média móvel para 8 elementos, e assim por diante. Bem, existe um algoritmo bem conhecido para calcular coisas que seguem esse padrão. É mais conhecido por seu uso na computação do poder de um número. É assim: então, vamos aplicá-lo aqui: então, ela é a lógica. O período 0 é inválido, o período 1 é igual à entrada, o período 2 é uma janela deslizante de tamanho 2. Se for maior que isso, pode ser igual ou impar. Se for estranho, adicionamos cada elemento ao MoveSum dos próximos elementos (ímpares 1). Por exemplo, se 3, adicionamos cada elemento ao MoveSum dos próximos 2 elementos. Se mesmo, calculamos o MoveSum para n 2. depois adicione cada elemento ao n 2 passos depois. Com essa definição, podemos voltar ao problema e fazer isso: há uma ligeira ineficiência com relação ao uso de. Mas é O (período), não O (valores. size). Pode ser mais eficiente com uma função recursiva da cauda. E, é claro, a definição de deslizamento que forneci é horrível desempenho-sábio, mas haverá uma definição muito melhor sobre isso no Scala 2.8. Observe que não podemos fazer um método de deslizamento eficiente em uma Lista. Mas podemos fazê-lo em um Iterable. Tendo dito tudo isso, Id com a primeira definição e otimize apenas se uma análise do caminho crítico identificou isso como um grande negócio. Para concluir, vamos considerar como eu fiz o problema. Temos um problema de média móvel. Uma média móvel é a soma de uma janela em movimento em uma lista, dividida pelo tamanho dessa janela. Então, primeiro, eu tento obter uma janela deslizante, somar tudo sobre isso, e depois dividir pelo tamanho. O próximo problema foi evitar a repetição de adições já computadas. Nesse caso, eu fui ao menor possível, e tentei descobrir como calcular maiores somas reutilizando esses resultados. Finalmente, vamos tentar resolver o problema do jeito que você achou, adicionando e subtraindo o resultado anterior. Obter a primeira média é fácil: agora fazemos duas listas. Primeiro, a lista de elementos a serem subtraídos. Em seguida, a lista de elementos a serem adicionados: podemos adicionar essas duas listas usando zip. Este método só produzirá tantos elementos quanto a menor lista, o que evita o problema de subtrair sendo maior do que o necessário: terminamos compondo o resultado com uma dobra: qual é a resposta a ser retornada. Toda a função parece assim: conheço Clojure melhor do que Scala, então aqui vai. Ao escrever isso, a outra entrada de Clojure aqui é imperativa, não é realmente o que você está procurando (e não é o Clojure idiomático). O primeiro algoritmo que vem à minha mente é tomar repetidamente o número solicitado de elementos da seqüência, descartando o primeiro elemento e recorrente. O seguinte funciona em qualquer tipo de seqüência (vetor ou lista, preguiçoso ou não) e dá uma sequência preguiçosa de médias --- o que pode ser útil se você estiver trabalhando em uma lista de tamanho indefinido. Observe que ele cuida do caso base, retornando nitidamente, se não houver elementos suficientes na lista para consumir. Executando isso em seus rendimentos de dados de teste. Não dá 0 para os primeiros elementos na seqüência, embora isso poderia ser facilmente manipulado (um pouco artificialmente). A coisa mais fácil de tudo é ver o padrão e ser capaz de lembrar uma função disponível que se encaixa na conta. A partição dá uma visão preguiçosa de porções de uma seqüência, que podemos então mapear: Alguém pediu uma versão recursiva da cauda, ​​a recursão da cauda, ​​a preguiça é um pouco de compensação. Quando seu trabalho é criar uma lista, então, tornar a sua função cauda recursiva geralmente é bastante simples, e isso não é exceção - basta criar a lista como um argumento para uma subfunção. Bem, acumule-se em um vetor em vez de uma lista porque, de outra forma, a lista será construída para trás e precisará ser revertida no final. Loop é uma maneira de fazer uma função interna anônima (tipo de como Schemes named let) recorrer deve ser usado em Clojure para eliminar chamadas de cauda. Conj é um meio generalizado. Acrescentando da maneira natural para a coleta --- o início das listas eo final dos vetores. Respondeu 24 de agosto às 2:58 I39ve decidiu adicionar a esse antigo Q, porque o tópico surgiu novamente (stackoverflowquestions2359821hellip) e acho preferível apontar para esta linda coleção de possíveis soluções ao mesmo tempo que adiciono minha própria tomada (o que é diferente de Versões anteriores em Clojure, como explicado na A). Talvez possamos construir o repositório mais completo das implementações de mov-avg funcionais da Web -) ndash Micha Marczyk 2 de março 10 às 0:20 Heres uma solução Haskell de uma linha parcialmente sem pontos: primeiro aplica caudas à lista para obter as listas de caudas , Então: Inverte e cai as primeiras entradas p (tomando p como 2 aqui): Caso você não esteja familiarizado com o símbolo (.) Dotnipple, é o operador para composição funcional, o que significa que ele passa a saída de uma função como a Entrada de outro, compondo-os em uma única função. (G. F) significa executar f em um valor, em seguida, passar a saída para g, então ((f. G) x) é o mesmo que (g (f x)). Geralmente, seu uso leva a um estilo de programação mais claro. Em seguida, mapeia a função (((deIntegral p)). Soma. Pegue p) na lista. Assim, para cada lista na lista, leva os primeiros elementos p, resume-os e, em seguida, divide-os por p. Então, basta virar a lista novamente com o inverso. Isso parece muito mais ineficiente do que o reverso, não invoca fisicamente a ordem de uma lista até que a lista seja avaliada, ela apenas acha na pilha (boa e voraz Haskell). As caudas também não criam todas essas listas separadas, ele apenas faz referência a diferentes seções da lista original. Ainda não é uma ótima solução, mas é uma linha longa :) É uma solução ligeiramente mais bonita, mas mais longa, que usa mapAccum para fazer uma subtração deslizante e adição: Primeiro dividimos a lista em duas partes em p, então: Soma o primeiro bit: Coloque o segundo bit com a lista original (isso apenas faz com que os itens sejam excluídos das duas listas). A lista original é, obviamente, mais longa, mas perdemos esse bit extra: agora definimos uma função para o nosso mapAccum (ulator). MapAccumL é o mesmo que o mapa, mas com um parâmetro de acumulação de estado de execução extra, que é passado do mapeamento anterior para o próximo, à medida que o mapa é executado pela lista. Usamos o acumulador como nossa média móvel, e como nossa lista é formada pelo elemento que acabou de sair da janela deslizante e do elemento que acabou de entrar (a lista que acabamos de fechar), nossa função deslizante tira o primeiro número x de A média e adiciona o segundo número y. Em seguida, passamos o novo s junto e retornamos s divididos por p. Snd (segundo) apenas leva o segundo membro de um par (tupla), que é usado para obter o segundo valor de retorno do mapAccumL, pois mapAccumL retornará o acumulador, bem como a lista mapeada. Para aqueles que não conhecem o símbolo. É o operador da aplicação. Ele realmente não faz nada além de ter uma prioridade de ligação associativa baixa e direita, por isso significa que você pode deixar de lado os suportes (pegue nota LISPers), ou seja, (fx) é o mesmo que fx Running (ma 4 2.0, 4.0, 7,0, 6,0, 3,0, 8,0, 12,0, 9,0, 4,0, 1,0) produz 4,75, 5,0, 6,0, 7,25, 8,0, 8,25, 6,5 para qualquer das soluções. Ah, e você precisará importar a lista do módulo para compilar qualquer uma das soluções. Daniel Obrigado Escrever o código é muito mais fácil do que explicá-lo -) Você descreveu a essência disso. Duas ListasStreams são mantidas em ambas as funções e retiram seu quotheadsquot durante cada iteração. One ListStream serve como a coleção principal para iterar, enquanto o outro ListStream, que é a mesma coleção, exceto que o tempo não é menor do que o dobrado, é usado no cálculo da nova média móvel. Ndash Walter Chang 24 de agosto de 09 às 17:19 A linguagem de programação J facilita programas como a média móvel. Na verdade, há menos caracteres em () do que em seu rótulo, média móvel. Para os valores especificados nesta questão (incluindo os valores do nome), aqui é uma maneira direta de codificar isso: podemos descrever isso usando rótulos para componentes. Ambos os exemplos utilizam exatamente o mesmo programa. A única diferença é o uso de mais nomes na segunda forma. Esses nomes podem ajudar os leitores que não conhecem as primárias J. Vamos olhar um pouco mais para o que está acontecendo no subprograma, a média. Denota soma () e denota divisão (como o sinal clássico). O cálculo de um recorde (contagem) de itens é feito por. O programa geral, então, é a soma dos valores divididos pelo valor dos valores: o resultado do cálculo da média móvel escrito aqui não inclui os zeros iniciais esperados na pergunta original. Esses zeros não são parte do cálculo pretendido. A técnica usada aqui é chamada de programação tácita. É praticamente o mesmo que o estilo livre de pontos de programação funcional. Respondeu 26 de agosto 10 às 16:15 Aqui está Clojure fingindo ser uma linguagem mais funcional. Isto é totalmente reverso-recursivo, btw, e inclui zeros à esquerda. Normalmente, coloco a coleção ou o último parâmetro da lista para tornar a função mais fácil de curry. Mas em Clojure. É tão complicado, geralmente acabo fazendo isso. Nesse caso, realmente não importa qual é a ordem dos parâmetros. Respondeu 24 de agosto 09 às 4:56 Oi Jonathan, eu sou muito novo para esta programação funcional, você poderia me explicar como isso é recursivo da cauda Obrigado ndash James P 24 de agosto 09 às 14:38 A recursão ocorre na declaração if, Onde qualquer opção é baseada em recur. Isso irá calcular todos os parâmetros primeiro, e apenas depois recurse. A resposta será o resultado de recorrer. Como o resultado é o mesmo resultado retornado pela recursão, sem outros cálculos, esta é a cauda recursiva. Ndash Daniel C. Sobral 24 de agosto de 09 às 15:20 Este exemplo faz uso do estado, uma vez que para mim é uma solução pragmática neste caso e um fechamento para criar a função de média de janelas: ainda é funcional no sentido de fazer uso De funções de primeira classe, embora não seja livre de efeitos colaterais. Os dois idiomas que você mencionou ambos são executados em cima da JVM e, portanto, ambos permitem gerenciamento de estado quando necessário. Respondeu 24 de agosto 09 às 1:55 Esta solução está em Haskell, que é mais familiar para mim: respondeu 24 de agosto de 09 às 10:23 Eu gosto do uso da declaração da partida. Tentei fazer algo parecido, mas não conseguiria fazer isso até lá. Ndash James P 24 de agosto de 09 às 14:39 Uma breve versão de Clojure que tem a vantagem de ser O (comprimento da lista), independentemente do seu período: isso explora o fato de que você pode calcular a soma de uma variedade de números criando uma soma cumulativa Da sequência (por exemplo, 1 2 3 4 5 - 0 1 3 6 10 15) e depois subtrair os dois números com um deslocamento igual ao seu período. Estando atrasado na festa e também a programação nova para a funcionalidade, cheguei a esta solução com uma função interna: adotei a ideia, para dividir toda a lista pelo período (len) com antecedência. Então eu gerar a soma para começar com os elementos de len-first. E ganho os primeiros elementos inválidos (0.0, 0.0.). Então, recursivamente, resumo o primeiro e adicione o último valor. No final, sinto-me lisonjeado com a coisa toda. Respondeu 29 de abril 10 às 19:28 No pseudocódigo de Haskell: (Agora um realmente deve abstrair os 4 para fora.) Respondeu Jul 23 13 às 13:45 A chave é a função da cauda, ​​que mapeia uma lista para uma lista de cópias do original Lista, com a propriedade de que o n-ésimo elemento do resultado está faltando os primeiros elementos n-1. Nós aplicamos fmap (avg. Take n) ao resultado, o que significa que nós levamos o prefixo n-length da sublista e calculamos seu valor médio. Se o comprimento da lista que somos avging não é n, então não calculamos a média (uma vez que é indefinido). Nesse caso, devolvemos nada. Se é, nós fazemos, e embrulhe-o em Just. Finalmente, corremos o CatMaybes no resultado do fmap (avg. Take n), para eliminar o tipo de talvez. Respondeu 21 de outubro às 13h29. Fiquei (surpreso e) desapontado com a performance do que me pareceu as soluções de Clojure mais idiomáticas, as soluções de preguiça-seq de JamesCunningham. Então, ela é uma combinação de solução de James com a idéia de DanielC. Sobral de adaptar a rápida expansão a somas em movimento: Editar: esta baseada na solução do mikera - é ainda mais rápida. Respondeu 22 de julho 13 às 19:21 Sua resposta 2017 Stack Exchange, Inc

No comments:

Post a Comment