75
\$\begingroup\$

The other day we were writing sentences with my daughter with a fridge magnet letter. While we were able to make some(I love cat), we didn't have enough letters to make the others (I love you too) due to an insufficient amount of letters o (4)

I then found out that while one set included 3 e letters it had only 2 o letters. Probably inspired by http://en.wikipedia.org/wiki/Letter_frequency this would still not reflect the actual situation "on the fridge".

Problem

Given the text file where each line contains a "sample sentence" one would want to write on the fridge, propose an alphabet set with minimum amount of letters but still sufficient to write each sentence individually.

Note: ignore cases, all magnet letters are capitals anyway.

Input

The file contain newline separated sentences:

hello
i love cat
i love dog
i love mommy
mommy loves daddy

Output

Provide back sorted list of letters, where each letter appears only as many times to be sufficient to write any sentence:

acdddeghillmmmoostvyy

(thanks, isaacg!)

Winner

Shortest implementation (code)

UPDATED: Testing

I have created an extra test and tried with various answers here:

https://gist.github.com/romaninsh/11159751

\$\endgroup\$
12
  • 2
    \$\begingroup\$ There should be a letter v in the output ;) \$\endgroup\$ Commented Apr 10, 2014 at 6:35
  • 47
    \$\begingroup\$ Are we allowed / required to substitute an upside-down M for a W, or a sideways N for a Z? ;-) \$\endgroup\$ Commented Apr 10, 2014 at 11:42
  • 4
    \$\begingroup\$ Basically you can construct any letter using Is. \$\endgroup\$
    – swish
    Commented Apr 10, 2014 at 11:53
  • 7
    \$\begingroup\$ More seriously, when you say "ignore cases", do you mean that we can assume that the input is already all in the same case, or that we must convert it all into the same case? Also, is it OK for the output to include some leading spaces? \$\endgroup\$ Commented Apr 10, 2014 at 12:01
  • 3
    \$\begingroup\$ @Doorknob: _\¯ \$\endgroup\$ Commented Apr 10, 2014 at 13:37

57 Answers 57

18
\$\begingroup\$

GolfScript, 28 / 34 chars

n/:a{|}*{a{.[2$]--}%*$-1=}%$

The 28-character program above assumes that all the input letters are in the same case. If this is not necessarily so, we can force them into upper case by prepending {95&}% to the code, for a total of 34 chars:

{95&}%n/:a{|}*{a{.[2$]--}%*$-1=}%$

Notes:

  • For correct operation, the input must include at least one newline. This will be true for normal text files with newlines at the end of each line, but might not be true if the input consists of just one line with no trailing newline. This could be fixed at the cost of two extra chars, by prepending n+ to the code.

  • The uppercasing used in the 34-character version is really crude — it maps lowercase ASCII letters to their uppercase equivalents (and spaces to NULs), but makes a complete mess of numbers and most punctuation. I'm assuming that the input will not include any such characters.

  • The 28-character version treats all input characters (except newlines and NULs) equally. In particular, if the input contains any spaces, some will also appear in the output; conveniently, they will sort before any other printable ASCII characters. The 34-character version, however, does ignore spaces (because it turns out I can do that without it costing me any extra chars).

Explanation:

  • The optional {95&}% prefix uppercases the input by zeroing out the sixth bit of the ASCII code of each input byte (95 = 64 + 31 = 10111112). This maps lowercase ASCII letters to uppercase, spaces to null bytes, and leaves newlines unchanged.

  • n/ splits the input at newlines, and :a assigns the resulting array into the variable a. Then {|}* computes the set union of the strings in the array, which (assuming that the array has at least two elements) yields a string containing all the unique (non-newline) characters in the input.

  • The following { }% loop then iterates over each of these unique characters. Inside the loop body, the inner loop a{.[2$]--}% iterates over the strings in the array a, removing from each string all characters not equal to the one the outer loop is iterating over.

    The inner loop leaves the ASCII code of the current character on the stack, below the filtered array. We make use of this by repeating the filtered array as many times as indicated by the ASCII code (*) before sorting it ($) and taking the last element (-1=). In effect, this yields the longest string in the filtered array (as they all consist of repeats of the same character, lexicographic sorting just sorts them by length), except if the character has ASCII code zero, in which case it yields nothing.

  • Finally, the $ at the end just sorts the output alphabetically.

