2012-05-09 3 views
2

У меня есть 2 файла данных: file01 и file02. В обоих наборах полей данных: (i) идентификатор; (ii) числовая ссылка; (iii) долгота; и (iv) широта. Для каждой строки в file01 я хочу найти данные в file02 с той же числовой ссылкой, а затем найти идентификатор в file02, который является ближайшим к идентификатору в file01.Слияние двух наборов данных с использованием AWK

я могу получить это, если я прохожу вручную значение от file01 к программе AWK, используя следующий код:

awk 'function acos(x) { return atan2(sqrt(1-x*x), x) } 
BEGIN {pi=3.14159; 
     ndist=999999999.1; 
     date=1001; 
     lo1=-1.20; lg1=lo1*(pi/180); 
     la1=30.31; lt1=la1*(pi/180) 
      } 
{if($2==date) {ws=$1; 
       lg2=$3*(pi/180); 
       lt2=$4*(pi/180); 
       dist= 6378.7 * acos(sin(lt1)*sin(lt2) + cos(lt1)*cos(lt2)*cos(lg2-lg1)); 
       if(dist < ndist) {ndist=dist; ws0=ws}}} 
END {print(ws0,ndist)}' file02

Как вы видите, date, lo1 и la1 в BEGIN заявления являются значением в 1-я строка file01 (см. Ниже для файлов данных). Мой вопрос в том, могу ли я сделать это сразу, поэтому каждый раз, когда я читаю строку в file01, я получаю ближайший идентификатор и расстояние и добавляю к данным строки в file01. Я не знаю, может ли какая-нибудь команда оболочки выполнить это проще, возможно, используя канал.

Пример этих двух файлов данных и желаемого выхода являются:

=== file01 ===

A 1001 -1.2 30.31 
A 1002 -1.2 30.31 
B 1002 -1.8 30.82 
B 1003 -1.8 30.82 
C 1001 -2.1 28.55 

=== file02 ===

ws1 1000 -1.3 29.01 
ws1 1001 -1.3 29.01 
ws1 1002 -1.3 29.01 
ws1 1003 -1.3 29.01 
ws1 1004 -1.3 29.01 
ws1 1005 -1.3 29.01 
ws2 1000 -1.5 30.12 
ws2 1002 -1.5 30.12 
ws2 1003 -1.5 30.12 
ws2 1004 -1.5 30.12 
ws2 1005 -1.5 30.12 
ws3 1000 -1.7 29.55 
ws3 1001 -1.7 29.55 
ws3 1002 -1.7 29.55 
ws3 1003 -1.7 29.55 
ws3 1004 -1.7 29.55 
ws3 1005 -1.7 29.55 
ws4 1000 -1.9 30.33 
ws4 1001 -1.9 30.33 
ws4 1002 -1.9 30.33 
ws4 1003 -1.9 30.33 
ws4 1004 -1.9 30.33 
ws4 1005 -1.9 30.33 

= == выходной файл ===

A 1001 -1.2 30.31 ws4 67.308 
A 1002 -1.2 30.31 ws2 35.783 
B 1002 -1.8 30.82 ws4 55.387 
B 1003 -1.8 30.82 ws4 55.387 
C 1001 -2.1 28.55 ws1 85.369 

EDIT # 1: Учитывая предложение по @Eran, я написал следующий код:

join -j 2 < (sort -k 2,2 file01) < (sort -k 2,2 file02) | 
awk 'function acos(x) { return atan2(sqrt(1-x*x), x) } 
    BEGIN {pi=3.14159} 

    {if (last != $1 $2) 
     {print NR, id,r,lon,lat,ws0,ndist; 
      last = $1 $2; 
      ndist=999999999.1 

     } else { 

      lg1=$3*(pi/180); 
      lt1=$4*(pi/180); 
      lg2=$6*(pi/180); 
      lt2=$7*(pi/180); 
      dist= 6378.7 * acos(sin(lt1)*sin(lt2) + cos(lt1)*cos(lt2)*cos(lg2-lg1)); 
      if(dist< ndist) {ndist=dist; ws0=$5} 
      id=$2;r=$1;lon=$3;lat=$4 

      } 
    }' 

