Introduction
Just on Hacker news, John Resig contemplates transforming a query "foo=1&foo=2&foo=3&blah=a&blah=b" into one that looks like this: "foo=1,2,3&blah=a,b", https://johnresig.com/blog/search-and-dont-replace/. He claims "being 10 lines shorter than Mark’s solution".
Query strings consist of sequences of name-value pairs. Name-value pairs consist of a name and value, separated by =. names and values are possibly empty sequences of alphanumeric characters. Name-value pairs are separated by the & character in the sequence. Values are unique for each name.
Challenge
10 lines shorter than Mark's solution is not enough.
- Read a query string.
- Combine name value pairs with equal name into a single name value pair, with values concatenated and separated by comma.
- Output the combined query string, with name-value pairs in the order the names are found from left to right in the query string.
This is code golf, the standard loopholes are closed.
Example Input and Output
Input:
foo=1&foo=&blah=a&foo=3&bar=x&blah=b&=1&=2
Output:
foo=1,,3&blah=a,b&bar=x&=1,2
Input:
foo=bar&foo=foo
Output:
foo=bar,foo
27 Answers 27
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`&`
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 '&'
-
1\$\begingroup\$ You can save one byte by using
s.replace(...)as the second parameter toObject.values()Try It Online \$\endgroup\$Matthew Jensen– Matthew Jensen2020年09月29日 23:19:03 +00:00Commented Sep 29, 2020 at 23:19 -
\$\begingroup\$ @MatthewJensen Nice one! \$\endgroup\$Arnauld– Arnauld2020年09月29日 23:28:22 +00:00Commented Sep 29, 2020 at 23:28
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}
-
5\$\begingroup\$ Welcome to the site! The winning criteria of this challenge is code golf, meaning you should attempt to minimise your program as much as possible (e.g. remove whitespace, unnecessary brackets etc.). Furthermore, be sure to check out our Tips for golfing in Javascript for ways of golfing that may not be as obvious \$\endgroup\$2020年09月29日 20:52:16 +00:00Commented Sep 29, 2020 at 20:52
-
2\$\begingroup\$ @cairdcoinheringaahing thanks for the warm welcome and the link to the tips! I was a bit shy about posting my first golf and knew that I'd probably miss something... Are the "usual loopholes" referenced by OP listed somewhere? \$\endgroup\$lucasreta– lucasreta2020年09月29日 21:03:01 +00:00Commented Sep 29, 2020 at 21:03
-
5\$\begingroup\$ The "usual loopholes", more commonly referred to as "standard loopholes", are listed on meta \$\endgroup\$The Fifth Marshal– The Fifth Marshal2020年09月29日 21:11:21 +00:00Commented Sep 29, 2020 at 21:11
-
\$\begingroup\$ Thanks, @pppery! I'll be sure to check them out \$\endgroup\$lucasreta– lucasreta2020年09月29日 21:29:49 +00:00Commented Sep 29, 2020 at 21:29
-
2\$\begingroup\$ @lucasreta The loopholes are basically un-sportsmanlike "cheats" and jokes that stopped being funny after the first time. Don't worry about them too much, you won't accidentally run into one! \$\endgroup\$Zgarb– Zgarb2020年09月30日 12:00:49 +00:00Commented Sep 30, 2020 at 12:00
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)),"&")
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))
-46 bytes thanks to ovs
-1 byte thanks to Jonathan Allan (in Py 3.8 PR, with walrus)
-
3\$\begingroup\$ Two small golfs: You don't need to convert the split result into a tuple, so the
(*,)is not necessary. Andk=[x[0]for x in a]can be shortened tok,*_=zip(*a). \$\endgroup\$ovs– ovs2020年09月29日 21:07:39 +00:00Commented Sep 29, 2020 at 21:07 -
3\$\begingroup\$ And you can use dictionaries for uniqifying a list in the original order: tio.run/##NYzBCoMwEER/… \$\endgroup\$ovs– ovs2020年09月29日 21:17:50 +00:00Commented Sep 29, 2020 at 21:17
-
\$\begingroup\$ @ovs Oh that's clever. Also dictionaries keep the original order? TIL, thank you! \$\endgroup\$2020年09月29日 21:20:17 +00:00Commented Sep 29, 2020 at 21:20
-
1\$\begingroup\$ Yes they do. Since version 3.7 this is part of the specification, before that it was just the way it happened to work in CPython. From the docs: Changed in version 3.7: Dictionary order is guaranteed to be insertion order. This behavior was an implementation detail of CPython from 3.6. \$\endgroup\$ovs– ovs2020年09月29日 21:31:24 +00:00Commented Sep 29, 2020 at 21:31
-
\$\begingroup\$ @ovs Ooh, that's very nice. I was considering deduping with sets but I know those don't keep order, so I wasn't sure about dicts. Thanks for the info! \$\endgroup\$2020年09月29日 21:32:33 +00:00Commented Sep 29, 2020 at 21:32
Ruby, 88 80 76 64 bytes
->s{s.scan(/(\w*=)(\w*)/).group_by(&:shift).map{|k,v|k+v*?,}*?&}
-8 bytes thanks to ovs for pointing I could assign a lambda to a variable
-12 bytes thanks to Dingus!
-
\$\begingroup\$ It is a shame tio doesn't use 2.7+ or we could save 3 more bytes by using
.map{_1+_2*?,}\$\endgroup\$engineersmnky– engineersmnky2020年10月01日 16:22:59 +00:00Commented Oct 1, 2020 at 16:22 -
\$\begingroup\$ Oh very cool, I was not even aware of that feature. \$\endgroup\$Jonah– Jonah2020年10月01日 16:24:18 +00:00Commented Oct 1, 2020 at 16:24
Python 3, 94 bytes
lambda s:'&'.join(k+'='+",".join(v)for k,v in parse_qs(s,1).items())
from urllib.parse import*
A few things:
- This only works because Python 3.6+ maintains dictionary order based upon time of insertion. We would love to use Python 2 for a shorter import (
urlparsevsurllib.parse) but dictionaries are not ordered properly. - As much as I love f-strings,
k+'='+",".join(v)is shorter thanf'{k}={",".join(v)}'. - The double
.joinfeels like golf smell, but I can't find a shorter way.
Python 3, 95 Bytes
from urllib.parse import*
d=parse_qs(input(),1)
print('&'.join(k+'='+','.join(d[k])for k in d))
-
\$\begingroup\$ I think I know what you mean with code smell. The problem has also a solution of the form
f(x) = find two equal names and merge valuesandg(x, y=f(x)) = x == y ? x : g(y)\$\endgroup\$Moritz Schauer– Moritz Schauer2020年10月01日 07:17:28 +00:00Commented Oct 1, 2020 at 7:17
Jelly, 28 bytes
-2 Thanks to Zgarb!
ṣ"&ṣ€"=Ṗ€ĠṢịƲμZQ€j€",j"=)j"&
A monadic Link accepting and yielding a list of characters.
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
-
\$\begingroup\$ I don't think the
0¦is necessary. You can safely join the left half too since it's a singleton list. \$\endgroup\$Zgarb– Zgarb2020年09月30日 06:05:29 +00:00Commented Sep 30, 2020 at 6:05 -
\$\begingroup\$ You are correct, thanks! \$\endgroup\$Jonathan Allan– Jonathan Allan2020年09月30日 10:49:43 +00:00Commented Sep 30, 2020 at 10:49
05AB1E, (削除) 62 (削除ここまで) 51 bytes
(-11 from @kevin)
'&¡'=δ¡D€нÙÐV_UsvYyнk©Xsèyθ',««Xs®ǝU}Xε¦ ̈}Yζí'=ý'&ý
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 &
-
1\$\begingroup\$ Can probably be golfed some more, but here is 51 bytes for now based on your current implementation. The main changes are
ε'=¡}to'=δ¡andεć'=s«« ̃} ̃'&ýtoí'=ý'&ý, and I've swapped the©®andVYvariables, by changing the©DtoÐV, so you can change theDVto©inside the loop. \$\endgroup\$Kevin Cruijssen– Kevin Cruijssen2020年09月30日 08:03:36 +00:00Commented Sep 30, 2020 at 8:03 -
\$\begingroup\$ Thanks! I'll add your improvements. \$\endgroup\$SomoKRoceS– SomoKRoceS2020年09月30日 17:48:12 +00:00Commented Sep 30, 2020 at 17:48
-
1\$\begingroup\$ I had a bit more time for this challenge and was able to find a shorter approach (23 bytes). Since it's completely different than your current approach I've posted it as a separated answer, but I figured I'd let you know in case you want to learn from it. :) \$\endgroup\$Kevin Cruijssen– Kevin Cruijssen2020年10月01日 13:44:47 +00:00Commented Oct 1, 2020 at 13:44
-
1\$\begingroup\$ That's awesome! I'll take a look \$\endgroup\$SomoKRoceS– SomoKRoceS2020年10月01日 13:54:57 +00:00Commented Oct 1, 2020 at 13:54
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.
-
\$\begingroup\$ @Shaggy You don't really need more test cases, do you? \$\endgroup\$Moritz Schauer– Moritz Schauer2020年09月30日 14:57:39 +00:00Commented Sep 30, 2020 at 14:57
-
1\$\begingroup\$ Yes, @mschauer, more (than one) is always better. See, for example, the comment I just left on the question. \$\endgroup\$Shaggy– Shaggy2020年09月30日 16:39:43 +00:00Commented Sep 30, 2020 at 16:39
-
\$\begingroup\$ @Shaggy OK, so how about this version? \$\endgroup\$Neil– Neil2020年09月30日 19:04:41 +00:00Commented Sep 30, 2020 at 19:04
-
-
\$\begingroup\$ @Shaggy That needs a newer version of JS than mine does though. \$\endgroup\$Neil– Neil2020年10月01日 09:36:19 +00:00Commented Oct 1, 2020 at 9:36
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)
-
2\$\begingroup\$ Wow. Really impressive! Brilliant \$\endgroup\$SomoKRoceS– SomoKRoceS2020年10月01日 13:58:43 +00:00Commented Oct 1, 2020 at 13:58
-
\$\begingroup\$ Really wished Japt had a method just for grouping while working on this challenge, the group & sort one cost me 5 bytes :( \$\endgroup\$Shaggy– Shaggy2020年10月01日 14:35:06 +00:00Commented Oct 1, 2020 at 14:35
-
1\$\begingroup\$ Although, you planted the idea of transposing in my head which lead to me saving 2 bytes so thanks for that :) \$\endgroup\$Shaggy– Shaggy2020年10月01日 14:50:41 +00:00Commented Oct 1, 2020 at 14:50
-
\$\begingroup\$ @Shaggy Yeah, I actually know the feeling. The
.¡is actually rather new. Before we had to first sort everything, and then could use a consecutive group by builtin. Usually took almost halve the program for a group by before the.¡was added. And glad my answer inspired you to golf some bytes. :) \$\endgroup\$Kevin Cruijssen– Kevin Cruijssen2020年10月01日 15:06:29 +00:00Commented Oct 1, 2020 at 15:06 -
\$\begingroup\$ @SomoKRoceS Absolutely! \$\endgroup\$Moritz Schauer– Moritz Schauer2020年10月01日 15:26:06 +00:00Commented Oct 1, 2020 at 15:26
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)
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`&`
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.
Japt, (削除) 29 (削除ここまで) (削除) 28 (削除ここまで) (削除) 27 (削除ここまで) (削除) 26 (削除ここまで) 24 bytes
q& móÈk¶
ü@bøXÎîÕvÎqÃq&
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"
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)-'&'})}
-
\$\begingroup\$ You can fix it by including the
trimwssource in the TIO header like this \$\endgroup\$Dominic van Essen– Dominic van Essen2020年09月30日 14:23:46 +00:00Commented Sep 30, 2020 at 14:23 -
\$\begingroup\$ But: "in the order the names are found from left to right in the query string" \$\endgroup\$Dominic van Essen– Dominic van Essen2020年09月30日 14:24:46 +00:00Commented Sep 30, 2020 at 14:24
-
\$\begingroup\$ Well spotted - fixed (at the cost of 26 bytes) by using
tapplyinstead ofbyand ordering the factor levels first. \$\endgroup\$Cong Chen– Cong Chen2020年09月30日 14:45:03 +00:00Commented Sep 30, 2020 at 14:45 -
\$\begingroup\$ Ouch! 26 bytes! \$\endgroup\$Dominic van Essen– Dominic van Essen2020年09月30日 14:53:40 +00:00Commented Sep 30, 2020 at 14:53
-
\$\begingroup\$ You can just use
factor(L,unique(L))to save 7... \$\endgroup\$Dominic van Essen– Dominic van Essen2020年09月30日 14:59:43 +00:00Commented Sep 30, 2020 at 14:59
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
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
-
\$\begingroup\$ I don't think I should wave the ordering requirement at this time, but you can submit the unordered solution clearly marked in parallel competition if you want. \$\endgroup\$Moritz Schauer– Moritz Schauer2020年09月30日 18:41:36 +00:00Commented Sep 30, 2020 at 18:41
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"
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 ;
It's long but I'm somewhat content with it :)
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
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
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.
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!!!
-
\$\begingroup\$ "in the order the names are found from left to right in the query string" \$\endgroup\$Dominic van Essen– Dominic van Essen2020年09月30日 14:05:52 +00:00Commented Sep 30, 2020 at 14:05
-
\$\begingroup\$ ...but I think you could solve it in pure AWK -F= for 144 bytes \$\endgroup\$Dominic van Essen– Dominic van Essen2020年09月30日 14:06:40 +00:00Commented Sep 30, 2020 at 14:06
-
\$\begingroup\$ @DominicvanEssen Oops, missed that. Great save - thanks! :D \$\endgroup\$Noodle9– Noodle92020年09月30日 14:10:32 +00:00Commented Sep 30, 2020 at 14:10
-
\$\begingroup\$
printflets you get rid ofORS=""for 138 bytes \$\endgroup\$Dominic van Essen– Dominic van Essen2020年09月30日 14:18:07 +00:00Commented Sep 30, 2020 at 14:18 -
\$\begingroup\$ @DominicvanEssen Nice one - thanks! :D \$\endgroup\$Noodle9– Noodle92020年09月30日 14:33:29 +00:00Commented Sep 30, 2020 at 14:33
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]
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...
Charcoal, 42 bytes
≔E⪪S&⪪ι=θW−Eθ§κ0υ⊞υ§ι0⪫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θ§κ0υ⊞υ§ι0
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 &.
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]);}
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);
}
}
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
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.
-
\$\begingroup\$ great answer, some bytes may be saved Try it online! \$\endgroup\$Nahuel Fouilleul– Nahuel Fouilleul2020年09月30日 20:11:56 +00:00Commented Sep 30, 2020 at 20:11
-
\$\begingroup\$ @NahuelFouilleul Nice, thanks :) \$\endgroup\$Chris– Chris2020年09月30日 20:14:46 +00:00Commented Sep 30, 2020 at 20:14
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='&')
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
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='&')
-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 '&'
}
-
\$\begingroup\$ There are possibly byte savings using
substringin place ofsubstr+strsplitbut unpacking agregexpris expensive in bytes so that might be a dead end. \$\endgroup\$Giuseppe– Giuseppe2020年09月30日 19:12:54 +00:00Commented Sep 30, 2020 at 19:12 -
\$\begingroup\$ Well done already. I never would have been able to bring myself to use 2x
paste(...,collapse=)which is my most-hated R argument... \$\endgroup\$Dominic van Essen– Dominic van Essen2020年09月30日 19:15:51 +00:00Commented Sep 30, 2020 at 19:15 -
\$\begingroup\$ 151 bytes with
substring, which goes to the end of the string when the end isn't specified. \$\endgroup\$Dominic van Essen– Dominic van Essen2020年10月01日 08:26:29 +00:00Commented Oct 1, 2020 at 8:26 -
\$\begingroup\$ ...and I suppose that
paste0is a (byte-wasting) accident... \$\endgroup\$Dominic van Essen– Dominic van Essen2020年10月01日 08:35:09 +00:00Commented Oct 1, 2020 at 8:35 -
\$\begingroup\$ @DominicvanEssen nice. I guess that assumes that the strings won't be of length greater than 1M? And the
paste0is on purpose, otherwise the query string will look like<key>= <value>(with a space in between the=and thevalue) \$\endgroup\$Giuseppe– Giuseppe2020年10月01日 14:54:03 +00:00Commented Oct 1, 2020 at 14:54
Wolfram Language (Mathematica), (削除) 90 (削除ここまで) (削除) 88 (削除ここまで) 87 bytes
StringRiffle[List@@@Normal@Merge[Rule@@@S[#~S~"&","=",2],D],"&","=",","]&
S=StringSplit
-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
-
\$\begingroup\$ You can replace
Allwith2to save 2 bytes. \$\endgroup\$LegionMammal978– LegionMammal9782020年09月30日 20:40:44 +00:00Commented Sep 30, 2020 at 20:40 -
\$\begingroup\$ @LegionMammal978 You should post it. I didn't even know about
StringExtract. \$\endgroup\$att– att2020年10月01日 02:28:15 +00:00Commented Oct 1, 2020 at 2:28
&=1,2is not included). \$\endgroup\$