\$\endgroup\$
2
  • 3
    \$\begingroup\$ Amazing. TODO: Learn GolfScript! \$\endgroup\$
    – DLosc
    Commented Apr 13, 2014 at 2:38
  • 1
    \$\begingroup\$ You may even reduce it to 26: n/:a{|}*{{{=}+,}+a%$-1=}%$. \$\endgroup\$
    – Howard
    Commented Jun 5, 2014 at 9:41
13
\$\begingroup\$

J - 37 char

Reads from stdin, outputs to console.

dlb#&a.>./+/"2=/&a.tolower;._2[1!:1]3

1!:1]3 is the call to stdin. tolower;._2 performs double duty by splitting up the lines and making them lowercase simultaneously. Then we count how many times a character occurs in each row with +/"2=/&a., and take the pointwise maximum over all lines with >./.

Finally, we pull that many of each character out of the alphabet with #&a.. This includes spaces—all found at the front due to their low ASCII value—so we just delete leading blanks with dlb.

\$\endgroup\$
12
\$\begingroup\$

JavaScript (ECMAScript 6) - 148 139 135 Characters

Version 2:

Updated to use array comprehension:

[a[i][0]for(i in a=[].concat(...s.split('\n').map(x=>x.split(/ */).sort().map((x,i,a)=>x+(a[i-1]==x?++j:j=0)))).sort())if(a[i-1]<a[i])]

Version 1:

[].concat(...s.split('\n').map(x=>x.split(/ */).sort().map((x,i,a)=>x+(a[i-1]==x?++j:j=0)))).sort().filter((x,i,a)=>a[i-1]!=x).map(x=>x[0])

