Moléculas em Átomos

44

O desafio

Escreva um programa que possa quebrar uma fórmula química de entrada (veja abaixo) e produza seus respectivos átomos no formulário element: atom-count.


Entrada

Entrada de amostra:

H2O

Sua entrada sempre conterá pelo menos um elemento, mas não mais que dez. Seu programa deve aceitar entradas que contenham parênteses, que podem estar aninhados.

Os elementos nas seqüências sempre corresponderão [A-Z][a-z]*, o que significa que sempre começarão com uma letra maiúscula. Os números sempre terão um dígito.


Resultado

Saída de amostra (para a entrada acima):

H: 2
O: 1

Sua saída pode ser opcionalmente seguida por uma nova linha.


Quebrando Moléculas

Os números à direita de um conjunto de parênteses são distribuídos para cada elemento dentro:

Mg(OH)2

Saída deve:

Mg: 1
O: 2
H: 2

O mesmo princípio se aplica aos átomos individuais:

O2

Saída deve:

O: 2

E também encadeamento:

Ba(NO2)2

Saída deve:

Ba: 1
N: 2
O: 4

Exemplos

> Ba(PO3)2
Ba: 1
P: 2
O: 6

> C13H18O2
C: 13
H: 18
O: 2

> K4(ON(SO3)2)2
K: 4
O: 14
N: 2
S: 4

> (CH3)3COOC(CH3)3
C: 8
H: 18
O: 2

> (C2H5)2NH
C: 4
H: 11
N: 1

> Co3(Fe(CN)6)2
Co: 3
Fe: 2
C: 12
N: 12

As entradas são indicadas por uma seta (sinal maior que; >).

Placar

Para que sua pontuação apareça no quadro, ela deve estar neste formato:

# Language, Score

Ou se você ganhou um bônus:

# Language, Score (Bytes - Bonus%)

Editar: colchetes não fazem mais parte da pergunta. Todas as respostas postadas antes das 3:00 UTC, 23 de setembro, são seguras e não serão afetadas por esta alteração.

Zach Gates
fonte
Quais são as formas de entrada permitidas?
Oberon
1
@ZachGates É melhor apoiarmos também, mas lembre-se de que colchetes ainda estão incorretos. O AFAIK em fórmulas químicas entre colchetes é usado apenas para a concentração indicada. Por exemplo: [HCl] = 0.01 mol L^-1.
orlp 22/09/15
Eles são, mas, para todos os fins intensivos, também os vamos usar para agrupar. @orlp A menos que seja realmente um grande negócio; Nesse caso, removerei os suportes completamente.
Zach Gates
Veja a seção "Exemplos". Você está perguntando algo específico? As entradas @Oberon são indicadas por a >.
Zach Gates
1
Apenas uma observação, os exemplos ainda têm elementos com contagens de átomos com vários dígitos.
ProgrammerDan

Respostas:

11

CJam, 59 57 bytes

