Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I have a file with numbers in float format.
I can review them via sort -rn numbers.txt | less
I would like though to be able to "group" them. I.e. easily see how many are in the same range.
To give an example of the file:

30.9695041179657  
30.8851490020752  
30.2127060890198  
29.1361880302429  
26.4587681293488   
25.8535399436951   
25.7361891269684   
25.7305450439453   
25.1068568229675   
24.7598769664764   
24.3106801509857   
24.0782940387726   

I don't care about accuracy. So I would like to know how many 25's are in the file e.g. in this case 4 and 30's etc for all numbers in the file.
So for this example an output like: 3 for 30, 1 for 29, 1 for 26, 4 for 25, 3 for 24.
Is there an easy way to do this?

share|improve this question

7 Answers 7

How about

cut -d. -f1 numbers.txt | sort | uniq -c

Using your example data,

$ cut -d. -f1 numbers.txt | sort | uniq -c
      3 24
      4 25
      1 26
      1 29
      3 30
share|improve this answer
2  
I'd make that sort -n to be on the safe side. +1, though. –  MvG May 2 at 22:51
    
@MyG, more like sort -rn if you've got to choose a particular sort order. –  Stéphane Chazelas May 14 at 15:13

With awk (mawk):

$ awk -F . '{COUNTS[$1]++} END{for(ct in COUNTS) {printf("%d %d time(s)\n", ct, COUNTS[ct])}}' test.txt
30 3 time(s)
24 3 time(s)
25 4 time(s)
26 1 time(s)
29 1 time(s)

The -F sets the field separator (FS) to ., other than that we go through all lines with the {COUNTS[$1]++}, using $1 as the part before the decimal separator (.) and keeping a record of how many times we encounter them in an array named COUNTS.

At the end (END {}) we then dump what we found. As you can see the largest part is the output.

A bit more readable in a file:

{COUNTS[$1]++}
END {
  for(ct in COUNTS)
  {
    printf("%d %d time(s)\n", ct, COUNTS[ct])
  }
}
share|improve this answer
1  
Excellent answer - explaining the steps really improves the quality of the response. –  EightBitTony May 2 at 13:31

You could use awk:

awk '{a[int($1)]++}END{for (i in a) {print a[i], i}}' inputfile

If you want the output to be sorted, pipe the output to sort:

awk '{a[int($1)]++}END{for (i in a) {print a[i], i}}' inputfile | sort -k2

For your sample input, this would produce:

3 24
4 25
1 26
1 29
3 30
share|improve this answer

In perl:

perl -lan -F'\.' -e '$count{$F[0]}++;
    END{ 
        print "$_ --> $count{$_}" for sort {$a <=> $b} keys %count
    }' your_file

Edit

Probably more efficient:

perl -ne '
    $count{int()}++;
    END{ print "$_ --> $count{$_}" for sort {$a <=> $b} keys %count }'
your_file
share|improve this answer
cut -b-2 numbers.txt | sort -n | uniq -c | sort -nr

cut -b-2 picks out the two first characters, sort -nr sorts the results by highest frequency first

Resulting output:

  4 25
  3 30
  3 24
  1 29
  1 26

Or as a python oneliner, just for the heck of it:

python -c 'l = [x[:2] for x in open("numbers.txt").readlines()];print(list(reversed(sorted([(l.count(x),x) for x in set(l)]))))'

Resulting output:

[(4, '25'), (3, '30'), (3, '24'), (1, '29'), (1, '26')]
share|improve this answer

It seems that your file has been sorted, so you can do like this:

$ uniq -c <(perl -pe 's/\.\d*//' file)
      3 30
      1 29
      1 26
      4 25
      3 24

If it has not been sorted:

$ uniq -c <(perl -pe 's/\.\d*//' file | sort -rn)
      3 30
      1 29
      1 26
      4 25
      3 24
share|improve this answer

A GNU coreutils + grep approach:

$ grep -oP '^\d+' file | sort | uniq -c
      3 24
      4 25
      1 26
      1 29
      3 30

The -o flag tells grep to print only the matching portion of the line and the -P activates Perl Compatible Regular Expressions which lets us use \d for numbers. So, the grep will print the longest stretch of digits found at the beginning of the line (i.e. everything up to the first non-digit, the .) and then sort sorts the output and uniq -c counts the number of occurences of each string in the input.

Another Perl approach:

$ perl -lne '/^\d+/ && $k{$&}++; END{print "$k{$_} : $_" for sort keys %k}' file 
3 : 24
4 : 25
1 : 26
1 : 29
3 : 30

The $& is the string matched in the previous match operation, so we store it in a hash (%k) and increment its value by one. The END block will print each number found ($_) and the value it has in the hash ($k{$_}), the number of times it was found.

And a bash (>=version 4) associative arrays approach:

$ while IFS='\.' read -r a b; do (( ll[$a]++ )); done <  file; 
    for i in ${!ll[@]} ; do echo ${ll[$i]} : $i; done
3 : 24
4 : 25
1 : 26
1 : 29
3 : 30

IFS set to . means that input lines are split into records on . so that $a will be the first digits up to the .. We iterate through them and use them as keys to an associative array whose value is increased each time the number is found. Then, once the array has been populated, we iterate through its list of keys (${!ll[@]}) and print each key and its value (the number of times it was seen).

share|improve this answer
    
Note that coreutils is a GNU software package containing a collection of utilities (merged the former GNU sh-utils, textutils, fileutils), grep is not part of that (it's packaged separately), and coreutils has no particular meaning outside of GNU systems. –  Stéphane Chazelas May 14 at 15:10
    
@StephaneChazelas fair enough, edited. Thanks. –  terdon May 14 at 15:13

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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