15
\$\begingroup\$

I have this problem where I listen to so much music that I can never remember what songs are called. But even if I remember the lyrics, I won't always know the song's name. But lucky for me, there's a pretty neat formula to determine¹ the title of a song just based on its lyrics.

I'll define the way a song title can be guessed as the line repeated in its lyrics the most times. If multiple lines are repeated the same number of times, choose the shortest of them. If multiple lyrics both occur the same number of times and are of equal length, you may choose any of them.

Given an input of a multiline string where each line is a lyric (you may take input as a list of lines), output the predicted title of the song, using the method outlined above.

For example, say this totally real song was stuck in my head and I couldn't remember its name:

Hello, world
Hello, world
I just got to say it, hello world
Goodbye, world
Goodbye, world
Goodbye

I can figure using this formula that the song's title is "Hello, world".

You may assume that the input string will contain no empty lines.

Test cases:

Links go to pastebins of the lyrics, with any " removed.


This is , so shortest code in bytes wins.

¹ Accuracy not guaranteed. No refunds.

\$\endgroup\$
4
  • 3
    \$\begingroup\$ It turns out you only get 1/4 correct rate on test cases :) \$\endgroup\$
    – l4m2
    Jan 9, 2023 at 2:27
  • 2
    \$\begingroup\$ What if there is a tie in length as well as count? \$\endgroup\$
    – chunes
    Jan 9, 2023 at 2:36
  • \$\begingroup\$ @chunes In that case you may choose any of the tied lyrics. \$\endgroup\$
    – noodle man
    Jan 9, 2023 at 3:30
  • \$\begingroup\$ @KevinCruijssen Depending on where you put your line breaks, I'd say that song should just be called "Hey now"... \$\endgroup\$ Jan 9, 2023 at 15:30

20 Answers 20

12
\$\begingroup\$

05AB1E, 3 bytes

é.M

Input as a list of lines.

Try it online or verify all test cases.

Explanation:

é    # Sort the (implicit) input-list by their length (shortest to longest)
 .M  # Pop and leave the (first/shortest) most frequent line
     # (which is output implicitly as result)
\$\endgroup\$
11
\$\begingroup\$

Vyxal, 4 bytes

Þṡ∆M

Try it Online!

Þṡ   # Sort by length
  ∆M # Get the most common item (order overrides repetitions)
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Defeating the creator :) \$\endgroup\$ Jan 9, 2023 at 5:52
  • \$\begingroup\$ Cool that Vyxal sort of has a builtin for this. Nice solution \$\endgroup\$
    – noodle man
    Jan 9, 2023 at 15:37
10
\$\begingroup\$

Excel (ms365), 74 bytes

enter image description here

Formula in B1:

=LET(x,TEXTSPLIT(A1," "),@SORTBY(x,MAP(x,LAMBDA(y,SUM(-(x=y)))),,LEN(x),))

  • TEXTSPLIT(A1," ") - Split input in an horizontal array called 'x';
  • SORTBY(x,MAP(x,LAMBDA(y,SUM(-(x=y)))),,LEN(x),) - 1st Sort by count, then by length (both ascending by default);
  • @ - Use implicit intersection to only return the 1st element in the resulting array.
\$\endgroup\$
2
  • \$\begingroup\$ I think the result should only be love me do. \$\endgroup\$
    – jdt
    Jan 9, 2023 at 14:37
  • \$\begingroup\$ The result should be “Love, love me do” \$\endgroup\$
    – noodle man
    Jan 9, 2023 at 15:32
8
\$\begingroup\$

Python, 49 43 bytes

lambda a:max(sorted(a,key=len),key=a.count)

Attempt This Online!

Takes input as a list of lines

\$\endgroup\$
0
6
\$\begingroup\$

J, 28 17 bytes