Assumes that:

  • The input string is in the variable s;
  • We can ignore the case of the input (as specified by the question - i.e. it is all in either upper or lower case);
  • The output is an array of characters (which is about as close as JavaScript can get to the OP's requirement of a list of characters); and
  • The output is to be displayed on the console.

With comments:

var l = s.split('\n')             // split the input up into sentences
         .map(x=>x.split(/ */)   // split each sentence up into letters ignoring any
                                  // whitespace
                  .sort()         // sort the letters in each sentence alphabetically
                  .map((x,i,a)=>x+(a[i-1]==x?++j:j=0)))
                                  // append the frequency of previously occurring identical
                                  // letters in the same sentence to each letter.
                                  // I.e. "HELLO WORLD" =>
                                  // ["D0","E0","H0","L0","L1","L2","O0","O1","R0","W0"]
[].concat(...l)                   // Flatten the array of arrays of letters+frequencies
                                  // into a single array.
  .sort()                         // Sort all the letters and appended frequencies
                                  // alphabetically.
  .filter((x,i,a)=>a[i-1]!=x)     // Remove duplicates and return the sorted
  .map(x=>x[0])                   // Get the first letter of each entry (removing the
                                  // frequencies) and return the array.

If you want to:

  • Return it as a string then add .join('') on the end;
  • Take input from a user then replace the s variable with prompt(); or
  • Write it as a function f then add f=s=> to the beginning.

Running:

s="HELLO\nI LOVE CAT\nI LOVE DOG\nI LOVE MOMMY\nMOMMY LOVE DADDY";
[].concat(...s.split('\n').map(x=>x.split(/ */).sort().map((x,i,a)=>x+(a[i-1]==x?++j:j=0)))).sort().filter((x,i,a)=>a[i-1]!=x).map(x=>x[0])

Gives the output:

["A","C","D","D","D","E","G","H","I","L","L","M","M","M","O","O","T","V","Y","Y"]
\$\endgroup\$
4
  • 1
    \$\begingroup\$ Nice! You can save 3 bytes by reducing /\s*/ to / */ and removing the parens around j=0 \$\endgroup\$
    – nderscore
    Commented Apr 11, 2014 at 6:16
  • 1
    \$\begingroup\$ couldn't you use ... instead of apply ? \$\endgroup\$
    – Ven
    Commented Apr 12, 2014 at 21:57
  • \$\begingroup\$ Thanks to you both - that saves 9 characters - The spread (...) operator is one I've not come across before. \$\endgroup\$
    – MT0
    Commented Apr 12, 2014 at 22:30
  • \$\begingroup\$ [].concat(...s.split`N`.map(x=>x.split(/ */).map((x,i,a)=>x+(a[x]=a[x]?++j:j=1)))).sort().map((x,i,a)=>a[i-1]<x?x[0]:'').join``; \$\endgroup\$
    – l4m2
    Commented May 12, 2018 at 5:35
11
\$\begingroup\$

Perl - 46 bytes

#!perl -p
$s=~s/$_//ifor/./g;$s.=uc}for(sort$s=~/\w/g){

Counting the shebang as 1. This is a loose translation of the Ruby solution below.


Ruby 1.8 - 72 bytes

s='';s+=$_.upcase.scan(/./){s.sub!$&,''}while gets;$><<s.scan(/\w/).sort

Input is taken from stdin.

Sample usage:

$ more in.dat
Hello
I love cat
I love dog
I love mommy
Mommy loves daddy

$ ruby fridge-letters.rb < in.dat
ACDDDEGHILLMMMOOSTVYY
\$\endgroup\$
3
  • \$\begingroup\$ Output needs to be sorted. \$\endgroup\$
    – Matt
    Commented Apr 10, 2014 at 7:28
  • \$\begingroup\$ @Matt now fixed. \$\endgroup\$
    – primo
    Commented Apr 10, 2014 at 8:04
  • \$\begingroup\$ Nice. If your Perl is vaguely recent though, you'll want a space between /i and for. \$\endgroup\$
    – tobyink
    Commented Apr 10, 2014 at 13:45
9
\$\begingroup\$

Python - 206 204 199 177 145 129 117 94 88 chars

print(''.join(c*max(l.lower().count(c)for l in open(f))for c in map(chr,range(97,123))))

I wasn't sure how I was supposed to obtain the file name, so at the moment the code assumes that it is contained in a variable named f. Please let me know if I need to change that.

\$\endgroup\$
13
  • 9
    \$\begingroup\$ in the spirit of unix - you could read from stdin. \$\endgroup\$
    – romaninsh
    Commented Apr 10, 2014 at 7:02
  • 5
    \$\begingroup\$ always make the file name one character long... \$\endgroup\$
    – user16402
    Commented Apr 10, 2014 at 12:40
  • 3
    \$\begingroup\$ @Tal I'm also new, but if it saves characters, why not? \$\endgroup\$
    – user16402
    Commented Apr 10, 2014 at 12:54
  • 1
    \$\begingroup\$ By assuming f for the input filename and using uppercase (all magnet letters are uppercase anyway), you can get it down to 91: print(''.join([chr(i)*max(l.upper().count(chr(i))for l in open(f))for i in range(65,91)])) \$\endgroup\$
    – Gabe
    Commented Apr 10, 2014 at 16:30
  • 1
    \$\begingroup\$ @njzk2 well, if we run this in the console, in theory it would just print the result by itself... \$\endgroup\$
    – Tal
    Commented Apr 11, 2014 at 15:54
6
\$\begingroup\$

Ruby 1.9+, 51 (or 58 or 60)

a=*$<
?a.upto(?z){|c|$><<c*a.map{|l|l.count c}.max}

Assumes everything's in lowercase. Case insensitivity costs 7 characters via .upcase, while case insensitivity and lowercase output costs 9 characters via .downcase.

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

R (156, incl. file read)

With table I construct the letter frequency table for each sentence. Then I end up with taking for each letter the maximum value.

a=c();for(w in tolower(read.csv(fn,h=F)$V1))a=c(a,table(strsplit(w,"")[[1]]));a=tapply(seq(a),names(a),function(i)max(a[i]))[-1];cat(rep(names(a),a),sep="")

Ungolfed:

a=c()
words = read.csv(fn,h=F)$V1
for(w in tolower(words))
  a=c(a, table(strsplit(w, "")[[1]]))
a = tapply(seq(a), names(a), function(i) max(a[i]))[-1] ## The -1 excludes the space count.
cat(rep(names(a), a), sep="")

Solution:

acdddeghillmmmoooooostuvyy
\$\endgroup\$
2
  • \$\begingroup\$ @lambruscoAcido you could vectorize the three first lines (of the ungolfed code) which would give you a=unlist(lapply(readLines(fn),function(x)table(strsplit(tolower(x),""))));a=tapply(seq(a),names(a),function(i)max(a[i]))[-1];cat(rep(names(a),a),sep=""), but it is only 3 characters shorter \$\endgroup\$
    – jkd
    Commented Feb 28, 2018 at 20:48
  • \$\begingroup\$ Another approach with only 112 characters would be cat(unlist(sapply(letters,function(i)rep(i,max(sapply(gregexpr(i,readLines(f)),function(x)sum(x>0)))))),sep="") assuming f is the filename \$\endgroup\$
    – jkd
    Commented Feb 28, 2018 at 20:49
4
\$\begingroup\$

Haskell, 109 108

import Data.List
import Data.Char
main=interact$sort.filter(/=' ').foldl1(\x y->x++(y\\x)).lines.map toLower

The program reads from stdin and writes to sdtout.

It is quite straightforward: it breaks the string into a list of lines, and rebuilds it by iterating on the list and adding the new letters contained in each line.

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

Perl 6: 56 53 characters; 58 55 bytes

say |sort
([∪] lines.map:{bag comb /\S/,.lc}).pick(*)

For each line, this combs through it for the non-space characters of the lower-cased string (comb /\S/,.lc), and makes a Bag, or a collection of each character and how many times it occurs. [∪] takes the union of the Bags over all the lines, which gets the max number of times the character occurred. .pick(*) is hack-y here, but it's the shortest way to get all the characters from the Bag replicated by the number of times it occurred.

EDIT: To see if it would be shorter, I tried translating histocrat's Ruby answer. It is 63 characters, but I still very much like the approach:

$!=lines».lc;->$c{print $c x max $!.map:{+m:g/$c/}} for"a".."z"
\$\endgroup\$
3
\$\begingroup\$

Javascript, 261 chars

eval('s=prompt().toUpperCase().split("\\n");Z=[########0,0];H=Z.slice();s@r){h=Z.slice();r.split("")@c){if(c.match(/\\w/))h[c.charCodeAt(0)-65]++});H=H@V,i){return V>h[i]?V:h[i]})});s="";H@n,i){s+=Array(n+1).join(String.fromCharCode(i+97))});s'.replace(/@/g,".map(function(").replace(/#/g,"0,0,0,"))

Remove the eval(...) and execute to get the real code; this is (somewhat) compressed.

s multi-functions as the array of lines and as the outputted string, h contains the histogram of the letters per line and H contains the histogram with the maximum values up until now. It's case-insensitive, and just ignores anything but a-z and A-Z (I think... JS arrays are sometimes weird).

Now correct :)

\$\endgroup\$
3
  • \$\begingroup\$ This just totals the characters, not quite what the question asked. The letters should be totalled to be the bare minimum set to form any single sentence in the input, not all of them. I quite like your approach to prevent the need to sort the output though. \$\endgroup\$
    – Matt
    Commented Apr 10, 2014 at 12:11
  • \$\begingroup\$ @Matt oh that's right... I'll fix it later. Haven't really got time right now. \$\endgroup\$
    – tomsmeding
    Commented Apr 10, 2014 at 15:57
  • 1
    \$\begingroup\$ Wondered what was going on with the @ until I got to the end. I like it :) \$\endgroup\$
    – Matt
    Commented Apr 10, 2014 at 23:20
3
\$\begingroup\$

Haskell, 183 162 159

Assuming the file is in file.txt!

import Data.Char
import Data.List
main=readFile"file.txt">>=putStr.concat.tail.map(tail.maximum).transpose.map(group.sort.(++' ':['a'..'z'])).lines.map toLower

If file.txt contains, for example

abcde
abcdef
aaf

The script will output

aabcdef

Basically I'm appending the whole alphabet to each line, so that when grouping and sorting, I'm sure I'll end up with a list that contains 27 elements. Next, I transpose the "frequency table", so that each row in this array consists of the frequencies of a single letter in each line, e.g. ["a","","aaa","aa","aaaa"]. I then choose the maximum of each array (which works just like I want because of how the Ord-instance of Strings work), and drop the letter that I appended at the start, get rid of the spaces, and output the result.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Instead of drop 1, just use tail \$\endgroup\$
    – Bergi
    Commented Apr 10, 2014 at 23:55
  • \$\begingroup\$ @Bergi Haha derp, thanks! I changed it in the post. \$\endgroup\$
    – Flonk
    Commented Apr 11, 2014 at 11:29
3
\$\begingroup\$

C, 99 chars

t[256];main(c){for(--*t;++t[1+tolower(getchar())];);for(c=97;c<123;c++)while(t[c]--)putchar(c-1);}

It crashes if less than one newline is provided. I think it could be fixed quite easily.

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

kdb (q/k): 59 characters:

d:.Q.a! 26#0
.z.pi:{d|:.Q.a##:'=_y}.z.exit:{-1@,/.:[d]#'!:d}
  • generate pre-sorted seed dictionary from alphabet .Q.a
  • process each line of input, convert to lowercase, group into dictionary, count each element, take alphabetic characters from result (I.e. prune spaces, newlines, etc at this stage) and use max-assign to global d to keep a running total.
  • define exit handler, which gets passed in to .z.pi to save a delimiter but otherwise unused there. Take from each key-value to generate list of characters, flatten and finally print to stdout.

-1 adds a newline, using 1 would save a character but does not generate the output specified. Wish I could get rid of the .z.pi / .z.exit boilerplate, which would remove 14 characters.

Edit: avoid use of inter/asc by using seed dictionary.

\$\endgroup\$
3
\$\begingroup\$

Perl, 46

for$:(a..z){$a[ord$:]|=$:x s/$://gi}}{print@a

Here's another Perl solution, reads from STDIN, requires -n switch (+1 to count), ties with primo's score but runs without complaints :-). It exploits the fact that bitwise or's result has longer string argument's length.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ tried with my test and it worked great. \$\endgroup\$
    – romaninsh
    Commented Apr 21, 2014 at 23:37
3
\$\begingroup\$

I'm adding my own solution:

Bash - 72

Assumes that input is in file "i"

for x in {A..Z};do echo -n `cat i|sed "s/[^$x]//g"|sort -r|head -1`;done

Explanation

For each possible letter, filters it out only from input file resulting in something like this:

AAA
A
A

AAAA

A
AAAAAAAAAAAAAAAA

Then result is sorted and the longest line is selected. echo -n is there to remove newlines.

\$\endgroup\$
3
\$\begingroup\$

Bash, 171 159 158, 138 with junk output

Requires lowercase-only input. Assumes that the file is called _ (underscore). Maximum of 26 lines in the input file due to the annoying filenames which split creates (xaa, xab... xaz, ???).

In bash, {a..z} outputs a b c d e f ....

touch {a..z}
split _ -1
for l in {a..z}
do for s in {a..z}
do grep -so $l xa$s>b$l
if [ `wc -l<b$l` -ge `wc -l<$l` ]
then mv b$l $l
fi
done
tr -d '\n'<$l
done

Sample output

acdddeghillmmmoostvyy

Explanation

touch {a..z}

Create files that we will be reading from later on so that bash doesn't complain that they don't exist. If you remove this line you will save 13 chars but get a lot of junk output.

split _ -1

Split the input file into sections, each storing 1 line. The files this command creates are named xaa, xab, xac and so on, I have no idea why.

for l in {a..z}
do for s in {a..z}

For each letter $l read through all lines stored in files xa$s.

do grep -so $l xa$s>b$l

Remove the -s switch to save 1 char and get a lot of junk output. It prevents grep from complaining about nonexistent files (will occur unless you have 26 lines of input). This processes the file xa$s, removing anything but occurences of $l, and sending output to the file b$l. So "i love mommy" becomes "mmm" with new lines after each letter when $l is m.

if [ `wc -l<b$l` -ge `wc -l<$l` ]

If the number of lines in the file we just created is greater than or equal to (i.e. more letters since there is one letter per line) the number of lines in our highest result so far (stored in $l)...

then mv b$l $l

...store our new record in the file $l. At the end of this loop, when we have gone through all the lines, the file $l will store x lines each containing the letter $l, where x is the highest number of occurences of that letter in a single line.

fi
done
tr -d '\n'<$l

Output the contents of our file for that particular letter, removing new lines. If you don't want to remove the new lines, change the line with tr to echo $l, saving 6 chars.

done
\$\endgroup\$
3
  • \$\begingroup\$ Tried with GNU bash, version 3.2.51 (apple), but file '-l1aa' in a current folder containing input data.. \$\endgroup\$
    – romaninsh
    Commented Apr 21, 2014 at 23:33
  • \$\begingroup\$ @romaninsh It might be that you have a different version of split (from coreutils). I am currently running GNU bash 4.3.8 and GNU coreutils 8.21 on Ubuntu 14.04 and it works fine (it also worked on Ubuntu 13.10 before I upgraded). However, I did have to place the program and the input file in a separate directory for it to work properly - I suspect this was only because of the millions of junk files in my home folder. \$\endgroup\$
    – user16402
    Commented Apr 22, 2014 at 7:03
  • \$\begingroup\$ @romaninsh in fact, if you look at the exact command in the script: split _ -l1 and you notice that your input is being saved to -l1aa, I think that your version of split isn't recognising -l1 as an option and instead taking it to be a prefix for output. Try putting a space between -l and 1, or putting --lines=1, or just -1 (this appears to be an obsolete and more golfy syntax which I will now update the post with). \$\endgroup\$
    – user16402
    Commented Apr 22, 2014 at 7:06
3
\$\begingroup\$

C#, 172 bytes

var x="";foreach(var i in File.ReadAllText(t).ToLower().Split('\r','\n'))foreach(var j in i)if(x.Count(c=>c==j)<i.Count(c=>c==j))x+=j;string.Concat(x.OrderBy(o=>o)).Trim();
\$\endgroup\$
1
  • \$\begingroup\$ Clever ...clever ... I thought about playing with linq, but doubt it'll be as short as these contorted foreachs :) \$\endgroup\$
    – Noctis
    Commented Apr 14, 2014 at 6:42
3
\$\begingroup\$

05AB1E, 12 10 8 bytes

Aεδ¢ày×?

-2 bytes thanks to @ovs, which also allowed -2 more bytes.

Input taken as a list of lowercase strings. (Would require 1 additional byte if taking the input newline-delimited is mandatory.)

Try it online.

Explanation:

A        # Push the lowercase alphabet
 ε       # For-each over the letters:
  δ¢     #  Count how many times this letter occurs in each string of the (implicit)
         #  input-list
    à    #  Pop and leave the maximum of these counts
     y×  #  Repeat the current letter that many times
       ? #  And print it (without newline)
\$\endgroup\$
2
  • 1
    \$\begingroup\$ ASδ¢ saves a byte, and Aεδ¢ày×}J (or Avyδ¢àFy?) comes in at 10 bytes. \$\endgroup\$
    – ovs
    Commented Sep 16, 2021 at 14:51
  • \$\begingroup\$ @ovs Thanks! And it can actually be just Aεδ¢ày×? for an additional -2 (you will need the --no-lazy flag, though) :) \$\endgroup\$ Commented Sep 16, 2021 at 17:08