q{:Ci32/")("C#-"[ ] aC~* Ca C+"S/=~}%`La`-S%$e`{~": "@N}/

Experimente online no intérprete CJam .

Como funciona

q             e# Read all input from STDIN.
{             e# For each character:
  :Ci         e#   Save it in C and cast to integer.
  32/         e#   Divide the code point by 32. This pushes
              e#   2 for uppercase, 3 for lowercase and 1 for non-letters.
  ")("C#      e#   Find the index of C in that string. (-1 if not found.)
  -           e#   Subtract. This pushes 0 for (, 1 for ), 2 for digits,
              e#   3 for uppercase letters and 4 for lowercase letters.

 "[ ] aC~* Ca C+"

 S/           e#   Split it at spaces into ["[" "]" "aC~*" "Ca" "C+"].
 =~           e#   Select and evaluate the corresponding chunk.
              e#     (   : [    : Begin an array.
              e#     )   : ]    : End an array.
              e#     0-9 : aC~* : Wrap the top of the stack into an array
              e#                  and repeat that array eval(C) times.
              e#     A-Z : Ca   : Push "C".
              e#     a-z : C+   : Append C to the string on top of the stack.
}%            e#
`             e# Push a string representation of the resulting array.
              e# For input (Au(CH)2)2, this pushes the string
              e# [[["Au" [["C" "H"] ["C" "H"]]] ["Au" [["C" "H"].["C" "H"]]]]]
La`           e# Push the string [""].
-             e# Remove square brackets and double quotes from the first string.
S%            e# Split the result at runs of spaces.
$e`           e# Sort and perform run-length encoding.
{             e# For each pair [run-length string]:
  ~           e#   Dump both on the stack.
  ": "        e#   Push that string.
  @N          e#   Rotate the run-length on top and push a linefeed.
}/            e#
Dennis
fonte
10

Pitão, 66 65 bytes

VrSc-`v::z"([A-Z][a-z]*)""('\\1',),"",?(\d+)""*\\1,"`(k))8j": "_N

Porta da minha resposta Python. Só suporta entrada usando colchetes regulares.

orlp
fonte
3
+1. Três respostas em uma hora? Agradável.
Zach Gates
10

Python3, 157 154 bytes

import re
s=re.sub
f=s("[()',]",'',str(eval(s(',?(\d+)',r'*\1,',s('([A-Z][a-z]*)',r'("\1",),',input()))))).split()
for c in set(f):print(c+":",f.count(c))

Só suporta entrada usando colchetes regulares.

Antes de criar a solução golfed usando evalacima, criei esta solução de referência, que achei muito elegante:

import re, collections

parts = filter(bool, re.split('([A-Z][a-z]*|\(|\))', input()))
stack = [[]]
for part in parts:
    if part == '(':
        stack.append([])
    elif part == ')':
        stack[-2].append(stack.pop())
    elif part.isdigit():
        stack[-1].append(int(part) * stack[-1].pop())
    else:
        stack[-1].append([part])

count = collections.Counter()
while stack:
    if isinstance(stack[-1], list):
        stack.extend(stack.pop())
    else:
        count[stack.pop()] += 1

for e, i in count.items():
    print("{}: {}".format(e, i))
orlp
fonte
6

JavaScript ES6, 366 bytes

function f(i){function g(a,b,c){b=b.replace(/[[(]([^[(\])]+?)[\])](\d*)/g,g).replace(/([A-Z][a-z]?)(\d*)/g,function(x,y,z){return y+((z||1)*(c||1))});return(b.search(/[[(]/)<0)?b:g(0,b)}return JSON.stringify(g(0,i).split(/(\d+)/).reduce(function(q,r,s,t){(s%2)&&(q[t[s-1]]=+r+(q[t[s-1]]||0));return q},{})).replace(/["{}]/g,'').replace(/:/g,': ').replace(/,/g,'\n')}

JS Fiddle: https://jsfiddle.net/32tunzkr/1/

Tenho certeza de que isso pode ser reduzido, mas preciso voltar ao trabalho. ;-)

styletron
fonte
2
Tenho certeza de que também pode ser reduzido. Como você clama por usar o ES6, pode começar usando a notação de seta grande para criar funções. E a returndeclaração implícita . Isso deve ser suficiente por enquanto.
Ismael Miguel
Você também usa replacemuito para poder salvar alguns bytes usando xyz[R='replace'](...)a primeira vez e abc[R] (...)cada vez subseqüente.
DankMemes
6

SageMath , 156 148 bytes

import re
i=input()
g=re.sub
var(re.findall("[A-Z][a-z]?",i))
print g("(\d+).(\S+)\D*",r"\2: \1\n",`eval(g("(\d+)",r"*\1",g("([A-Z(])",r"+\1",i)))`)

Experimente on-line aqui (espero que o link funcione, talvez seja necessário ter uma conta on-line)

Nota: Se tentar online, será necessário substituí-lo input()pela string (por exemplo "(CH3)3COOC(CH3)3")

Explicação

O Sage permite simplificar expressões algébricas, desde que estejam no formato correto (consulte 'manipulação simbólica' deste link). As expressões regulares dentro de eval () servem basicamente para colocar a string de entrada no formato correto, por exemplo, algo como:

+(+C+H*3)*3+C+O+O+C+(+C+H*3)*3

eval()simplificará isso para: 8*C + 18*H + 2*Oe, então, é apenas uma questão de formatar a saída com outra substituição de regex.

Jarmex
fonte
5

Python 3, 414 bytes

Espero que a ordem do resultado não conte.

import re
t=input().replace("[", '(').replace("]", ')')
d={}
p,q="(\([^\(\)]*\))(\d*)","([A-Z][a-z]*)(\d*)"
for i in re.findall(q,t):t = t.replace(i[0]+i[1],i[0]*(1if i[1]==''else int(i[1])))
r=re.findall(p,t)
while len(r)>0:t=t.replace(r[0][0]+r[0][1],r[0][0][1:-1]*(1if r[0][1]==''else int(r[0][1])));r=re.findall(p,t)
for i in re.findall(q[:-5], t):d[i]=d[i]+1if i in d else 1
for i in d:print(i+': '+str(d[i]))
uno20001
fonte
5

Javascript (ES6), 286 284

Não muito mais curto que o outro ES6, mas dei o meu melhor. Nota: isso resultará em erro se você fornecer uma string vazia ou a maioria das entradas inválidas. Também espera que todos os grupos tenham uma contagem superior a 1 (ou seja, não CO[OH]). Se isso quebrar alguma regra de desafio, me avise.

a=>(b=[],c={},a.replace(/([A-Z][a-z]*)(?![0-9a-z])/g, "$11").match(/[A-Z][a-z]*|[0-9]+|[\[\(]/g).reverse().map(d=>(d*1==d&&b.push(d*1),d.match(/\(|\[/)&&b.pop(),d.match(/[A-Z]/)&&eval('e=b.reduce((f,g)=>f*g,1),c[d]=c[d]?c[d]+e:e,b.pop()'))),eval('g="";for(x in c)g+=x+`: ${c[x]}\n`'))

Usa uma abordagem baseada em pilha. Primeiro, ele processa previamente a string para adicionar 1a qualquer elemento sem um número, ou seja, Co3(Fe(CN)6)2torna-se Co3(Fe1(C1N1)6)2. Em seguida, ele percorre a ordem inversa e acumula a contagem de elementos.

a=>(
  // b: stack, c: accumulator
  b=[], c={},

  // adds the 1 to every element that doesn't have a count
  a.replace(/([A-Z][a-z]*)(?![0-9a-z])/g, "$11")

    // gathers a list of all the elements, counts, and grouping chars
    .match(/[A-Z][a-z]*|[0-9]+|[\[\(]/g)

    // loops in reverse order
    .reverse().map(d=>(

       // d*1 is shorthand here for parseInt(d)
       // d*1==d: true only if d is a number
       // if it's a number, add it to the stack
       d * 1 == d && b.push(d * 1),

       // if there's an opening grouping character, pop the last item
       // the item being popped is that group's count which isn't needed anymore
       d.match(/\(|\[/) && b.pop(),

       // if it's an element, update the accumulator
       d.match(/[A-Z]/) && eval('

         // multiplies out the current stack
         e = b.reduce((f, g)=> f * g, 1),

         // if the element exists, add to it, otherwise create an index for it
         c[d] = c[d] ? c[d] + e : e,

         // pops this element's count to get ready for the next element
         b.pop()
       ')
  )),

  // turns the accumulator into an output string and returns the string
  eval('
    g="";

    // loops through each item of the accumulator and adds it to the string
    // for loops in eval always return the last statement in the for loop
    // which in this case evaluates to g
    for(x in c)
      g+=x+`: ${c[x]}\n`
  ')
)

Violino

DankMemes
fonte
5

Perl, 177 172 bytes

Código de 171 bytes + parâmetro de linha de comando de 1 byte

Ok, então eu pode ter conseguido um pouco levado com regex em um presente ...

s/(?>[A-Z][a-z]?)(?!\d)/$&1/g;while(s/\(([A-Z][a-z]?)(\d+)(?=\w*\W(\d+))/$2.($3*$4).$1/e||s/([A-Z][a-z]?)(\d*)(\w*)\1(\d*)/$1.($2+$4).$3/e||s/\(\)\d+//g){};s/\d+/: $&\n/g

Exemplo de uso:

echo "(CH3)3COOC(CH3)3" | perl -p entry.pl
Jarmex
fonte
2

Mathematica, 152 bytes

f=TableForm@Cases[PowerExpand@Log@ToExpression@StringReplace[#,{a:(_?UpperCaseQ~~___?LowerCaseQ):>"\""<>a<>"\"",b__?DigitQ:>"^"<>b}],a_. Log[b_]:>{b,a}]&

O acima define uma função fque recebe uma string como entrada. A função pega a string e agrupa cada nome de elemento entre aspas e adiciona um operador de exponenciação de infix antes de cada número e interpreta a string como uma expressão:

"YBa2Cu3O7" -> ""Y""Ba"^2"Cu"^3"O"^7" -> "Y" "Ba"^2 "Cu"^3 "O"^7

Então ele pega o logaritmo disso e o expande (o mathematica não se importa, o que levar o logaritmo de :)):

Log["Y" "Ba"^2 "Cu"^3 "O"^7] -> Log["Y"] + 2 Log["Ba"] + 3 Log["Cu"] + 7 Log["O"]

e, em seguida, encontra todas as ocorrências de multiplicação de a Logpor um número e a analisa na forma de {log-argument, number}e as gera em uma tabela. Alguns exemplos:

f@"K4(ON(SO3)2)2"
K   4
N   2
O   14
S   4


f@"(CH3)3COOC(CH3)3"
C   8
H   18
O   2


f@"Co3(Fe(CN)6)2"
C   12
Co  3
Fe  2
N   12
LLlAMnYP
fonte
1

Java, 827 bytes

import java.util.*;class C{String[]x=new String[10];public static void main(String[]a){new C(a[0]);}C(String c){I p=new I();int[]d=d(c,p);for(int i=0;i<10;i++)if(x[i]!=null)System.out.println(x[i]+": "+d[i]);}int[]d(String c,I p){int[]f;int i,j;Vector<int[]>s=new Vector();while(p.v<c.length()){char q=c.charAt(p.v);if(q=='(')s.add(d(c,p.i()));if(q==')')break;if(q>='A'&&q<='Z'){f=new int[10];char[]d=new char[]{c.charAt(p.v),0};i=1;if(c.length()-1>p.v){d[1]=c.charAt(p.v+1);if(d[1]>='a'&&d[1]<='z'){i++;p.i();}}String h=new String(d,0,i);i=0;for(String k:x){if(k==null){x[i]=h;break;}if(k.equals(h))break;i++;}f[i]++;s.add(f);}if(q>='0'&&q<='9'){j=c.charAt(p.v)-'0';f=s.get(s.size()-1);for(i=0;i<10;)f[i++]*=j;}p.i();}f=new int[10];for(int[]w:s){j=0;for(int k:w)f[j++]+=k;}return f;}class I{int v=0;I i(){v++;return this;}}}

Repositório Git com fonte não- bloqueada (paridade não perfeita, não-suportado suporta números com vários caracteres).

Já faz um tempo, achei que eu daria alguma representação a Java. Definitivamente não vou ganhar nenhum prêmio :).

ProgrammerDan
fonte
1

ES6, 198 bytes

f=s=>(t=s.replace(/(([A-Z][a-z]?)|\(([A-Za-z]+)\))(\d+)/,(a,b,x,y,z)=>(x||y).repeat(z)))!=s?f(t):(m=new Map,s.match(/[A-Z][a-z]?/g).map(x=>m.set(x,-~m.get(x))),[...m].map(([x,y])=>x+": "+y).join`\n`)

Onde \nestá um caractere literal de nova linha.

Ungolfed:

function f(str) {
    // replace all multiple elements with individual copies
    // then replace all groups with copies working outwards
    while (/([A-Z][a-z]?)(\d+)/.test(str) || /\(([A-Za-z]+)\)(\d+)/.test(str)) {
        str = RegExp.leftContext + RegExp.$1.repeat(RegExp.$2) + RegExp.rightContext;
    }
    // count the number of each element in the expansion
    map = new Map;
    str.match(/[A-Z][a-z]?/g).forEach(function(x) {
        if (!map.has(x)) map.set(x, 1);
        else map.set(x, map.get(x) + 1);
    }
    // convert to string
    res = "";
    map.forEach(function(value, key) {
        res += key + ": " + value + "\n";
    }
    return res;
}
Neil
fonte
1

Pip , 85 77 + 1 = 78 bytes

Resposta não concorrente porque usa recursos de idioma mais recentes que o desafio. Toma a fórmula como um argumento da linha de comando e usa o -nsinalizador para formatar a saída corretamente.

Y(VaRl:`([A-Z][a-z]*)``"&"`R`\d+``X&`R`(?<=\d|")[("]``.&`l)u:UQyu.": ".Y_NyMu

Experimente online!

O principal truque é transformar a fórmula via substituições de expressões regulares em uma expressão Pip. Isso, quando avaliado, fará a repetição e resolverá os parênteses para nós. Depois, processamos um pouco para obter as contagens de átomos e formatar tudo corretamente.

Ungolfed, com comentários:

                         a is command-line arg (implicit)
l:`([A-Z][a-z]*)`        Regex matching element symbols
aR:l `"&"`               Replace each symbol in a with symbol wrapped in quotes
aR:`\d+` `X&`            Add X before each number
aR:`(?<=\d|")[("]` `.&`  Add . before ( or " if it's preceded by a digit or "
Y (Va)@l                 Eval result, findall matches of l, and yank resulting list into y
u:UQy                    Remove duplicates and store in u
u.": ".(_Ny M u)         Map function {a IN y} to u, returning list of element counts;
                           append this (with colon & space) itemwise to list of symbols
                         Print that list, newline-separated (implicit, -n flag)

Veja como a entrada Co3(Fe(CN)6)2é transformada:

Co3(Fe(CN)6)2
"Co"3("Fe"("C""N")6)2
"Co"X3("Fe"("C""N")X6)X2
"Co"X3.("Fe".("C"."N")X6)X2
CoCoCoFeCNCNCNCNCNCNFeCNCNCNCNCNCN

Então:

["Co" "Co" "Co" "Fe" "C" "N" "C" "N" "C" "N" "C" "N" "C" "N" "C" "N" "Fe" "C" "N" "C" "N" "C" "N" "C" "N" "C" "N" "C" "N"]
["Co" "Fe" "C" "N"]
[3 2 12 12]
["Co: 3" "Fe: 2" "C: 12" "N: 12"]
DLosc
fonte