{.@\:%@#&>+1#.=/~

Try it online!

-11 thanks to l4m2's idea. Also thanks to l4m2 for pointing out an improvement!

Consider:

Hello, world
Hello, world
I just got to say it, hello world
Goodbye, world
Goodbye, world
Goodbye

In J we take the input as a list of boxed strings:

┌────────────┬────────────┬─────────────────────────────────┬──────────────┬──────────────┬───────┐
│Hello, world│Hello, world│I just got to say it, hello world│Goodbye, world│Goodbye, world│Goodbye│
└────────────┴────────────┴─────────────────────────────────┴──────────────┴──────────────┴───────┘
  • =/~ Create an equality table to see which lines equal other lines:

    1 1 0 0 0 0
    1 1 0 0 0 0
    0 0 1 0 0 0
    0 0 0 1 1 0
    0 0 0 1 1 0
    0 0 0 0 0 1
    
  • 1#. Sum the rows (resulting length is now equal to input length):

    2 2 1 2 2 1
    
  • %@#&>+ And add the reciprocals of each line length elementwise:

    2.083 2.083 1.0303 2.071 2.071 1.1429
    
  • {.@\: Use that result to sort the input descending, and take the first:

    ┌────────────┐
    │Hello, world│
    └────────────┘
    
\$\endgroup\$
6
  • \$\begingroup\$ J always seems very intimidating to me because of its syntax. I’d be interested to know how this solution works. \$\endgroup\$
    – noodle man
    Jan 9, 2023 at 3:38
  • 1
    \$\begingroup\$ @Jacob I updated the solution but added an explanation to the new one. \$\endgroup\$
    – Jonah
    Jan 9, 2023 at 4:40
  • 1
    \$\begingroup\$ Why do you need multiplying input length if len(line) is encoded after decimal point? \$\endgroup\$
    – l4m2
    Jan 9, 2023 at 5:28
  • \$\begingroup\$ tio.run/##7VPfa9RAEH7PX/… \$\endgroup\$
    – l4m2
    Jan 9, 2023 at 5:44
  • \$\begingroup\$ I removed some input line in tio link for lack of space, but you shouldn't \$\endgroup\$
    – l4m2
    Jan 9, 2023 at 5:50
5
\$\begingroup\$

JavaScript (Node.js), 65 61 bytes

x=>x.sort(g=(a,b)=>a?(x.map(b=>b!=a?x:0)+a).length-g(b):0)[0]

Try it online!

Sort <different lines>*(<input length>-1)+<element length>

\$\endgroup\$
1
  • \$\begingroup\$ Nice solution! Didn’t think of doing it this way \$\endgroup\$
    – noodle man
    Jan 9, 2023 at 3:35
5
\$\begingroup\$

PARI/GP, 38 bytes

s->vecsort(s,a->[[1|l<-s,a!=l],#a])[1]

Attempt This Online!

Based on @l4m2's JavaScript answer.

Takes a list of lines.


PARI/GP, 46 bytes

s->vecsort(matreduce(s)~,a->#a[1]*I-a[2])[1,1]

Attempt This Online!

Takes a list of lines.

\$\endgroup\$
5
\$\begingroup\$

Jelly, 6 bytes

Æṃ'LÞḢ

Try it online!

For some reason, Jelly's mode atom insists on vectorising over a list of lines, so we need '

\$\endgroup\$
4
\$\begingroup\$

Japt -g, 9 bytes

Input as an array of lines.

ü üÊÌcÌñÊ

Try it

ü üÊÌcÌñÊ     :Implicit input of array
ü             :Group & sort by value
  ü           :Group & sort by
   Ê          :  Length
    Ì         :Last element
     c        :Flat map
      Ì       :  Last element
       ñ      :Sort by
        Ê     :  Length
              :Implicit output of first element
\$\endgroup\$
5
  • 2
    \$\begingroup\$ Love it! Beats my initial solution ü üÊo mÎñÊ -g (10 bytes) which is pretty close to your 9-byte version. \$\endgroup\$
    – noodle man
    Jan 9, 2023 at 11:44
  • \$\begingroup\$ Nice work, @Jacob; you seem to be getting to grips with Japt nicely. \$\endgroup\$
    – Shaggy
    Jan 9, 2023 at 19:22
  • \$\begingroup\$ Just came back to this, it seems that your 8-byte version is failing test cases. \$\endgroup\$
    – noodle man
    Jan 12, 2023 at 22:44
  • \$\begingroup\$ Fails for "All Star" and "Hello world" \$\endgroup\$
    – noodle man
    Jan 12, 2023 at 22:46
  • 1
    \$\begingroup\$ Thanks, @Jacob. I actually realised that the other night down the pub but forgot to update the next morn'. Will do so tomorrow. \$\endgroup\$
    – Shaggy
    Jan 12, 2023 at 23:36
3
\$\begingroup\$

Vyxal, 11 7 bytes

vOÞMİÞg

Try it Online!

Explained

vOÞMİÞg
vO       # [input.count(item) for item in input]
  ÞM     # indices of maximal items
    İ    # indexed into the input
     Þg  # shortest by length
\$\endgroup\$
3
\$\begingroup\$

Retina 0.8.2, 40 bytes

O`
O^$#`
$.&
O^$#`(.+¶)(?=(\1)*)
$#2
1G`

Try it online! Takes input as a list of newline-terminated strings. Explanation:

O`

Sort everything.

O^$#`
$.&

Sort order of length, then reverse the result. The reverse in the next sort will then bring the shortest line of equal occurrence count back to the front.

O^$#`(.+¶)(?=(\1)*)
$#2

Sort by count of subsequent occurrences. Only one of each of the most frequent lines will actually sort last, but that doesn't matter because we only need one of them. The sort is stable, so the most frequent lines remain sorted in descending order of length. The result is then reversed, bringing the shortest most frequent line to the start.

1G`

Keep only the first line.

\$\endgroup\$
2
\$\begingroup\$

Scala 3, 40 bytes

s=>s.maxBy(l=>(s.count(Set(l)),-l.size))

Returns a tuple from the comparison key, which are ordered lexicographically.

Attempt This Online!

\$\endgroup\$
2
\$\begingroup\$

Factor,  62 61  60 bytes

[ [ length ] sort-with dup histogram '[ _ at ] supremum-by ]

Try it online!

  • [ length ] sort-with Sort by ascending length.
  • dup histogram '[ _ at ] supremum-by Find the most frequent element that is encountered first.
\$\endgroup\$
2
\$\begingroup\$

PowerShell, 49 bytes

($input|group|sort C*,{-$_.Name.Length})[-1].Name

Try it online!

Input comes from the pipeline.

$input is an automatic variable that enumerates all input that is piped to a function.
The lines are passed to the cmdlet Group-Object which groups identical lines into GroupInfo objects with the properties Count and Name (the original line).
The objects are then sorted first by the Count property (how often a line was found), and then the negative (so both Count and Length can be sorted ascending) length of the line.
Then the last element of the sorted array [-1] is taken, and its Name property returned.
Output is implicit.

Will return "Woah, love me do" (same length as "Love, love me do") for the Beatles because of the ascending sorting.

\$\endgroup\$
2
\$\begingroup\$

Go, 140 bytes

import."strings"
func f(s string)(O string){m,M:=-1,make(map[string]int)
for _,l:=range Split(s,"\n"){if M[l]+=1;M[l]>m{O,m=l,M[l]}}
return}

Attempt This Online!

\$\endgroup\$
2
\$\begingroup\$

Charcoal, 23 bytes

WS⊞υι≔Eυ⟦№υι±Lι⟧θ§υ⌕θ⌈θ

Try it online! Link is to verbose version of code. Takes input as a list of newline-terminated strings. Explanation: Port of @mousetail's original Python answer.

WS⊞υι

Input the lines of the song.

≔Eυ⟦№υι±Lι⟧θ

Get the count and negated length of each line.

§υ⌕θ⌈θ

Output the line that has the maximum value of that.

\$\endgroup\$
1
\$\begingroup\$

Pip -r, 15 bytes

@Y-(_Ng)AE#_SKg

The -r flag reads the program arguments from lines of stdin rather than command-line args. Attempt This Online!

Explanation

@Y-(_Ng)AE#_SKg
              g  ; Lines of stdin (due to -r flag)
            SK   ; Sort ascending according to this key function:
    _Ng          ;   Number of occurrences of this line in the input list
  -(   )         ;   Negated
        AE       ;   Placed in a list with
          #_     ;   Length of the line
 Y               ; No-op, necessary for parsing
@                ; Get the first element of the sorted list
\$\endgroup\$
1
\$\begingroup\$

Arturo, 41 bytes

$[a][minimum arrange a=>size=>[size--a&]]

Try it

$[a][                     ; a function taking an argument a
    minimum               ; get minimum element of
    arrange a=>size       ; input sorted by ascending length
    =>[                   ; by
        size              ; length of
        --                ; set difference between
        a                 ; input
        &                 ; current line
    ]                     ; end minimum
]                         ; end function
\$\endgroup\$
0
\$\begingroup\$

Nekomata, 4 bytes

ŢṂaş

Attempt This Online!

ŢṂ      Maximum by count
  aş    Minimum by length
\$\endgroup\$
0
\$\begingroup\$

Mathematica, 59 bytes

Try it online!

f@a_:=Commonest[Select[a,Length[#]==Max[Length/@a]&]][[1]];
\$\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.