쿼리 문자열 압축
소개
Hacker 뉴스에서 John Resig는 "foo = 1 & foo = 2 & foo = 3 & blah = a & blah = b"쿼리를 "foo = 1,2,3 & blah = a, b"와 같은 쿼리로 변환하는 것을 고려합니다. https://johnresig.com/blog/search-and-dont-replace/. 그는 "마크의 솔루션보다 10 줄이 짧다"고 주장합니다.
쿼리 문자열은 이름-값 쌍의 시퀀스로 구성됩니다. 이름-값 쌍은 =로 구분 된 이름과 값으로 구성됩니다. 이름과 값은 영숫자의 빈 시퀀스 일 수 있습니다. 이름-값 쌍은 시퀀스에서 & 문자로 구분됩니다. 값은 각 이름에 대해 고유합니다.
도전
Mark의 솔루션보다 10 줄이 짧은 것은 충분하지 않습니다.
- 쿼리 문자열을 읽습니다.
- 동일한 이름의 이름 값 쌍을 쉼표로 구분 된 값을 연결하여 단일 이름 값 쌍으로 결합합니다.
- 쿼리 문자열에서 이름이 왼쪽에서 오른쪽으로 발견되는 순서대로 이름-값 쌍을 사용하여 결합 된 쿼리 문자열을 출력합니다.
이것은 코드 골프이며 표준 허점은 닫혀 있습니다.
입력 및 출력 예
입력:
foo = 1 & foo = & blah = a & foo = 3 & bar = x & blah = b & = 1 & = 2
산출:
foo = 1,, 3 & blah = a, b & bar = x & = 1,2
입력:
foo = bar & foo = foo
산출:
foo = bar, foo
답변
05AB1E , 23 바이트
'&¡'=δ¡.¡н}εø€Ù}…,=&vyý
온라인으로 시도 하거나 모든 테스트 사례를 확인하십시오 .
설명:
'&¡ '# 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)
단계별 출력으로 온라인에서 시도해보십시오.
JavaScript (ES6), 103 99 87 86 바이트
@MatthewJensen 덕분에 1 바이트 절약
s=>Object.values(o=/(\w*=)(\w*)/g,s.replace(o,(s,k,v)=>o[k]=o[k]?o[k]+[,v]:s)).join`&`
온라인으로 시도하십시오!
댓글 작성
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 '&'
자바 스크립트, 263 201 196 194 190 189 188 173 161 바이트
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}
온라인으로 시도
Julia 1.0 , 106 바이트
f(a,z=split.(split(a,'&'),'='),u=first.(z))=join((i*'='*join(last.(z)[u.==i],',') for i in unique(u)),"&")
온라인으로 시도하십시오!
Python 3.8 (사전 출시) , 116 바이트
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))
온라인으로 시도하십시오!
ovs 덕분에 -46 바이트
-Jonathan Allan 덕분에 1 바이트 (Py 3.8 PR, 해마 포함)
Ruby , 88 80 76 64 바이트
->s{s.scan(/(\w*=)(\w*)/).group_by(&:shift).map{|k,v|k+v*?,}*?&}
온라인으로 시도하십시오!
-8 바이트를 가리키는 ovs 덕분에 변수에 람다를 할당 할 수 있습니다.
Dingus 덕분에 -12 바이트!
Python 3 , 94 바이트
lambda s:'&'.join(k+'='+",".join(v)for k,v in parse_qs(s,1).items())
from urllib.parse import*
온라인으로 시도하십시오!
몇 가지:
- 이것은 Python 3.6+가 삽입 시간에 따라 사전 순서를 유지하기 때문에 작동합니다. 더 짧은 가져 오기 (
urlparsevsurllib.parse) 를 위해 Python 2를 사용하고 싶지만 사전이 제대로 정렬되지 않았습니다. - 많이 나는 F-문자열, 사랑으로
k+'='+",".join(v)보다 짧은f'{k}={",".join(v)}'. - 더블
.join은 골프 냄새처럼 느껴지지만 더 짧은 방법을 찾을 수 없습니다.
Python 3 , 95 바이트
from urllib.parse import*
d=parse_qs(input(),1)
print('&'.join(k+'='+','.join(d[k])for k in d))
온라인으로 시도하십시오!
젤리 , 28 바이트
-2 Zgarb 덕분에!
ṣ”&ṣ€”=Ṗ€ĠṢịƲµZQ€j€”,j”=)j”&
문자 목록을 수락하고 생성하는 모나 딕 링크.
온라인으로 시도하십시오!
어떻게?
ṣ”&ṣ€”=Ṗ€ĠṢịƲµ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 바이트
(@kevin의 -11)
'&¡'=δ¡D€нÙÐV_UsvYyнk©Xsèyθ',««Xs®ǝU}X妨}Yζí'=ý'&ý
온라인으로 시도하십시오!
내 62 접근 방식 :
'&¡ε'=¡}D€нÙ©DgÅ0Usvy¬®skDVXsèyθ',««XsYǝU}X妨}®ζεć'=s««˜}˜'&ý
설명:
'&¡ε'=¡}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 &
온라인으로 시도하십시오!
자바 스크립트, 79 바이트
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>
I / O가 쿼리 문자열처럼 보이지만 올바르게 URL 인코딩되지 않은 유효하지 않은 문자열이 아니라 WHATWG 사양에 따른 실제 쿼리 문자열 일 수있는 경우 결과를 이스케이프 해제하는 대신 문자열 화하여 7 바이트를 저장할 수 있습니다.
자바 스크립트, 106 (104) 76 바이트
Port of Neil의 Retina 솔루션 , 허가를 받아 게시되었습니다.
f=q=>q==(q=q.replace(/(?<=^|&)((\w*=)[^&]*)(.*?)&\2(\w*)/,`$1,$4$3`))?q:f(q)
온라인으로 시도하십시오!
실물
술에 취한 채 약간의 골프를 쳐서 술에 취하지 않고 돌아 왔지만 그 과정에서 Arnauld의 해결책을 발견하고 내가 거의 똑같은 길에 있다는 것을 깨달았 기 때문에 이것을 그대로 두었습니다.
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 바이트
+1`(?<=^|&)((\w*=)[^&]*)(.*?)&\2(\w*)
$1,$4$3
온라인으로 시도하십시오! 설명 : 첫 번째 중복 키와 첫 번째 중복 키를 반복적으로 일치시키고 값을 원래 키의 값에 결합합니다.
Japt , 29 28 27 26 24 바이트
q& móÈk¶
ü@bøXÎîÕvÎqÃq&
시도 해봐
Kevin의 영감 덕분에 2 바이트가 절약되었습니다 .
(오, Japt에만 정렬이 아닌 그룹화 전용 메소드가 있다면 19 바이트 가 될 수 있습니다 .)
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 "&"
또는 단계별 안내를 제공하려면 :
입력
"foo=1&foo=&blah=a&foo=3&bar=x&blah=b&=1&=2"
스플릿
["foo=1","foo=","blah=a","foo=3","bar=x","blah=b","=1","=2"]
지도 및 파티션
[["foo=","1"],["foo="],["blah=","a"],["foo=","3"],["bar=","x"],["blah=","b"],["=","1"],["=","2"]]
그룹화 및 정렬
[[["foo=","1"],["foo="],["foo=","3"]],[["blah=","a"],["blah=","b"]],[["bar=","x"]],[["=","1"],["=","2"]]]
지도 및 ...
바꾸어 놓다
[[["foo=","foo=","foo="],["1",null,"3"]],[["blah=","blah="],["a","b"]],[["bar="],["x"]],[["=","="],["1","2"]]]
첫 번째 요소를 첫 번째 요소에 매핑
[["foo=",["1",null,"3"]],["blah=",["a","b"]],["bar=",["x"]],["=",["1","2"]]]
어울리다
["foo=1,,3","blah=a,b","bar=x","=1,2"]
어울리다
"foo=1,,3&blah=a,b&bar=x&=1,2"
Awk , 144 \$\cdots\$144 138 바이트
버그 수정을 위해 6 바이트가 추가되었습니다. Dominic van Essen이
친절하게 지적하고 해결 한 버그를 수정하기 위해 4 바이트를 추가했습니다 . Dominic van Essen 덕분에 6 바이트 절약 !!!
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="&"}}
온라인으로 시도하십시오!
R , 195 바이트
{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)-'&'})}
온라인으로 시도하십시오!
Lua , 162 바이트
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
온라인으로 시도하십시오!
이것은 긴 것입니다. 주문 요건이 아니었다면 훨씬 짧아 졌을 수도 있습니다.
설명:
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
Wolfram 언어 (Mathematica) , 86 바이트
StringRiffle[Last@Reap[Sow@@@StringExtract[#,"&"->;;,"="->{2,1}],_,List],"&","=",","]&
온라인으로 시도하십시오! 순수 함수는 문자열을 입력으로 받고 다른 문자열을 출력으로 반환합니다. 이것은 att의 답변 에서 영감을 얻었고 매우 유사 하지만 블로그 게시물 (여기에서 Sow/ 사용 Reap) 과 유사한 알고리즘을 사용합니다 . 다음은 입력에서 하위 표현식이 평가되는 방법에 대한 예입니다 "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"
인수 , 229 바이트
: 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 ;
온라인으로 시도하십시오!
길지만 약간 만족합니다 :)
SNOBOL4 (CSNOBOL4) , 243 (221) 212 바이트
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
온라인으로 시도하십시오!
TABLESNOBOL에서는 이상합니다. PATTERN좋아요 ARB를 키로 받아들이는 것은 완벽 하지만 빈 문자열은 허용하지 않습니다 ''. 그러나 깔끔하게 <label>=대신 라벨로 사용 <label>하면이 문제가 해결됩니다.
이전 반복에 대한 설명 :
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 바이트
<?=parse_str(str_replace('=','_[]=',$argv[1]),$a)??join('&',array_map(function($b,$c){return rtrim($b,'_').'='.join(',',$c);},array_keys($a),$a));
온라인으로 시도하십시오! 설명 : parse_str반복되는 값을 처리하도록 설계되지는 않았지만 각 값의 이름을 뒤에 붙임으로써 설득 할 수 있습니다 []. 또한 빈 이름을 처리하도록 설계되지 않았지만 []어쨌든 추가하고 _있으므로이 경우를 충족시키기 위해 를 추가 할 수도 있습니다 . 쿼리 문자열을 구문 분석 한 다음 모든 것을 다시 결합 할 수 있습니다.
빨간색 , 221 바이트
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]
온라인으로 시도하십시오!
이것은 너무 길고 적어도 조금은 골프를 칠 것입니다. 너무 나쁜 레드 는 편리한 join기능 이 없습니다 ...
차콜 , 42 바이트
≔E⪪S&⪪ι=θW⁻Eθ§κ⁰υ⊞υ§ι⁰⪫Eυ⁺⁺ι=⪫EΦθ¬⌕λι⊟λ,¦&
온라인으로 시도하십시오! 링크는 자세한 코드 버전입니다. 설명:
≔E⪪S&⪪ι=θ
&s 에서 입력을 분할하고 s에서 각 토큰을 분할합니다 =.
W⁻Eθ§κ⁰υ⊞υ§ι⁰
처음 나타나는 순서대로 고유 키 목록을 만듭니다.
⪫Eυ⁺⁺ι=⪫EΦθ¬⌕λι⊟λ,¦&
각 키에 대해 값을 추출 및 결합 ,하고 키 및 구분 기호로 결합한 다음 전체 결과를 &.
C (gcc) , 319304 바이트
제안에 대한 ceilingcat에게 감사드립니다.
이 함수는 토큰 화 된 각 (이름, 값) 쌍을 스캔하고 발견 된 각 새 이름에 대한 목록 항목을 추가 한 다음 각 값에 대한 목록 항목을 추가합니다. 목록을 구성한 후 각 목록을 반복하고 값을 인쇄합니다. 공간을 절약하기 위해 구조를 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]);}
온라인으로 시도하십시오!
골프를 치지 않은 원본 제출물 :
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);
}
}
온라인으로 시도하십시오!
Wolfram 언어 (Mathematica) , 90 88 바이트
StringRiffle[S=StringSplit;List@@@Normal@Merge[Rule@@@S[#~S~"&","=",2],#&],"&","=",","]&
온라인으로 시도하십시오!
-2 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\&플래그, 73 57 바이트
용도 -pF\&입에 자동 구분을 통해 루프 &.
정렬되지 않은 결과, 병렬 경쟁.
/=/,$z{$`}.=$z{$`}?",$'":"$`=$'"for@F;$_=join'&',values%z
온라인으로 시도하십시오!
해시 %z를 사용하여 개별 이름의 값을 추적 한 다음 끝에 모두 인쇄합니다. NahuelFouilleul 덕분에 -16 바이트.
R , 174166 바이트
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='&')
온라인으로 시도하십시오!
설명 할 수없는 이유로 저는이 도전이 R 의 끔찍하게 장황한 문자열 처리로 고통받지 않을 것이라고 생각했습니다 .
적어도 지금까지의 시도에 따르면 이것은 사실이 아닙니다 ...
골프 전 코멘트 :
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 바이트
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='&')
온라인으로 시도하십시오!
-Dominic van Essen 덕분에 6 바이트
멋진 한 줄 기능. 미 골프 :
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 '&'
}