Calcular a estimativa de entropia do histograma de uma string


19

Escreva um programa ou função que calcule a entropia de Shannon de uma determinada string.

Se uma string possui n caracteres, d caracteres distintos , x i é o i- ésimo caractere distinto e P (x i ) é a probabilidade desse caractere ocorrer na string, nossa estimativa de entropia de Shannon para essa string é dada por:

H = -n \ soma \ limites_ {i = 1} ^ d P (x_i) \ log_2 P (x_i)

Para a estimativa deste desafio, assumimos que a probabilidade de um caractere ocorrer em uma string é simplesmente o número de vezes que ocorre dividido pelo número total de caracteres.

Sua resposta deve ser precisa com pelo menos três dígitos após o período.


Casos de teste:

"This is a test.", 45.094
"00001111", 8.000
"cwmfjordbankglyphsvextquiz", 122.211
"             ", 0.0

Contrário de meus desafios habituais, este parece complicado, mas é realmente muito simples :)
orlp


É seguro assumir ASCII imprimível para a sequência de entrada?
AdmBorkBork

@ TimmyD No. Qualquer string que o tipo de string do seu idioma suporte.
orlp

Infelizmente, o Mathematica Entropyconta bits por caractere, não total para a string; oh bem ...
2012rcampion

Respostas:


2

Geléia, 11 8 bytes

ċЀ÷Ll.S

Experimente online!


Posso perguntar como você insere esses caracteres? Com copiar e colar?
Bálint

Pelo menos no Linux, todos eles podem ser digitados no teclado internacional dos EUA.
Dennis

11

Python 3.3 ou superior, 64 bytes

import math
lambda s:sum(math.log2(len(s)/s.count(c))for c in s)

Obtido da soluçãomath.log2 de mbomb007 .


Então, o @orlp não nos deu uma fórmula totalmente simplificada, eh ...?
mbomb007

@ mbomb007 Depende de qual propósito você está simplificando. Escrever em termos de probabilidades e caracteres distintos é natural como definição, mas para o golfe é mais curto trabalhar com contagens e iterar sobre todos os caracteres.
Xnor

11
Pyth, responda com sua fórmula: pyth.herokuapp.com/… 8 bytes
Maltysen

2

APL, 18 14 bytes

+/2⍟≢÷(+/∘.=⍨)

Este é um trem de função monádico e sem nome que aceita uma string à direita e retorna um real.

Como todas as coisas boas da vida, isso usa a fórmula do xnor . Nós obtemos uma matriz de booleanos correspondentes às ocorrências de cada caractere na string usando ∘.=⍨, soma isso ao longo do primeiro eixo ( +/) para obter o número de ocorrências de cada caractere, dividimos o comprimento da string por cada um e, em seguida, assumimos a base de log 2 ( 2⍟) e soma.

Experimente aqui

Economizou 4 bytes graças a Dennis!



1

JavaScript (ES6), 67 bytes

s=>[...s].map(c=>t+=Math.log2(s.length/~-s.split(c).length),t=0)&&t

Eu preciso usar ~-s.splitporque isso aceita seqüências de caracteres em vez de regexps. Como sempre, mapsupera reduceum byte.

s=>[...s].reduce((t,c)=>t+Math.log2(s.length/~-s.split(c).length),0)

1

Perl 5, 58 bytes

Uma sub-rotina:

{for$a(@a=split'',pop){$t+=(log@a/grep/\Q$a/,@a)/log 2}$t}

Uma dica do meu chapéu para xnor para a fórmula.


-Fnão funciona (no Strawberry, de qualquer maneira) porque inclui o $/.
Msh210

1

MATL , 14 bytes

!Gu=stGn/Zl*s|

Experimente online!

!      % transpose implicit input into column vector
Gu     % row vector with unique elements of input
=      % test for equality, element-wise with broadcast
s      % sum of each column
tGn/   % duplicate. Divide by number of input characters
Zl     % binary logarithm
*      % element-wise multiplication
s      % sum of array
|      % absolute value. Display implicitly


1

J - 18 16 14 bytes

1#.2^.#%1#.=/~

Encurtado usando a idéia no método de Dennis.

Uso

   f =: 1#.2^.#%1#.=/~
   f 'This is a test.'
45.0936
   f '00001111'
8
   f 'cwmfjordbankglyphsvextquiz'
122.211
   f '             '
0

Explicação

1#.2^.#%1#.=/~  Input: string S
           =/~  Create a table testing for equality
        1#.     Convert each row from a list of base 1 digits to decimal
                This is equivalent to taking the sum and forms a list of tallies
      #         Get the length of S
       %        Divide the length by each tally
   2^.          Log base 2 of each
1#.             "Sum" those values and return

11
Eu não acho que isso conta como uma função. Se você atribuir o código a uma variável, ele fará algo completamente diferente.
Dennis

