g | x | w | all
Bytes Lang Time Link
087Wolfram Language Mathematica200930T072510Zatt
135Awk200930T113804ZNoodle9
146PHP201009T125008ZNeil
024Japt200930T155203ZShaggy
076JavaScript200929T211735ZShaggy
nanSNOBOL4 CSNOBOL4200930T160001ZGiuseppe
229Factor201001T183714ZGalen Iv
02305AB1E201001T133458ZKevin Cr
151R200930T190749ZGiuseppe
166R200930T092642ZDominic
086Wolfram Language Mathematica201001T130246ZLegionMa
057Perl 5200929T220317ZChris
304C gcc200930T053621ZErikF
042Charcoal200930T191812ZNeil
079JavaScript200929T224120ZNeil
162Lua200930T181529Zval - di
05105AB1E200929T221343ZSomoKRoc
195R200930T134837ZCong Che
045Retina 0.8.2200930T152341ZNeil
nanRuby200929T221122ZJonah
221Red200930T113441ZGalen Iv
028Jelly200929T215442ZJonathan
086JavaScript ES6200929T213217ZArnauld
094Python 3200930T013832ZSisyphus
116Python 3.8 prerelease200929T205928Zhyper-ne
106Julia 1.0200929T214649ZMoritz S
161JavaScript200929T204327Zlucasret

Wolfram Language (Mathematica), 90 88 87 bytes

StringRiffle[List@@@Normal@Merge[Rule@@@S[#~S~"&","=",2],D],"&","=",","]&
S=StringSplit

Try it online!

-2 thanks to LegionMammal978

S=StringSplit
Rule@@@S[#~S~"&","=",2]         Convert to a list of Rules
Merge[ % ,D]                    Combine rules into an Association, with values unchanged
Normal@ %                       Convert Association back into a list of Rules,
List@@@ %                       and turn Rules into Lists
StringRiffle[ % ,"&","=",","]   Concatenate, using "&", "=", and "," as separators

Awk, 144 \$\cdots\$138 135 bytes

BEGIN{RS="&"}{a[$1][j=i[$1]++]=$2;j||(n[m++]=$1)}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="&"}}

Added 6 bytes to fix a bug
Added 4 bytes to fix a bug kindly pointed out and solved by Dominic van Essen!!!
Saved 6 bytes thanks to Dominic van Essen!!!
Saved 3 bytes thanks to ceilingcat!!!

Try it online!

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));

Try it online! Explanation: parse_str wasn't designed to handle repeated values, but you can persuade it to by naming each value with a trailing []. It also wasn't designed to handle empty names, but since I'm appending [] anyway I can also add a _ to satisfy that case. Having parsed the query string it then remains to join everything back together.

Japt, 29 28 27 26 24 bytes

q& móÈk¶
ü@bøXÎîÕvÎqÃq&

Try it

Saved 2 bytes thanks to some inspiration from Kevin.

(Oh, if only Japt had a method just for grouping, rather than also sorting, this could be 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 "&"

Or, to provide a step-by-step walkthrough:

Input

"foo=1&foo=&blah=a&foo=3&bar=x&blah=b&=1&=2"

Split

["foo=1","foo=","blah=a","foo=3","bar=x","blah=b","=1","=2"]

Map & Partition

[["foo=","1"],["foo="],["blah=","a"],["foo=","3"],["bar=","x"],["blah=","b"],["=","1"],["=","2"]]

Group & sort

[[["foo=","1"],["foo="],["foo=","3"]],[["blah=","a"],["blah=","b"]],[["bar=","x"]],[["=","1"],["=","2"]]]

Map and ...

Transpose

[[["foo=","foo=","foo="],["1",null,"3"]],[["blah=","blah="],["a","b"]],[["bar="],["x"]],[["=","="],["1","2"]]]

Map first element to its first element

[["foo=",["1",null,"3"]],["blah=",["a","b"]],["bar=",["x"]],["=",["1","2"]]]

Join

["foo=1,,3","blah=a,b","bar=x","=1,2"]

Join

"foo=1,,3&blah=a,b&bar=x&=1,2"

JavaScript, 106 104 76 bytes

Port of Neil's Retina solution, posted with permission.

f=q=>q==(q=q.replace(/(?<=^|&)((\w*=)[^&]*)(.*?)&\2(\w*)/,`$1,$4$3`))?q:f(q)

Try it online!

Original

A little bit of drunk golfing that I came back to work on sober but in the process spotted Arnauld's solution and realised I was on the path to something almost identical so I left this as-was.

q=>Object.keys(o={},q.split`&`.map(p=>o[[k,v]=p.split`=`,k]=[...o[k]||[],v])).map(x=>x+`=`+o[x]).join`&`

Try it online!

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

Try it online!

TABLE in SNOBOL is weird. It's perfectly fine accepting a PATTERN like ARB as a key, but not the empty string ''. However, using <label>= as the label instead of <label> neatly solves this problem.

Explanation for a previous iteration:

	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

Factor, 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 ;

Try it online!

It's long but I'm somewhat content with it :)

05AB1E, 23 bytes

'&¡'=δ¡.¡н}εø€Ù}…,=&vyý

