Используя скрипт bash (Ubuntu 16.04), я пытаюсь сравнить 2 списка диапазонов: совпадает ли любое число в любом из диапазонов в file1 с любым числом в любом из диапазонов в file2? Если это так, выведите строку во втором файле. Здесь у каждого диапазона есть 2 столбца с разделителями табуляции (в файле 1 строка 1 представляет диапазон 1-4, то есть 1, 2, 3, 4). Реальные файлы довольно большие.

File1 :

1 4
5 7 
8 11
12 15

File2 :

3 4 
8 13 
20 24

Пожеланная выходная мощность:

3 4 
8 13

Моя лучшая попытка была:

awk 'NR=FNR { x[$1] = $1+0; y[$2] = $2+0; next}; 
{for (i in x) {if (x[i] > $1+0); then
{for (i in y) {if (y[i] <$2+0); then            
{print $1, $2}}}}}' file1 file2 > output.txt

Это возвращает пустой файл.

Я думаю, что сценарий должен будет включать сравнения диапазонов с использованием условий if-then и перебирать каждую строку в обоих файлах. Я нашел примеры каждой концепции, но не могу понять, как их объединить.

Любая помощь приветствуется!

4
Marla 4 Сен 2017 в 12:19

6 ответов

Лучший ответ

Конечно, это зависит от размера ваших файлов. Если они недостаточно велики, чтобы исчерпать память, вы можете попробовать это 100% решение для bash:

declare -a min=() # array of lower bounds of ranges
declare -a max=() # array of upper bounds of ranges

# read ranges in second file, store then in arrays min and max
while read a b; do
    min+=( "$a" );
    max+=( "$b" );
done < file2

# read ranges in first file    
while read a b; do
    # loop over indexes of min (and max) array
    for i in "${!min[@]}"; do
        if (( max[i] >= a && min[i] <= b )); then # if ranges overlap
            echo "${min[i]} ${max[i]}" # print range
            unset min[i] max[i]        # performance optimization
        fi
    done
done < file1

Это только отправная точка. Есть много возможных улучшений производительности / памяти. Но они сильно зависят от размеров ваших файлов и распределения ваших диапазонов.

РЕДАКТИРОВАТЬ 1 : улучшен тест на перекрытие диапазона.

РЕДАКТИРОВАТЬ 2 : повторно использовал превосходную оптимизацию, предложенную RomanPerekhrest (не заданы уже напечатанные диапазоны от file2). Производительность должна быть лучше, когда вероятность перекрытия диапазонов высока.

РЕДАКТИРОВАТЬ 3 : сравнение производительности с версией awk, предложенной RomanPerekhrest (после исправления начальных мелких ошибок): awk в 10-20 раз быстрее, чем bash по этой проблеме. Если производительность важна и вы колеблетесь между awk и bash, предпочтите:

awk 'NR == FNR { a[FNR] = $1; b[FNR] = $2; next; }
    { for (i in a)
          if ($1 <= b[i] && a[i] <= $2) {
              print a[i], b[i]; delete a[i]; delete b[i];
          } 
    }' file2 file1
4
Renaud Pacalet 7 Сен 2017 в 15:08

awk :

awk 'NR==FNR{ a[$1]=$2; next }
     { for(i in a) 
           if (($1>=i+0 && $1<=a[i]) || ($2<=a[i] && $2>=i+0)) { 
               print i,a[i]; delete a[i];
           } 
     }' file2 file1

Выход:

3 4
8 13
2
RomanPerekhrest 4 Сен 2017 в 14:32
awk 'FNR == 1 && NR == 1 { file=1 } FNR == 1 && NR != 1 { file=2 } file ==1 { for (q=1;q<=NF;q++) { nums[$q]=$0} } file == 2 { for ( p=1;p<=NF;p++) { for (i in nums) { if (i == $p) { print $0 } } } }' file1 file2

Сломать:

FNR == 1 && NR == 1 { 
                  file=1 
                  }
