Compactar a string de consulta
Introdução
Apenas nas notícias do Hacker, John Resig pensa em transformar uma consulta “foo = 1 & foo = 2 & foo = 3 & blah = a & blah = b” em uma que se pareça com: “foo = 1,2,3 & blah = a, b", https://johnresig.com/blog/search-and-dont-replace/. Ele afirma "ser 10 linhas mais curto do que a solução de Mark".
Strings de consulta consistem em sequências de pares nome-valor. Os pares nome-valor consistem em um nome e valor, separados por =. nomes e valores são possivelmente sequências vazias de caracteres alfanuméricos. Os pares nome-valor são separados pelo caractere & na sequência. Os valores são exclusivos para cada nome.
Desafio
10 linhas mais curtas do que a solução de Mark não é suficiente.
- Leia uma string de consulta.
- Combine pares de valores de nomes com nomes iguais em um único par de valores de nomes, com valores concatenados e separados por vírgula.
- Produza a string de consulta combinada, com pares nome-valor na ordem em que os nomes são encontrados da esquerda para a direita na string de consulta.
Este é o código de golfe, as lacunas padrão estão fechadas.
Exemplo de entrada e saída
Entrada:
foo = 1 & foo = & blah = a & foo = 3 & bar = x & blah = b & = 1 & = 2
Resultado:
foo = 1,, 3 & blah = a, b & bar = x & = 1,2
Entrada:
foo = bar & foo = foo
Resultado:
foo = bar, foo
Respostas
05AB1E , 23 bytes
'&¡'=δ¡.¡н}εø€Ù}…,=&vyý
Experimente online ou verifique todos os casos de teste .
Explicação:
'&¡ '# Split the (implicit) input-string on "&"
δ # For each inner string:
'= ¡ '# Split it on "="
.¡ } # Group all pairs by:
н # Their first value
ε # Map over each group of pairs:
ø # Zip/transpose, swapping rows/columns
€ # For both inner lists:
Ù # Uniquify it
}…,=& # After the map: push string ",=&"
v # Pop and loop over each character `y`:
yý # Join the inner-most list of strings with `y` as delimiter
# (after the loop, the result is output implicitly)
Experimente online com uma saída passo a passo.
JavaScript (ES6), 103 99 87 86 bytes
Salvo 1 byte graças a @MatthewJensen
s=>Object.values(o=/(\w*=)(\w*)/g,s.replace(o,(s,k,v)=>o[k]=o[k]?o[k]+[,v]:s)).join`&`
Experimente online!
Comentou
s => // s = query string
Object.values( // get the values of ...
o = // ... the object of this regular expression, which is
/(\w*=)(\w*)/g, // re-used to store the keys and values of the query
s.replace( // match each key with the '=' sign and the corresponding
o, // value, using the regular expression defined above
(s, k, v) => // for each matched string s, key k and value v:
o[k] = // update o[k]:
o[k] ? // if it's already defined:
o[k] + // get the current value
[, v] // append a comma, followed by v
: // else:
s // set it to the entire matched string
// (key, '=', first value)
) // end of replace()
).join`&` // end of Object.values(); join with '&'
JavaScript, 263 201 196 194 190 189 188 173 161 bytes
q=>{w={};q.split`&`.map(x=>{y=x.split`=`;if(!w[y[0]])w[y[0]]=[];w[y[0]].push(y[1])});z=`${Object.entries(w).map(a=>a=[a[0]+'='+a[1].join`,`]).join`&`}`;return z}
Experimente online
Julia 1.0 , 106 bytes
f(a,z=split.(split(a,'&'),'='),u=first.(z))=join((i*'='*join(last.(z)[u.==i],',') for i in unique(u)),"&")
Experimente online!
Python 3.8 (pré-lançamento) , 116 bytes
lambda s:(a:=[k.split("=")for k in s.split("&")])and"&".join(b+"="+",".join(d for c,d in a if c==b)for b in dict(a))
Experimente online!
-46 bytes graças ao ovs
-1 byte graças a Jonathan Allan (em Py 3.8 PR, com morsa)
Ruby , 88 80 76 64 bytes
->s{s.scan(/(\w*=)(\w*)/).group_by(&:shift).map{|k,v|k+v*?,}*?&}
Experimente online!
-8 bytes graças a ovs por apontar, eu poderia atribuir um lambda a uma variável
-12 bytes graças a Dingus!
Python 3 , 94 bytes
lambda s:'&'.join(k+'='+",".join(v)for k,v in parse_qs(s,1).items())
from urllib.parse import*
Experimente online!
Algumas coisas:
- Isso só funciona porque o Python 3.6+ mantém a ordem do dicionário com base no tempo de inserção. Adoraríamos usar o Python 2 para uma importação mais curta (
urlparsevsurllib.parse), mas os dicionários não estão ordenados corretamente. - Por mais que eu ame f-strings,
k+'='+",".join(v)é mais curto quef'{k}={",".join(v)}'. - O duplo tem
.joincheiro de golfe, mas não consigo encontrar um caminho mais curto.
Python 3 , 95 bytes
from urllib.parse import*
d=parse_qs(input(),1)
print('&'.join(k+'='+','.join(d[k])for k in d))
Experimente online!
Jelly , 28 bytes
-2 Graças a Zgarb!
ṣ”&ṣ€”=Ṗ€ĠṢịƲµZQ€j€”,j”=)j”&
Um Link monádico que aceita e produz uma lista de personagens.
Experimente online!
Quão?
ṣ”&ṣ€”=Ṗ€ĠṢịƲµZQ€j€”,j”=)j”& - Link: list of characters, S
ṣ”& - split at '&'s
ṣ€”= - split each at '='s
call that A
Ʋ - last four links as a monad - f(A):
Ṗ€ - all but last of each
Ġ - group indices by their values
Ṣ - sort (since Ġ orders by the values, not the indices)
ị - index into (A) (vectorises)
µ ) - for each:
Z - transpose
Q€ - deduplicate each
j€”, - join each with ','s
j”= - join with '='s
j”& - join with '&'s
05AB1E , 62 51 bytes
(-11 de @kevin)
'&¡'=δ¡D€нÙÐV_UsvYyнk©Xsèyθ',««Xs®ǝU}X妨}Yζí'=ý'&ý
Experimente online!
Minha abordagem 62:
'&¡ε'=¡}D€нÙ©DgÅ0Usvy¬®skDVXsèyθ',««XsYǝU}X妨}®ζεć'=s««˜}˜'&ý
Explicação:
'&¡ε'=¡}D€нÙ©DgÅ0Usvy¬®skDVXsèyθ',««XsYǝU}X妨}®ζεć'=s««˜}˜'&ý
'&¡ split by &
ε'=¡} foreach: split by =
D duplicate
€н foreach: push header (get the keys list)
Ù uniquify
© save in register c
Dg suplicate and get the length of that list of keys
Å0 create a list of 0's with the length above
U save in variable X
svy } for each set of key-value
¨sk find the index of that key in the keys list
DV save the index in variable y
Xsè get the current value of the element of X at index Y (in X we are keeping the concatenation of the values for key i)
yθ extract the tail of the element in this iteration (a value to concatenate)
',«« concatenate with , in between
XsYǝU update X with the new value of the element representing the key
X妨} remove tail and head from each element of X (removing the trailing , and leading 0)
® push back the list of keys
ζ zip (list of keys and list of merged values)
εć'=s««˜} foreach element in the zipped list, join with = in between such that the result is "key=values"
˜ flat
'&ý join with &
Experimente online!
JavaScript, 79 bytes
f=
s=>(s=new URLSearchParams(s)).forEach((v,k)=>s.set(k,s.getAll(k)))||unescape(s)
<input oninput=o.textContent=f(this.value)><pre id=o>
Se E / S pudessem ser strings de consulta reais de acordo com a especificação WHATWG, em vez de strings inválidas que parecem strings de consulta, mas não são codificadas por URL corretamente, 7 bytes poderiam ser salvos pela string do resultado em vez de removê-lo.
JavaScript, 106 104 76 bytes
Solução Retina do Porto de Neil , publicada com permissão.
f=q=>q==(q=q.replace(/(?<=^|&)((\w*=)[^&]*)(.*?)&\2(\w*)/,`$1,$4$3`))?q:f(q)
Experimente online!
Original
Um pouco de golfe bêbado, no qual voltei a trabalhar sóbrio, mas no processo localizei a solução de Arnauld e percebi que estava no caminho para algo quase idêntico, então deixei como estava.
q=>Object.keys(o={},q.split`&`.map(p=>o[[k,v]=p.split`=`,k]=[...o[k]||[],v])).map(x=>x+`=`+o[x]).join`&`
Experimente online!
Retina 0.8.2 , 45 bytes
+1`(?<=^|&)((\w*=)[^&]*)(.*?)&\2(\w*)
$1,$4$3
Experimente online! Explicação: Corresponde repetidamente a primeira chave duplicada e sua primeira duplicata e une o valor ao da chave original.
Japt , 29 28 27 26 24 bytes
q& móÈk¶
ü@bøXÎîÕvÎqÃq&
Tente
Economizei 2 bytes graças a alguma inspiração de Kevin .
(Oh, se Japt tivesse um método apenas para agrupar, em vez de classificar, isso poderia ser 19 bytes .)
Explnataion
q& móÈk¥\nü@bøXÎîÕvÎqÃq& :Implicit input of string U
q& :Split on "&"
m :Map
ó : Partition after each character that returns falsey (i.e., an empty string)
È : When passed through the following function
k : Remove all characters that appear in
¥ : Literal "=="
\n :Reassign to U
ü :Group & sort by
@ :Passing each X through the following function
b : First index in U
ø : That contains
XÎ : First element of X
à :End grouping
® :Map each Z
Õ : Transpose
v : Map first element to
Î : Get first element
q : Join resulting array
à :End map
q& :Join with "&"
Ou, para fornecer uma explicação passo a passo:
Entrada
"foo=1&foo=&blah=a&foo=3&bar=x&blah=b&=1&=2"
Dividido
["foo=1","foo=","blah=a","foo=3","bar=x","blah=b","=1","=2"]
Mapa e partição
[["foo=","1"],["foo="],["blah=","a"],["foo=","3"],["bar=","x"],["blah=","b"],["=","1"],["=","2"]]
Agrupar e classificar
[[["foo=","1"],["foo="],["foo=","3"]],[["blah=","a"],["blah=","b"]],[["bar=","x"]],[["=","1"],["=","2"]]]
Mapa e ...
Transpor
[[["foo=","foo=","foo="],["1",null,"3"]],[["blah=","blah="],["a","b"]],[["bar="],["x"]],[["=","="],["1","2"]]]
Mapeie o primeiro elemento para seu primeiro elemento
[["foo=",["1",null,"3"]],["blah=",["a","b"]],["bar=",["x"]],["=",["1","2"]]]
Junte-se
["foo=1,,3","blah=a,b","bar=x","=1,2"]
Junte-se
"foo=1,,3&blah=a,b&bar=x&=1,2"
Awk , 144 \$\cdots\$144 138 byte
Adicionados 6 bytes para corrigir um bug.
Adicionados 4 bytes para corrigir um bug gentilmente apontado e resolvido por Dominic van Essen .
Economizei 6 bytes graças a Dominic van Essen !!!
BEGIN{RS="&"}{if(!(j=i[$1]++))n[m++]=$1;a[$1][j]=$2}END{for(l in n){k=n[l];o=k"=";for(j in a[k])o=o (j>0?",":"")a[k][j];printf z o;z="&"}}
Experimente online!
R , 195 bytes
{Z=pryr::f
`/`=Z(a,b,el(regmatches(a,gregexpr(b,a))))
`-`=Z(a,b,paste(a,collapse=b))
Z(S,{L=S/'\\w*='
L=factor(L,unique(L))
U=tapply(S/'=\\w*',L,Z(a,trimws(a,,'=')-','))
paste0(names(U),U)-'&'})}
Experimente online!
Lua , 162 bytes
l,t={},{}(...):gsub('(%w-)=(%w-)',load"k,v=...o=t[k]l[#l+1]=not o and k or _ t[k]=o and o..','..v or v")for i=1,#l do io.write(i>1 and'&'or'',l[i],'=',t[l[i]])end
Experimente online!
Este é um longo, hein. Poderia ter sido feito muito mais curto se não fosse pelo pedido de recompensa.
Explicação:
l,t={},{} -- list (by first inclusion), table (keys to string)
-- (ab)use string replacement function to callback over matches in input string
(...):gsub(
-- Match key-value pairs
'(%w-)=(%w-)',
-- For each pair, run callback (braces are replaced for multiline)
load[[
k,v=... -- Assign key, value
o=t[k] -- Look for already stored string if any
l[#l+1]=not o and k or _ -- If key is new, store it in list
t[k]=o and o..','..v or v -- Append to string if it is not new, store it if it is
]]
)
-- For every record in list
for i=1,#l do
-- Write with no newlines
io.write(
i>1 and'&'or'', -- Output & before all values but first
l[i],'=',t[l[i]] -- Print key-value pair
)
end
Linguagem Wolfram (Mathematica) , 86 bytes
StringRiffle[Last@Reap[Sow@@@StringExtract[#,"&"->;;,"="->{2,1}],_,List],"&","=",","]&
Experimente online! Função pura, recebe uma string como entrada e retorna outra string como saída. Isso é inspirado e muito semelhante à resposta de att , mas usa um algoritmo semelhante ao da postagem do blog (aqui utilizando Sow/ Reap). Aqui está um exemplo de como as subexpressões são avaliadas em uma entrada de "foo=1&bar=a&foo=":
StringExtract[#,"&"->;;,"="->{2,1}] == {{"1", "foo"}, {"a", "bar"}, {"", "foo"}}
Sow@@@... == {"1", "a", ""}; {"1", ""} sown w/ tag "foo"; {"a"} sown w/ tag "bar"
Reap[...,_,List] == {{"1", "a", ""}, {{"foo", {"1", ""}}, {"bar", {"a"}}}}
Last@... == {{"foo", {"1", ""}}, {"bar", {"a"}}}
StringRiffle[...,"&","=",","] == "foo=1,&bar=a"
Fator , 229 bytes
: c ( s -- s s ) 1 <hashtable> swap "&"split [ "="split ] map
[ [ dup [ last ] dip first pick push-at ] each ]
[ [ first ] map dup union ] bi dup [ [ over at ","join ] map ] dip
[ "="append ] map swap zip [ concat ] map "&"join ;
Experimente online!
É longo, mas estou um tanto satisfeito com ele :)
SNOBOL4 (CSNOBOL4) , 243 221 212 bytes
Q =INPUT
T =TABLE()
N Q (ARB '=') . L ARB . V ('&' | RPOS(0)) REM . Q :F(O)
T<L> =T<L> ',' V :(N)
O R =CONVERT(T,'ARRAY')
I X =X + 1
R<X,2> ',' REM . V :F(P)
O =O '&' R<X,1> V :(I)
P O '&' REM . OUTPUT
END
Experimente online!
TABLEno SNOBOL é estranho. É perfeitamente aceitável aceitar PATTERNlike ARBcomo chave, mas não a string vazia ''. No entanto, usar <label>=como rótulo em vez de <label>resolver esse problema de maneira organizada.
Explicação para uma iteração anterior:
E =RPOS(0) ;* alias for end of string
A =ARB ;* alias for ARBitrary match (as short as possible)
Q =INPUT ;* read input
T =TABLE() ;* create a TABLE (a dictionary)
N Q A . L '=' A . V ('&' | E) REM . Q :F(O) ;* in regex land, this is something like
;* '(.*=)(.*)(&|$)' where you save \1 and \2 as L and V, respectively. If there's no match, goto O
T<L> =T<L> V ',' :(N) ;* update the values list, then goto N
O R =CONVERT(T,'ARRAY') ;* convert T to a 2D array of [Label,Value]
I X =X + 1 ;* increment array index
R<X,2> A . V ',' E :F(P) ;* remove the trailing ',' from the value list. If X is out of bounds, goto P
O =O R<X,1> V '&' :(I) ;* Append L and V to O with an '=' and '&', then goto I
P O A . OUTPUT '&' E ;* Print everything except for the trailing '&'
END
PHP , 146 bytes
<?=parse_str(str_replace('=','_[]=',$argv[1]),$a)??join('&',array_map(function($b,$c){return rtrim($b,'_').'='.join(',',$c);},array_keys($a),$a));
Experimente online! Explicação: parse_strnão foi projetado para lidar com valores repetidos, mas você pode persuadi-lo nomeando cada valor com um final []. Ele também não foi projetado para lidar com nomes vazios, mas como estou anexando de []qualquer maneira, também posso adicionar um _para satisfazer esse caso. Tendo analisado a string de consulta, resta juntar tudo novamente.
Vermelho , 221 bytes
func[s][m: copy #()s: split s"&"forall s[s/1: split s/1"="append s/1/1"="put m s/1/1
copy""]foreach v s[m/(v/1): append m/(v/1) rejoin[v/2","]]t: copy""foreach k keys-of
m[take/last m/:k repend t[k m/:k"&"]]take/last t t]
Experimente online!
É terrivelmente longo e vou tentar jogar golfe pelo menos um pouco. Que pena que o Red não tem uma joinfunção útil ...
Carvão , 42 bytes
≔E⪪S&⪪ι=θW⁻Eθ§κ⁰υ⊞υ§ι⁰⪫Eυ⁺⁺ι=⪫EΦθ¬⌕λι⊟λ,¦&
Experimente online! O link é para a versão detalhada do código. Explicação:
≔E⪪S&⪪ι=θ
Divida a entrada em &s e divida cada token em =s.
W⁻Eθ§κ⁰υ⊞υ§ι⁰
Crie uma lista de chaves exclusivas na ordem de sua primeira aparição.
⪫Eυ⁺⁺ι=⪫EΦθ¬⌕λι⊟λ,¦&
Para cada chave, extraia e junte os valores com ,, concatene com a chave e o separador e junte o resultado geral com &.
C (gcc) , 319 304 bytes
Agradecimentos ao roofcat pelas sugestões.
Esta função verifica cada par tokenizado (nome, valor) e adiciona uma entrada de lista para cada novo nome encontrado e, em seguida, anexa uma entrada de lista para cada valor. Depois de construir as listas, ele itera através de cada lista e imprime os valores. Para economizar espaço, achatei as estruturas em matrizes de void *.
f(s,t,v,i)char*s,*t;{void*d[3]={t=0},**f,**w;for(;t=strtok(t?0:s,"&");*w=calloc(8,2),w[1]=v){t[i=strcspn(t,"=")]=0;v=t-~i;for(f=&d;strcmp(f[1]?:t,t);f=*f);for(w=f[2]=f[1]?f[2]:(f[1]=t,*f=calloc(8,5))+24;w[1];w=*w);}for(f=&d;i=*f&&printf("&%s="+!!s,f[1]);f=*f)for(w=f[2];s=*w;w=s)i=!printf(",%s"+i,w[1]);}
Experimente online!
Versão sem golfe do envio original:
struct list {
struct list *next;
char *name;
struct list *value;
};
void f(char *s) {
char *tok=NULL, *value;
struct list d={}, *e, *v;
int i;
for(; tok=strtok(tok?NULL:s, "&"); ) {
tok[i=strcspn(tok, "=")]=0;
value=tok+i+1;
for(e=&d; e->name && strcmp(e->name, tok); e=e->next);
if(!e->name) {
e->next=calloc(sizeof(struct list), 2);
e->name=tok;
e->value=e->next+1;
}
for(v=e->value; v->name; v=v->next);
v->next=calloc(sizeof(struct list), 1);
v->name=value;
}
for(e=&d; e->next; e=e->next, s=0) {
printf("&%s="+!!s, e->name);
for(v=e->value, i=1; v->next; v=v->next, i=0)
printf(",%s"+i, v->name);
}
}
Experimente online!
Wolfram Language (Mathematica) , 90 88 bytes
StringRiffle[S=StringSplit;List@@@Normal@Merge[Rule@@@S[#~S~"&","=",2],#&],"&","=",","]&
Experimente online!
-2 graças a LegionMammal978
S=StringSplit;
Rule@@@S[#~S~"&","=",2] (* Convert to a list of Rules *)
Merge[ % ,#&] (* Combine rules into an Association, leaving values unchanged *)
Normal@ % (* Convert Association back into a list of Rules, *)
List@@@ % (* and turn Rules into Lists *)
StringRiffle[ % ,"&","=",","] (* Concatenate, using "&", "=", and "," as separators *)
Perl 5 , -pF\&sinalizadores, 73 57 bytes
Usos -pF\&de varrer as entradas e autosplit on &.
Resultados não ordenados, em competição paralela.
/=/,$z{$`}.=$z{$`}?",$'":"$`=$'"for@F;$_=join'&',values%z
Experimente online!
Usa um hash %zpara controlar os valores de nomes individuais e imprime todos no final. -16 bytes graças a NahuelFouilleul.
R , 174 166 bytes
function(s,S=strsplit,A=sapply,P=paste,e=A(S(P(el(S(s,'&')),'=',sep=''),'='),c),n=unique(m<-e[1,]))P(n,A(n,function(x)P(e[2,m==x],collapse=',')),sep='=',collapse='&')
Experimente online!
Por alguma razão inexplicável, pensei que esse desafio não sofreria com o manuseio terrivelmente prolixo de R nas cordas.
Isso acabou não sendo o caso, pelo menos com base na minha tentativa até agora ...
Comentado antes de jogar golfe:
compactify=
function(s, # s = string to compactify
S=strsplit, # S = alias to strsplit() function
A=sapply, # A = alias to sapply() function
P=paste, # P = alias to paste() function
a=el( # a = the first element of ...
S(s,'&')) # ...s split on '&'
b=S(a,,'=') # b = each element of a, split on '='
# Now, unfortunately if there's nothing after the '=',
# the strsplit() function fails to add an empty string ''
# so we need to do this ourselves:
e=A(b,function(x)c(x,'') # e = for each element of b, add a '' ...
[1:2]) # ...and then take the first two elements
# This gives us a 2-row matrix, with the names in row 1,
# and the values in row 2
n=unique(m<-e[1,])) # n = unique names, m = all names of name-value pairs
m=A(n,function(x) # m = for each element of n...
P(e[2,m==x],collapse=',')) # ...paste together the values for this name, using ',' as separator
P(n,m,sep='=',collapse='&') # Finally, paste together the pairs of elements in m, using '=' as separator...
# ...and collapse them all into one string using '&' as separator
R , 162 157 151 bytes
function(s,S=substring)paste0(z<-unique(k<-S(x<-el(strsplit(s,"&")),1,y<-regexpr('=',x))),sapply(split(S(x,y+1),k),paste,collapse=',')[z],collapse='&')
Experimente online!
-6 bytes graças a Dominic van Essen
Uma boa função de linha única. Ungolfed:
function(s,S=substr){
pairs <- el(strsplit(s,"&")) # split on '&' getting list of pairs
loc <- regexpr('=',pairs) # find location of each '=' in each pair
keys <- substr(pairs,1,loc) # get the key, including the '='
values <- substr(pairs,loc + 1,nchar(pairs)) # get the values (everything after '=')
unq_keys <- unique(keys) # uniquify the keys. This retains the order.
split_vals <- split(values,keys) # group the values into sublists by which key they are associated with
collapsed_values <- sapply(split_vals,paste,collapse=',') # join each subgroup of values by ','
collapsed_values <- collapsed_values[unq_keys] # and reorder them to match the order of the keys
paste0(unq_keys,collapsed_values,collapse='&') # concatenate keys and values and join by '&'
}