Выход из этого сценария является:

1  
4 A 1001 -1.2 30.31 ws4 67.3078 
7 C 1001 -2.0 28.55 ws3 115.094 
11 A 1002 -1.2 30.31 ws2 35.7827 
15 B 1002 -1.8 30.82 ws4 55.387 

EDIT # 2: Используя следующее å предложение @Dennis (с некоторыми изменениями) У меня есть желаемый результат. Сценарий awk выглядит следующим образом:


awk 'function acos(x) { return atan2(sqrt(1-x*x), x) } 
    BEGIN {pi=3.14159} 
    NR==FNR {c++; a1[c]=$1;a2[c]=$2;a3[c]=$3;a4[c]=$4; next} 
      {d++; b1[d]=$1;b2[d]=$2;b3[d]=$3;b4[d]=$4} 

    END { 
    for(k=1;k<=c;k++) { 
     lg1=a3[k]*(pi/180); 
     lt1=a4[k]*(pi/180); 
     ndist=999999999.1; 
     for(l=1;l<=d;l++) { 
      if(b2[l]==a2[k]) {kk=b2[l]; 
       lg2=b3[l]*(pi/180); 
       lt2=b4[l]*(pi/180); 
       dist= 6378.7 * acos(sin(lt1)*sin(lt2) + cos(lt1)*cos(lt2)*cos(lg2-lg1)); 
       if(dist&ltndist) {ndist=dist; ws0=b1[l]} 
      } 
     } 
     print a1[k],a2[k],a3[k],a4[k],ws0,ndist 
    } 
    }' file01 file02 

ответ

2

Прочитайте ваши значения из файла01 в один или несколько массивов. Вы можете использовать getline в блоке BEGIN, или канонический путь - использовать цикл FNR == NR как один из основных блоков.