@ Dennis Pelo que eu entendi, parece que J o interpreta como uma cadeia de composições, usando 3 : '... y'a mesma sintaxe seria uma maneira válida de defini-lo como uma função. J afirma que é avaliado da direita para a esquerda, então refatorei meu código como um trem. Não gosto de bonés, [:mas não encontro outra maneira de fazer um trem.
miles


0

Jolf, 26 bytes

_*liuΜGμiEd*γ/l miLeHlimzγ

Experimente aqui!(Observe que a função da suíte de testes é acionada.)

Explicação

_*liuΜGμiEd*γ/l miLeHlimzγ
       μi                   unique members of i
      G  E                  split on ""
     Μ    d                 map over function
               _miLeH       match i with regex escaped member
             /l      li     divide length of (^) by length of i
            γ               γ = (^)
           *           mzγ  (^) * log_2(γ)
 *li                        (^) * length of i
_                           negate

0

Python 3.3 ou superior, 95 91 89 85 bytes

Solução simples. É necessário usar a versão 3.3 math.log2.

import math
def f(s):C=s.count;return-sum(C(x)*math.log2(C(x)/len(s))for x in set(s))

Experimente online


Você acha que há algo desnecessário aqui? n*sum(s.count(c)/n
Orlp 25/04/19

@orlp Obrigado. Originalmente, eu tinha uma função separada para encontrar a probabilidade, mas a colei duas vezes e a excluí para salvar os caracteres.
mbomb007

Você não precisa armazenar numa variável agora que a usa apenas uma vez.
Maltysen

0

Java 7, 207 bytes

double C(String x,Map<Character,Integer>f){double H=0,g;for(char c:x.toCharArray())f.put(c,f.containsKey(c)?f.get(c)+1:1);for(char c:f.keySet()){g=f.get(c);H+=g*Math.log(g/x.length())/Math.log(2);}return-H;}

Teste detalhado online

double log2(double d) { return Math.log(d) / Math.log(2); }

double C(String x, Map<Character,Integer>f)
{
    double H=0,g;

    // frequency
    for(char c : x.toCharArray())
    {
        f.put(c, f.containsKey(c) ? f.get(c)+1 : 1);
    }

    // calculate entropy
    for(char c : f.keySet())
    {
        g = f.get(c);
        H += g * log2(g / x.length());
    }

    return -H;
}

0

Fator, 98 bytes

[ [ length ] [ dup [ [ = ] curry dupd count ] { } map-as nip ] bi [ / log 2 log / ] with map sum ]

Esta é uma tradução direta desta resposta em Python . Vou adicionar uma explicação durante o jantar.


0

Raquete, 130 bytes

: c

#lang racket
(require math)(λ(S)(let([s(string->list S)])(sum(map(λ(c)(/(log(/(length s)(count(λ(x)(char=? c x))s)))(log 2)))s))))

Tradução da minha resposta ao fator, por isso é uma tradução indireta da resposta em Python de Kenny Lau.


0

k (32 bytes)

{-+/c*(log c%n:+/c:#:'=x)%log 2}

Ou então q, a tradução não é tão curta, mas mais clara:

{neg sum c*2 xlog c%n:sum c:count each group x}

0

Mathematica, 45 bytes

Tr[Log[2,Tr@#/#]#]&@Values@CharacterCounts@#&

Uso

Como retorna resultados exatos, aproximamos-os com N.

  f = Tr[Log[2,Tr@#/#]#]&@Values@CharacterCounts@#&
  f["This is a test."]//N
45.0936
  f["00001111"]//N
8.
  f["cwmfjordbankglyphsvextquiz"]//N
122.211
  f["             "]//N
0.

0

R, 67 bytes

l=length(i<-strsplit(readline(),"")[[1]]);-sum(log2(l/table(i)[i]))

Explicação

Pegue a entrada do stdin e divida-a em uma lista de caracteres. (Essa sintaxe desajeitada é a razão pela qual os desafios do golfe com cordas são tão difíceis em R ...)

         i<-strsplit(readline(),"")[[1]])

Essa atribuição está oculta dentro de um lengthcomando, portanto, temos duas atribuições pelo preço de um. Temos ia lista de caracteres e lseu comprimento.

l=length(i<-strsplit(readline(),"")[[1]]);

Agora calculamos a entropia. R tem uma função interessante tableque retorna as contagens de todos os valores exclusivos. Para entrada This is a test, table(i)retorna

> table(i)
i
  . a e h i s t T 
3 1 1 1 1 2 3 2 1

Isso é indexado por caracteres, o que é bom, pois podemos usar icomo índice para obter a contagem de cada caractere, assim:

> table(i)[i]
i
T h i s   i s   a   t e s t . 
1 1 2 3 3 2 3 3 1 3 2 1 3 2 1 

O restante do código é, então, uma implementação simples da fórmula de entropia, revertida um pouco.

                                           -sum(log2(l/table(i)[i]))

Guardar dois bytes (também sua submissão não funciona em TIO)
Jayce


0

C #, 159 bytes

Golfe:

string f(string s){var l=s.Length;double sum=0;foreach(var item in s.GroupBy(o=>o)){double p=(double)item.Count()/l;sum+=p*Math.Log(p,2);}return(sum*=-l)+"";}}

Ungolfed:

string f(string s)
{
  var l = s.Length;
  double sum = 0;
  foreach (var item in s.GroupBy(o => o))
  {
    double p = (double)item.Count() / l;
    sum += p * Math.Log(p, 2);
  }
  return (sum *= -l) + "";
}

Teste:

var codeGolf = new StringHistogramEntropyEstimation();
    Console.WriteLine(codeGolf.f("This is a test.")); //45.0935839298008
    Console.WriteLine(codeGolf.f("00001111")); //8
    Console.WriteLine(codeGolf.f("cwmfjordbankglyphsvextquiz")); //122.211432671668
    Console.WriteLine(codeGolf.f("             ")); //0

0

Groovy, 100 bytes

{a->n=a.size();a.toList().unique().collect{p=a.count(it)/n;p*(Math.log(p)/Math.log(2.0f))}.sum()*-n}

Testes:

This is a test. = 45.09358393449714
00001111 = 8.0
cwmfjordbankglyphsvextquiz = 122.21143275636976
aaaaaaaa = -0.0
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.