FNR == 1 && NR != 1 { 
                  file=2 
                  }
file == 1 { 
           for (q=1;q<=NF;q++) { 
                      nums[$q]=$0
                } 
          }
file == 2 {
      for ( p=1;p<=NF;p++) {
         for (i in nums) {
             if (i == $p) {
                      print $0
             }
          }
      }
}

Обычно мы устанавливаем file = 1, когда обрабатываем первый файл, и file = 2, когда обрабатываем второй файл. Когда мы находимся в первом файле, прочитайте строку в массив, связанный с каждым полем строки. Когда мы находимся во втором файле, обработайте массив (числа) и проверьте, есть ли запись для каждого поля в строке. Если есть, распечатайте его.

1
Raman Sailopal 4 Сен 2017 в 10:30

Для GNU awk, поскольку я контролирую порядок сканирования for для оптимизации времени:

$ cat program.awk
BEGIN {
    PROCINFO["sorted_in"]="@ind_num_desc"
}
NR==FNR {                                         # hash file1 to a
    if(($2 in a==0) || $1<a[$2])                  # avoid collisions
        a[$2]=$1
    next
}
{
    for(i in a) {                                 # in desc order
        # print "DEBUG: For:",$0 ":", a[i], i     # remove # for debug
        if(i+0>$1) {                              # next after
            if($1<=i+0 && a[i]<=$2) {
                print
                next
            }
        }
        else
            next
    }
}

Тестовые данные:

$ cat file1
0 3 # testing for completely overlapping ranges
1 4
5 7 
8 11
12 15
$ cat file2
1 2 # testing for completely overlapping ranges
3 4 
8 13 
20 24

Выход:

$ awk -f program.awk file1 file2
1 2
3 4 
8 13 

И

$ awk -f program.awk file2 file1
0 3
1 4
8 11
12 15
1
James Brown 6 Сен 2017 в 06:58

Если Perl-решение предпочтительнее, то под одной строкой будет работать

/tmp> cat marla1.txt
1 4
5 7
8 11
12 15
/tmp> cat marla2.txt
3 4
8 13
20 24
/tmp> perl -lane ' BEGIN { %kv=map{split(/\s+/)} qx(cat marla2.txt) } { foreach(keys %kv) { if($F[0]==$_ or $F[1]==$kv{$_}) { print "$_ $kv{$_}" }} } ' marla1.txt
3 4
8 13
/tmp>
0
stack0114106 10 Дек 2018 в 13:26

Если диапазоны упорядочены в соответствии с их нижними границами, мы можем использовать это для повышения эффективности алгоритмов. Идея состоит в том, чтобы попеременно проходить через диапазоны в file1 и file2. Точнее, когда у нас есть определенный диапазон R в file2, мы берем все новые и новые диапазоны в file1, пока не узнаем, перекрываются ли они с R . Как только мы это узнаем, мы переключаемся на следующий диапазон в file2.

#!/bin/bash

exec 3< "$1"  # file whose ranges are checked for overlap with those ...
exec 4< "$2"  # ... from this file, and if so, are written to stdout

l4=-1  # lower bound of current range from file 2 
u4=-1  # upper bound
# initialized with -1 so the first range is read on the first iteration

echo "Ranges in $1 that intersect any ranges in $2:"
while read l3 u3; do  # read next range from file 1
  if (( u4 >= l3 )); then
    (( l4 <= u3 )) && echo "$l3 $u3"
  else  # the upper bound from file 2 is below the lower bound from file 1, so ...
    while read l4 u4; do  # ... we read further ranges from file 2 until ...
      if (( u4 >= l3 )); then  # ... their upper bound is high enough
        (( l4 <= u3 )) && echo "$l3 $u3"
        break
      fi
    done <&4
  fi
done <&3

Сценарий можно вызвать с помощью ./script.sh file2 file1

0
Stefan Hamcke 7 Янв 2019 в 20:35