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.

Is there any clean way of calculating the range between elements of the second column of a text file using some bash magic? (I'm currently doing this using Python).

Input: File 1

A   1-5
A   17-19
B   1-5
B   4-6

Expected output: File 2

A   1,2,3,4,5,17,18,19
B   1,2,3,4,5,6    

EDIT @Anthon: to cumulate elements I'm using something like this (then calculate the ranges using a for loop)

d_pos= {} 
for row in open('File.txt'): 
    x, y = [ value.strip() for value in row.split('\t')] 
    if x in d_pos:        
        d_pos[x].append(y)    
    else:        
        d_pos[x] = [y]
share|improve this question
1  
I would argue that using Python is the clean way. Are you using a dict of sets to accumulate the elements? –  Anthon Jul 16 at 7:50
    
Please refer to my above edited question for the cumulation code. –  Dovah Jul 16 at 8:04

3 Answers 3

up vote 1 down vote accepted

Straight bash, since you asked (though note I'm using associative arrays, requiring bash 4.0).

The trick is in the brace sequence expansion expression {x..y} which, for integers x any y expands to the inclusive range of values (i.e. [x,y]) as a textual list. We need to throw in an eval too, since brace expansion happens before variable expansion.

declare -A data seen  # explicit associative arrays
while read col range; do
   data[$col]="${data[$col]} $(eval echo {${range/-/..}})"
done <<DATA
A   1-5
A   17-19
B   1-5
B   4-6
DATA

# dump array
#declare -p data

for ii in ${!data[@]}; do
    seen=();  datum=""
    # build list of unique values
    for dd in ${data[$ii]}; do
        (( ${seen[$dd]:-0} )) || datum="$datum $dd"
        let seen[$dd]++
    done

    datum=${datum# }     # drop leading space
    datum=${datum// /,}  # spaces to commas
    printf "%-4s %s\n" "$ii" "$datum"
done

A variation on the sequence expansion is a{x..y}b where "a" is prepended and "b" is appended to each term of the expansion: you can use this to append a "," and alter the datum variable fiddling if desired. Sequence expansion handles increments of 1, or -1 if x > y

You may also need to sort the output: iterating the keys of an associative array has no well-defined order, and you haven't stated if the input ranges are pre-sorted (so I didn't over-complicate the code).

share|improve this answer
    
oops, not quite right yet, I need to fix overlap... –  mr.spuratic Jul 16 at 9:08
1  
updated to output unique values only. –  mr.spuratic Jul 16 at 9:30

Your Python code comes close, but e.g. cannot deal with the overlap of 4 and 5 for item B.

The following handles that correctly using a set() to prevent the overlap, setdefault to eliminate the explicit test it the key already exists in d_pos and .split() on the input line to be less dependent on the \t character and eliminating the explicit .strip():

d_pos= {}
for row in open('File.txt'):
    x, y = [ value for value in row.split()]
    y1, y2 = map(int, y.split('-'))
    d_pos.setdefault(x, set()).update(range (y1, y2+1))
for x in sorted(d_pos):
    print '{}\t{}'.format(x, ','.join(map(str, d_pos[x])))
share|improve this answer
    
Thanks for the tips! ^^ –  Dovah Jul 16 at 8:52
    
@Dovah Your welcome, please be aware that this starts to come close to a generic programming question that would be more appropriate on Stack Overflow –  Anthon Jul 16 at 8:57

If you can use perl:

$ perl -MList::MoreUtils=uniq -anle '
    ($s,$e) = split "-", $F[1];
    push @{$h{$F[0]}}, $s..$e; 
    END {
        $" = ",";
        print "$_   @{[uniq@{$h{$_}}]}" for keys %h;
    }
' file
A   1,2,3,4,5,17,18,19
B   1,2,3,4,5,6

If you don't want to use List::MoreUtils, since when it's not in core, you can do:

$ perl -anle '
    ($s,$e) = split "-", $F[1];
    push @{$h{$F[0]}}, $s..$e; 
    END {
        $" = ",";
        for $k (keys %h) {
            %u=();
            print "$k   @{[grep {!$u{$_}++} @{$h{$k}}]}";
        }
    }
' file
share|improve this answer

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.