16
\$\begingroup\$

This is my first codegolf question, so I apologize in advance if it's not appropriate, and I welcome any feedback.

I have a file with this format:

a | rest of first line
b | rest of second line
b | rest of third line
c | rest of fourth line
d | rest of fifth line
d | rest of sixth line

The actual contents vary, as does the delimiter. The contents are just text. The delimiter only appears once per line. For this puzzle, feel free to change the delimiter, e.g. use "%" as the delimiter.

Desired output:

a | rest of first line
b | rest of second line % rest of third line
c | rest of fourth line
d | rest of fifth line % rest of sixth line

I already have both ruby and awk scripts to merge this, but I suspect it's possible to have a short oneliner. i.e. a one-liner that can be used along with pipes and other commands on the command line. I can't figure it out, and my own script is to long to just compress on the command line.

Shortest characters preferred. Input is not necessarily sorted, but we are only interested in merging consecutive lines with matching first fields. There are unlimited lines with matching first fields. Field 1 could be anything, e.g. names of fruits, proper names, etc.

(I run on MacOS, so I am personally most interested in implementations that run on the mac).


Here is a second example/test. Notice "|" is the delimiter. The space before the "|" is irrelevant, and if resent should be considered part of the key. I am using "%" as a delimited in the output, but again, feel free to change the delimiter (but don't used square brackets).

Input:

why|[may express] surprise, reluctance, impatience, annoyance, indignation
whom|[used in] questions, subordination
whom|[possessive] whose
whom|[subjective] who
whoever|[objective] whomever
whoever|[possessive] whosever
who|[possessive] whose
who|[objective] whom

Desired output:

why|[may express] surprise, reluctance, impatience, annoyance, indignation
whom|[used in] questions, subordination%[possessive] whose%[subjective] who
whoever|[objective] whomever%[possessive] whosever
who|[possessive] whose%[objective] whom
\$\endgroup\$
4
  • \$\begingroup\$ Is a newline at the beginning of the output allowed? \$\endgroup\$
    – mIllIbyte
    Jun 1, 2016 at 19:52
  • \$\begingroup\$ added comments into original question. And, @mIllIbyte, a newline is irrelevant to me. But in my idea, there are no blank lines, and no error checking. I assume all lines have text, and at least the first column and the delimiter. \$\endgroup\$ Jun 1, 2016 at 20:13
  • \$\begingroup\$ Judging by the test cases, is it save to assume that all keys are grouped? I.e.: ["A|some text", "B|other text", "A|yet some other text"] isn't a desired input to test, since the keywords for A aren't one after another in the list. \$\endgroup\$ Jun 2, 2016 at 11:59
  • \$\begingroup\$ I assumed all keys are grouped. I'm not concerned with the case where they are not, though in theory, it not they would be treated like unique keys. \$\endgroup\$ Jun 2, 2016 at 14:30

12 Answers 12

7
\$\begingroup\$

Retina, 17 bytes

  • 12 bytes saved thanks to @MartinEnder
  • 1 byte saved thanks to @jimmy23013

Scored in ISO 8859-1 encoded bytes.

Uses ; instead of | as the input field separator.

(?<=(.+;).+)¶\1
%

Try it online.

\$\endgroup\$
2
  • \$\begingroup\$ retina.tryitonline.net/… \$\endgroup\$
    – Leaky Nun
    Jun 1, 2016 at 23:34
  • 2
    \$\begingroup\$ @LeakyNun Because lookarounds are atomic. The first time the lookaround is used it captures the entire prefix of the line, and afterwards the regex engine won't backtrack into it any more. \$\endgroup\$ Jun 2, 2016 at 6:52
5
\$\begingroup\$

V, 16 13 bytes

òí^¨á«©.*úsî±

Try it online!

You said

Feel free to change the delimiter

So I picked | as the delimiter. If this is invalid, let me know and I'll change it.

Explanation:

ò                #Recursively:
 í               #Search for the following on any line:
  ^¨á«©          #1 or more alphabetic characters at the beginning of the line
       .*        #Followed by anything
         ús      #Mark everything after this to be removed:
           î±    #A new line, then the first match again (one or more alphabetic characters)
\$\endgroup\$
6
  • 1
    \$\begingroup\$ Let you know??? \$\endgroup\$ Jun 1, 2016 at 18:51
  • \$\begingroup\$ @ΈρικΚωνσταντόπουλος Yes? Is that a problem? \$\endgroup\$
    – DJMcMayhem
    Jun 1, 2016 at 18:55
  • \$\begingroup\$ For this puzzle, feel free to change the delimiter, e.g. use "%" as the delimiter. not i.e. \$\endgroup\$ Jun 1, 2016 at 18:57
  • 2
    \$\begingroup\$ The "|" delimiter is fine. \$\endgroup\$ Jun 1, 2016 at 19:22
  • \$\begingroup\$ @MichaelCodes Could you add some more test cases so we can verify if a solution counts or not? \$\endgroup\$
    – DJMcMayhem
    Jun 1, 2016 at 19:32
3
\$\begingroup\$

Perl -0n, 2 + 43 = 45 bytes

s/
.*\|/%/g,print for/(.*\|)((?:
\1|.)*
)/g

Demo:

$ perl -0ne 's/
> .*\|/%/g,print for/(.*\|)((?:
> \1|.)*
> )/g' <<EOF
> why|[may express] surprise, reluctance, impatience, annoyance, indignation
> whom|[used in] questions, subordination
> whom|[possessive] whose
> whom|[subjective] who
> whoever|[objective] whomever
> whoever|[possessive] whosever
> who|[possessive] whose
> who|[objective] whom
> EOF
why|[may express] surprise, reluctance, impatience, annoyance, indignation
whom|[used in] questions, subordination%[possessive] whose%[subjective] who
whoever|[objective] whomever%[possessive] whosever
who|[possessive] whose%[objective] whom
\$\endgroup\$
3
\$\begingroup\$

SQL (PostgreSQL), 43 72 bytes

COPY T FROM'T'(DELIMITER'|');SELECT a,string_agg(b,'%')FROM T GROUP BY A

This takes advantage of the handy string_agg aggregate function in PostgreSQL. Input is from a table called T with 2 columns A and B. To comply with the question better I have included to command to load data from a file into the table. The file is T as well. I haven't counted the table create statement.
The output will be unordered, but if that is a problem it can be fixed with an ORDER BY A

SQLFiddle didn't want to play for me, but this is what I get in my setup.

CREATE TABLE T (A VARCHAR(9),B VARCHAR(30));

COPY T FROM'T'(DELIMITER'|');SELECT a,string_agg(b,'%')FROM T GROUP BY A
a   string_agg
--- ----------------------------------------
c   rest of fourth line
b   rest of second line%rest of third line
a   rest of first line
d   rest of fifth line%rest of sixth line
\$\endgroup\$
2
  • 1
    \$\begingroup\$ To be fair, I'd suggest including a COPY command to read the contents of the file format specified into the table, too, otherwise you're not solving the same problem as everyone else. \$\endgroup\$
    – Jules
    Jun 2, 2016 at 6:54
  • \$\begingroup\$ @Jules Fair enough, I was thinking of this default i/o concensus when I answered. Rereading the question though I'll edit the answer. \$\endgroup\$
    – MickyT
    Jun 2, 2016 at 19:29
2
\$\begingroup\$

C, 127 bytes

o[99],n[99],p=n;main(i){for(;gets(n);strncmp(o,n,i-p)?printf(*o?"\n%s":"%s",n),strcpy(o,n):printf(" /%s",i))i=1+strchr(n,'|');}

Works with gcc. Changed delimiter to /. Takes input from stdin and writes output to stdout, so call with input redirection ./a.out <filename

Ungolfed:

o[99],n[99] //declare int, to save two bytes for the bounds
,p=n; //p is an int, saves one byte as opposed to applying an (int) cast to n,
//or to declaring o and n as char arrays
main(i){for(;gets(n);strncmp(o,n,i-p //an (int)n cast would be needed;
// -(n-i) does not work either,
//because pointer arithmetics scales to (int*)
)?printf(*o?"\n%s":"%s" //to avoid a newline at the beginning of output
,n),strcpy(o,n):printf(" /%s",i))i=1+strchr(n,'|');}
\$\endgroup\$
1
\$\begingroup\$

Pyth - 15 bytes

Making a few assumptions about the problem, will change when OP clarifies.

jm+Khhd-sdK.ghk

Try it online here.

\$\endgroup\$
1
  • \$\begingroup\$ This doesn't work if the "key" is a word, rather than a single letter. (OP clarified in the comments) \$\endgroup\$
    – DJMcMayhem
    Jun 1, 2016 at 18:47
1
\$\begingroup\$

Python 3 - 146 Bytes

Input is the filename or file path of the file, output is to stdout. Could be a lot shorter if I could take input as raw text from command line

Takes input from stdin and outputs to stdin. Setup with separator "|". To test the first example input use the separator " | "

from itertools import*
for c,b in groupby([x.split("|")for x in input().split("\n")],key=lambda x:x[0]):print(c,"|"," % ".join((a[1]for a in b)))
\$\endgroup\$
3
  • \$\begingroup\$ The challenge doesn't explicitly require the input to be read from a file, so I guess our Default I/O methods apply here. And since other answers also take the input from STDIN, I suppose the OP is fine with it. \$\endgroup\$
    – Denker
    Jun 2, 2016 at 10:10
  • \$\begingroup\$ @DenkerAffe Alright i'll edit it, it will just be completely useless because I don't think you can even give actual multiline input from stdin. \$\endgroup\$
    – Keatinge
    Jun 2, 2016 at 10:11
  • \$\begingroup\$ But you can do input redirection when you run the script. \$\endgroup\$
    – mIllIbyte
    Jun 2, 2016 at 10:13
1
\$\begingroup\$

Java 7, 167 bytes

It can probably be golfed more by using a different approach..

import java.util.*;Map c(String[]a){Map m=new HashMap();for(String s:a){String[]x=s.split("=");Object l;m.put(x[0],(l=m.get(x[0]))!=null?l+"%"+x[1]:x[1]);}return m;}

NOTE: The method above creates and returns a HashMap with the desired key-value pairs. However, it doesn't print it in the exact output as in OP's question with | as output-delimiter between the keys and new values. Judging by MickeyT's SQL answer where he returned a database table I figured this is allowed; if not more bytes should be added for a print function.

Ungolfed & test code:

import java.util.*;

class Main{

    static Map c(String[] a){
        Map m = new HashMap();
        for(String s : a){
            String[] x = s.split("\\|");
            Object l;
            m.put(x[0], (l = m.get(x[0])) != null
                            ? l + "%" + x[1]
                            : x[1]);
        }
        return m;
    }

    public static void main(String[] a){
        Map m = c(new String[]{
            "why|[may express] surprise, reluctance, impatience, annoyance, indignation",
            "whom|[used in] questions, subordination",
            "whom|[possessive] whose",
            "whom|[subjective] who",
            "whoever|[objective] whomever",
            "whoever|[possessive] whosever",
            "who|[possessive] whose",
            "who|[objective] whom"
        });

        // Object instead of Map.EntrySet because the method returns a generic Map
        for (Object e : m.entrySet()){
            System.out.println(e.toString().replace("=", "|"));
        }
    }
}

Output:

whoever|[objective] whomever%[possessive] whosever
whom|[used in] questions, subordination%[possessive] whose%[subjective] who
why|[may express] surprise, reluctance, impatience, annoyance, indignation
who|[possessive] whose%[objective] whom
\$\endgroup\$
1
\$\begingroup\$

PowerShell, 85 bytes

Strings are merged using hashtable:

%{$h=@{}}{$k,$v=$_-split'\|';$h.$k=($h.$k,$v|?{$_})-join'%'}{$h.Keys|%{$_+'|'+$h.$_}}

Example

Since PowerShell doesn't support stdin redirection via <, I'm assuming that Get-Content .\Filename.txt | will be used as default I/O method.

Get-Content .\Filename.txt | %{$h=@{}}{$k,$v=$_-split'\|';$h.$k=($h.$k,$v|?{$_})-join'%'}{$h.Keys|%{$_+'|'+$h.$_}}

Output

whoever|[objective] whomever%[possessive] whosever
why|[may express] surprise, reluctance, impatience, annoyance, indignation
whom|[used in] questions, subordination%[possessive] whose%[subjective] who
who|[possessive] whose%[objective] whom
\$\endgroup\$
1
\$\begingroup\$

APL, 42 chars

{⊃{∊⍺,{⍺'%'⍵}/⍵}⌸/↓[1]↑{(1,¯1↓'|'=⍵)⊂⍵}¨⍵}
\$\endgroup\$
1
  • \$\begingroup\$ isn't one byte in the APL encoding. \$\endgroup\$
    – Adalynn
    Jul 31, 2017 at 22:40
0
\$\begingroup\$

Sed, 55 bytes

:a N;:b s/^\([^|]*\)|\([^\n]*\)\n\1|/\1|\2 %/;ta;P;D;tb

Test run :

$ echo """why|[may express] surprise, reluctance, impatience, annoyance, indignation
> whom|[used in] questions, subordination
> whom|[possessive] whose
> whom|[subjective] who
> whoever|[objective] whomever
> whoever|[possessive] whosever
> who|[possessive] whose
> who|[objective] whom""" | sed ':a N;:b s/^\([^|]*\)|\([^\n]*\)\n\1|/\1|\2 %/;ta;P;D;tb'
why|[may express] surprise, reluctance, impatience, annoyance, indignation
whom|[used in] questions, subordination %[possessive] whose %[subjective] who
whoever|[objective] whomever %[possessive] whosever
who|[possessive] whose %[objective] whom
\$\endgroup\$
0
\$\begingroup\$

q/kdb+, 46 bytes

Solution:

exec"%"sv v by k from flip`k`v!("s*";"|")0:`:f

Example:

q)exec"%"sv v by k from flip`k`v!("s*";"|")0:`:f
who    | "[possessive] whose%[objective] whom"
whoever| "[objective] whomever%[possessive] whosever"
whom   | "[used in] questions, subordination%[possessive] whose%[subjective] who"
why    | "[may express] surprise, reluctance, impatience, annoyance, indignation"

Explanation:

`:f            // assumes the file is named 'f'
("s*";"|")0:   // read in file, assume it has two columns delimitered by pipe
flip `k`v      // convert into table with columns k (key) and v (value)
exec .. by k   // group on key
"%"sv v        // join values with "%"
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.