Try it online or verify all test cases.

Explanation:

'&¡                     '# 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)

Try it online with a step-by-step output.

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='&')

Try it online!

-6 bytes thanks to Dominic van Essen

A nice single line function. 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 '&'
}

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='&')

Try it online!

For some inexplicable reason I thought that this challenge wouldn't suffer from R's awfully-verbose string handling.
This didn't turn out to be the case, at least based on my attempt so far...

Commented before golfing:

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

Wolfram Language (Mathematica), 86 bytes

StringRiffle[Last@Reap[Sow@@@StringExtract[#,"&"->;;,"="->{2,1}],_,List],"&","=",","]&

Try it online! Pure function, takes a string as input and returns another string as output. This is inspired by and very similar to att's answer, but it uses an algorithm similar to the one in the blog post (here utilizing Sow/Reap). Here's an example of how the subexpressions evaluate on an input of "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"

Perl 5, -pF\& flags, 73 57 bytes

Uses -pF\& to loop over inputs and autosplit on &.

Unordered results, in parallel competition.

/=/,$z{$`}.=$z{$`}?",$'":"$`=$'"for@F;$_=join'&',values%z

Try it online!

Uses a hash %z to keep track of values for individual names, and then prints them all out at the end. -16 bytes thanks to NahuelFouilleul.

C (gcc), 319 304 bytes

Thanks to ceilingcat for the suggestions.

This function scans each tokenized (name,value) pair and adds a list entry for each new name encountered, then appends a list entry for each value. After constructing the lists, it then iterates through each list and prints the values. To save space, I flattened the structures into arrays of 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]);}

Try it online!

Non-golfed version of original submission:

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);
  }
}

Try it online!

Charcoal, 42 bytes

≔E⪪S&⪪ι=θW⁻Eθ§κ⁰υ⊞υ§ι⁰⪫Eυ⁺⁺ι=⪫EΦθ¬⌕λι⊟λ,¦&

Try it online! Link is to verbose version of code. Explanation:

≔E⪪S&⪪ι=θ

Split the input on &s and split each token on =s.

W⁻Eθ§κ⁰υ⊞υ§ι⁰

Create a list of unique keys in order of their first appearance.

⪫Eυ⁺⁺ι=⪫EΦθ¬⌕λι⊟λ,¦&

For each key, extract and join the values with ,, concatenate with the key and separator, and join the overall result with &.

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>

If I/O could be actual query strings according to the WHATWG spec, rather than invalid strings that looks like query strings but aren't correctly URLencoded, then 7 bytes could be saved by stringifying the result instead of unescaping it.

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

Try it online!

This is a long one, huh. It could have been made much shorter if not for ordering requiment.

Explanation:

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

05AB1E, 62 51 bytes

(-11 from @kevin)

'&¡'=δ¡D€нÙÐV_UsvYyнk©Xsèyθ',««Xs®ǝU}X妨}Yζí'=ý'&ý

Try it online!


My 62 approach:

'&¡ε'=¡}D€нÙ©DgÅ0Usvy¬®skDVXsèyθ',««XsYǝU}X妨}®ζεć'=s««˜}˜'&ý

Explanation:

'&¡ε'=¡}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 &

Try it 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)-'&'})}

Try it online!

Retina 0.8.2, 45 bytes

+1`(?<=^|&)((\w*=)[^&]*)(.*?)&\2(\w*)
$1,$4$3

Try it online! Explanation: Repeatedly matches the first duplicate key and its first duplicate and joins the value to that of the original key.

Ruby, 88 80 76 64 bytes

->s{s.scan(/(\w*=)(\w*)/).group_by(&:shift).map{|k,v|k+v*?,}*?&}

Try it online!

-8 bytes thanks to ovs for pointing I could assign a lambda to a variable

-12 bytes thanks to Dingus!

Red, 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]

Try it online!

This is awfully long and I'll try to golf it at least a bit. Too bad Red doesn't have a handy join function...

Jelly, 28 bytes

-2 Thanks to Zgarb!

ṣ”&ṣ€”=Ṗ€ĠṢịƲµZQ€j€”,j”=)j”&

A monadic Link accepting and yielding a list of characters.

Try it online!

How?

ṣ”&ṣ€”=Ṗ€ĠṢịƲµ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

JavaScript (ES6),  103 99 87  86 bytes

Saved 1 byte thanks to @MatthewJensen

s=>Object.values(o=/(\w*=)(\w*)/g,s.replace(o,(s,k,v)=>o[k]=o[k]?o[k]+[,v]:s)).join`&`

Try it online!

Commented

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 '&'

Python 3, 94 bytes

lambda s:'&'.join(k+'='+",".join(v)for k,v in parse_qs(s,1).items())
from urllib.parse import*

Try it online!

A few things:


Python 3, 95 Bytes

from urllib.parse import*
d=parse_qs(input(),1)
print('&'.join(k+'='+','.join(d[k])for k in d))

Try it online!

Python 3.8 (pre-release), 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))

Try it online!

-46 bytes thanks to ovs

-1 byte thanks to Jonathan Allan (in Py 3.8 PR, with walrus)

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)),"&")

Try it online!

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}

Try it online