FNR == NR {array[$1] = $1; ...; next } # read file01 into some arrays 
{ for (item in array) { ... }  # process each item in the array(s) against each line in file02 

Ваш сценарий будет использоваться в качестве awk '...' file01 file02

Вместо индексации массивов с помощью значений полей, вы могли бы индексировать их со счетчиком array1[c] = $1; array2[c] = $2; c++ и перебирать со счетчиком вместо использования in: for (i=0; i<c; i++).

Конечно, вы должны выбрать значащие имена массивов.

+0

+1 Благодарим вас @ Dennis, ваш ответ был очень полезен! –

1

Интересные проблемы. Поскольку сначала вы должны прочитать в файле02 и сохранить в информации в структуре данных, я бы сначала опирался на Perl.

#!/usr/bin/perl 
use strict; 
use warnings; 

# see http://perldoc.perl.org/Math/Trig.html 
use Math::Trig qw(great_circle_distance deg2rad); 
sub NESW {deg2rad($_[0]), deg2rad(90-$_[1])} 

# read file02 
my %data; 
my $file2 = 'file02'; 
open my $fid, '<', $file2 or die "can't read $file2: $!\n"; 
while (<$fid>) { 
    my ($id, $ref, $long, $lat) = split; 
    push @{$data{$ref}}, [$id, $long, $lat]; 
} 
close $fid; 

$, = " "; 

# process file01 
my $file1 = 'file01'; 
open $fid, '<', $file1 or die "can't read $file1: $!\n"; 
while (<$fid>) { 
    my ($id, $ref, $long, $lat) = split; 
    my @here = NESW($long, $lat); 
    my $min = 99_999_999; 
    my (@min_id, $dist); 

    while (my ($key, $listref) = each %data) { 
     next unless $key == $ref; 

     foreach my $trioref (@$listref) { 
      my ($other_id, $other_long, $other_lat) = @$trioref; 
      my @there = NESW($other_long, $other_lat); 
      $dist = great_circle_distance(@here, @there, 6378.7); 
      if ($dist < $min) { 
       $min = $dist; 
       @min_id = @$trioref; 
      } 
     } 
    } 

    printf "%s %d %s %s %s %6.3f\n", $id, $ref, $long, $lat, $min_id, $min; 
} 
close $fid; 

Это выводит

A 1001 -1.2 30.31 ws4 67.308 
A 1002 -1.2 30.31 ws2 35.783 
B 1002 -1.8 30.82 ws4 55.387 
B 1003 -1.8 30.82 ws4 55.387 
C 1001 -2.1 28.55 ws1 93.361 

Я заметил, расстояние "C" отличается от того, что вы предлагаете это должно быть.

+0

Спасибо @glenn за помощь. Я не знаком с perl, поэтому мне нужно больше времени, чтобы пересмотреть ваше предложение, но это кажется большой помощью. –

1

Чтобы сделать это сразу, запустите

join -j 2 <(sort -k 2,2 file01) <(sort -k 2,2 file02) 

И конвейеру к AWK, который на каждом изменении ссылки сделать известково:

gawk '{if (last != $1 $2) {calc_nearest_on_array; last=$1 $2; add_point_to_array} else {add_point_to_array}}' 
+0

Ваш attemp выглядит многообещающим. Тем не менее, у меня проблема при попытке сделать это: поскольку я вычисляю ближайший каждый раз 'last! = $ 1 $ 2' и учитывая, что при чтении первой строки' last' отличается от '$ 1' и' $ 2', я не получите ближайшую точку для первой строки, и у меня нет печати для последней строки в 'file01'. Пожалуйста, см. Мой EDIT по вопросу для просмотра моего кода. –

+0

Вы правы. Я написал «концептуальный» awk, чтобы продемонстрировать эту идею. Чтобы напечатать последнюю строку, вам нужно использовать предложение END. –

0

TxR:

@(do 
    (defvar pi 3.1415926535) 
    (defvar earth-radius 6378.7) 
    (defun rad (deg) (/ (* deg pi) 180)) 
    (defun sphere-distance (lat0 lon0 lat1 lon1) 
    (let ((lat0 (rad lat0)) (lat1 (rad lat1)) 
      (lon0 (rad lon0)) (lon1 (rad lon1))) 
     (* earth-radius (acos (+ (* (sin lat0) (sin lat1)) 
           (* (cos lat0) (cos lat1) (cos (- lon1 lon0))))))))) 
@(next "file01") 
@(collect) 
@id @ref @lon0 @lat0 
@ (filter :tonumber lon0 lat0) 
@ (next "file02") 
@ (bind (min-dist ws) (1e99 nil)) 
@ (collect) 
@ws1 @ref @lon1 @lat1 
@ (filter :tonumber lon1 lat1) 
@ (do (let ((d (sphere-distance lat0 lon0 lat1 lon1))) 
      (cond ((< d min-dist) 
        (set min-dist d) 
        (set ws ws1))))) 
@ (end) 
@ (do (format t "~a ~a ~0,2f ~0,2f ~a ~0,3f\n" id ref lon0 lat0 ws min-dist)) 
@(end) 

Пробег:

$ txr dist.txr 
A 1001 -1.20 30.31 ws4 67.308 
A 1002 -1.20 30.31 ws2 35.783 
B 1002 -1.80 30.82 ws4 55.387 
B 1003 -1.80 30.82 ws4 55.387 
C 1001 -2.10 28.55 ws1 93.361 
+0

Что такое TXR-скрипт? В моем распределении debian нет TXR. –

+0

Домашняя страница TxR: http://www.nongnu.org/txr. Нет поддерживающего пакета пакета Debian. – Kaz

 Смежные вопросы

  • Нет связанных вопросов^_^