2
\$\begingroup\$

Python 2 - 129

Idea from @Tal

a,r=[0]*26,range(26)
for l in open('f'):a=[max(a[i],l.lower().count(chr(i+97)))for i in r]
print''.join(chr(i+97)*a[i]for i in r)

A couple more ways to do the same thing in the same number of characters:

a=[0]*26
b='(chr(i+97)))for i in range(26)'
exec'for l in open("f"):a=[max(a[i],l.lower().count'+b+']\nprint"".join(a[i]*('+b+')'

a=[0]*26
b='(chr(i+97)))for i in range(26))'
exec'for l in open("f"):a=list(max(a[i],l.lower().count'+b+'\nprint"".join(a[i]*('+b

This assumes the file is saved as f in an accessible directory. This program is directly runable, with no extra input necessary.

\$\endgroup\$
1
  • \$\begingroup\$ Why the down vote? Sorry if I did something wrong. \$\endgroup\$
    – isaacg
    Commented Apr 10, 2014 at 7:53
2
\$\begingroup\$

Mathematica v10 - 110

It's not out yet, but reading new documentation very carefully, I think this should work:

StringJoin@MapIndexed[#2~Table~{#1}&,Rest@Merge[Counts/@Characters@StringSplit[ToLowerCase@Input[],"\n"],Max]]
\$\endgroup\$
2
\$\begingroup\$

Scala, 125 characters

val i=""::io.Source.stdin.getLines.toList.map(_.toLowerCase);println('a'to'z'map(c=>(""+c)*i.map(_.count(_==c)).max)mkString)

First I read the input, converting it into lower case and adding one empty line.

Then for each letter from a to z I repeat that letter maximum number of times it appears in any of the lines (that's why I need the empty line: max cannot be called on an enpty input). Then I just join the results and print to the output.

To read from a file, replace stdin with fromFile("FILENAME"), increasing the size of the code to 132 characters + file name length.

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

JavaScript (ES5) 141 bytes

Assuming variable s is the input string with no case-checking requirements and array output:

for(a in s=s[o=_='',y='split']('\n'))for(i=0;x=s[a][i++];)o+=x!=0&&(l=s[a][y](x).length-~-o[y](x).length)>0?Array(l).join(x):_;o[y](_).sort()
\$\endgroup\$
7
  • \$\begingroup\$ I tested your solution and was looking inside "o" for an output, but it does not seem to be sorted properly. (see gist.github.com/romaninsh/11159751) \$\endgroup\$
    – romaninsh
    Commented Apr 21, 2014 at 23:28
  • \$\begingroup\$ @romaninsh the output I see in your gist looks properly sorted \$\endgroup\$
    – nderscore
    Commented Apr 21, 2014 at 23:41
  • \$\begingroup\$ Yes, that's a reference / correct output. When I tried your code, I have got this: gist.github.com/romaninsh/11161018 \$\endgroup\$
    – romaninsh
    Commented Apr 22, 2014 at 0:16
  • \$\begingroup\$ Apologies if I executed your example incorrectly. \$\endgroup\$
    – romaninsh
    Commented Apr 22, 2014 at 0:18
  • \$\begingroup\$ @romaninsh ah, I had intended for it to just be run in the browser's console. Here's a version reformatted that works on node: gist.github.com/nderscore/96aa888c77d275c26c15 \$\endgroup\$
    – nderscore
    Commented Apr 22, 2014 at 0:27
2
\$\begingroup\$

PowerShell - 141

Reads text from a file named 'a'.

$x=@{}
gc a|%{[char[]]$_|group|%{$c=$_.name.tolower().trim()
$n=$_.count;$x[$c]=($n,$x[$c])[$n-lt$x[$c]]}}
($x.Keys|sort|%{$_*$x[$_]})-join""
\$\endgroup\$
2
\$\begingroup\$

Groovy, 113/127 102/116 characters

Assuming the file is all in one case (102 chars):

t=new File('f').text;t.findAll('[A-Z]').unique().sort().each{c->print c*t.readLines()*.count(c).max()}

Assuming the file is in mixed case (116 chars):

t=new File('f').text.toUpperCase();t.findAll('[A-Z]').unique().sort().each{c->print c*t.readLines()*.count(c).max()}

Basically:

  • t=new File('f').text To get the text of the file.
  • t.findAll('[A-Z]').unique().sort().each{c-> To get the unique characters, sort them, and iterate.
  • print c*t.readLines()*.count(c).max() Get the max occurances in a single line and print the character that many times.
\$\endgroup\$
2
\$\begingroup\$

Bash (mostly awk) - 172 163 157

awk -v FS="" '{delete l;for(i=1;i<=NF;i++)l[toupper($i)]++;for(i in l)o[i]=(o[i]>l[i]?o[i]:l[i])}END{for(i in o)for(j=0;j<o[i];j++)print i}'|sort|tr -d ' \n'

Text needs to be piped in to awk (or specified as a file).

Example Input

Hello
I love cat
I love dog
I love mommy
Mommy loves daddy

Example Output

ACDDDEGHILLMMMOOSTVYY

PHP (probably could be better) - 174 210

$o=array();foreach(explode("\n",$s) as $a){$l=array();$i=0;while($i<strlen($a)){$k=ucfirst($a[$i++]);if($k==' ')continue;$o[$k]=max($o[$k],++$l[$k]);}}ksort($o);foreach($o as $k=>$v)for($i=0;$i<$v;$i++)echo $k;

Assumes that the string is contained in the variable $s

Example Input

Hello
I love cat
I love dog
I love mommy
Mommy loves daddy

Example Output

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

I realize this probably isn't the most efficient answer, but I wanted to try and solve the problem anyways. Here's my ObjC variation:

- (NSArray *) lettersNeededForString:(NSString *)sourceString {
    sourceString = [sourceString stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    sourceString = [sourceString stringByReplacingOccurrencesOfString:@" " withString:@""];
    const char * sourceChars = sourceString.UTF8String;
    NSMutableArray * arr = [NSMutableArray new];
    for (int i = 0; i < sourceString.length; i++) {
        [arr addObject:[NSString stringWithFormat:@"%c", sourceChars[i]]];
    }
    return [arr sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
}    

Then you can call it for whatever string:

NSArray * letters = [self lettersNeededForString:@"Hello\nI love cat\nI love dog\nI love mommy\nMommy loves daddy"];
NSLog(@"%@",letters);

I was thinking about applications with larger amounts of text and I'd rather not have to count my array. For this, I added to the method to get this:

- (NSDictionary *) numberOfLettersNeededFromString:(NSString *)sourceString {

    sourceString = [sourceString stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    sourceString = [sourceString stringByReplacingOccurrencesOfString:@" " withString:@""];
    const char * sourceChars = sourceString.UTF8String;
    NSMutableArray * arr = [NSMutableArray new];
    for (int i = 0; i < sourceString.length; i++) {
        [arr addObject:[NSString stringWithFormat:@"%c", sourceChars[i]]];
    }

    static NSString * alphabet = @"abcdefghijklmnopqrstuvwxyz";
    NSMutableDictionary * masterDictionary = [NSMutableDictionary new];
    for (int i = 0; i < alphabet.length; i++) {
        NSString * alphabetLetter = [alphabet substringWithRange:NSMakeRange(i, 1)];
        NSIndexSet * indexes = [arr indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
            if ([[(NSString *)obj lowercaseString] isEqualToString:alphabetLetter]) {
                return YES;
            }
            else {
                return NO;
            }
        }];

        masterDictionary[alphabetLetter] = @(indexes.count);
    }

    return masterDictionary;
}

Run like:

NSDictionary * lettersNeeded = [self numberOfLettersNeededFromString:@"Hello\nI love cat\nI love dog\nI love mommy\nMommy loves daddy"];
NSLog(@"%@", lettersNeeded);

Will give you:

{ a = 2; b = 0; c = 1; d = 4; e = 5; f = 0; g = 1; h = 1; i = 3; j = 0; k = 0; l = 6; m = 6; n = 0; o = 8; p = 0; q = 0; r = 0; s = 1; t = 1; u = 0; v = 4; w = 0; x = 0; y = 3; z = 0; }

Which I think is better if I had a very large amount of text and I just needed to know how many of each letter I would need.

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

K, 34

{`$a@<a:,/(.:a)#'!:a:|/#:''=:'0:x}
\$\endgroup\$
2
\$\begingroup\$

Haskell, 41 bytes

f s=do c<-['a'..];maximum$filter(==c)<$>s

Try it online! f takes a list of lines as input.

Add main=interact$f.lines to get a full program for a total of 63 bytes.

Explanation

f s=                -- input s is the list of lines
 do c<-['a'..];     -- for each char c starting at c='a', concatenate the following strings
    filter(==c)<$>s -- for each line in s remove all characters except for the current c
   maximum$         -- take the longest of those strings
\$\endgroup\$
2
\$\begingroup\$

Python 2, 154 bytes

import collections
c = collections.Counter()
for line in open("input.txt"):
    c |= collections.Counter(line.upper())
print "".join(sorted(c.elements()))
\$\endgroup\$
8
  • \$\begingroup\$ Welcome to PCG! This site supports Markdown syntax, which you can use to format your code, so that it appears nice: just indent each line of code 4 spaces. \$\endgroup\$ Commented Apr 10, 2014 at 22:02
  • \$\begingroup\$ You'll need to add the characters necessary to import collections. \$\endgroup\$
    – isaacg
    Commented Apr 11, 2014 at 0:14
  • 1
    \$\begingroup\$ does not answer the question, as you need the minimum amount of letters to write each sentence individually. In your code, you output the number of letters needed to write all sentences at the same time. \$\endgroup\$
    – njzk2
    Commented Apr 11, 2014 at 14:35
  • \$\begingroup\$ You're missing an s at the end of the import statement and the with block lacks indentation. And since this is code golf, it would benefit you greatly to remove unnecessary whitespace where possible. \$\endgroup\$
    – ashastral
    Commented Apr 13, 2014 at 1:56
  • \$\begingroup\$ since this is code golf, remove the with statement (just loop over a call to open) and I don't think the elements need sorting. \$\endgroup\$ Commented Apr 13, 2014 at 19:04
2
\$\begingroup\$

C, 298 bytes

char c;
int j,n;
char C[26];
char D[26];
int main()
{
char a='a';
while((c=getchar())>=0)
{
c=tolower(c);
if(c>=a&&c<='z'){j=c-a;D[j]++;}
if(c=='\n'){
for(j=0;j<26;j++){
if(D[j]>C[j])
{C[j]=D[j];}
D[j]=0;
}
}
}
for(j=0;j<26;j++)
{
n=C[j];
while(n--)
{
putchar(a+j);
}
}
}

Array D holds tally of letters for each line, then the maximum count is copied to C.

Note: I put my answer yesterday but is now not listed, maybe I pressed delete instead of edit by mistake?

\$\endgroup\$
2
  • \$\begingroup\$ It's only 271 bytes. You also have a lot of extraneous newlines. Also, you can you can omit the int from int main() and int j,n;. \$\endgroup\$
    – user344
    Commented Apr 21, 2014 at 19:59
  • \$\begingroup\$ Also, your previous answer is still there. \$\endgroup\$
    – user344
    Commented Apr 21, 2014 at 20:01

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.