2009-10-28 9 views
5

Пусть file1 выглядит следующим образом:Сколько различных способов объединить два файла по строкам, используя Perl?

 
bye bye 
hello 
thank you 

И file2 выглядит следующим образом:

 
chao 
hola 
gracias 

Нужный выход заключается в следующем:

 
bye bye chao 
hello hola 
thank you gracias 

Я сам уже придумали пять различных подходы к решению этой проблемы. Но я думаю, что должно быть больше способов, возможно, более сжатых и более элегантных способов, и я надеюсь, что я смогу научиться более классным вещам :)

Следующее, что я пробовал до сих пор, основываясь на том, что я узнал из многие решения моих предыдущих проблем. Кроме того, я пытаюсь сортировать дайджест или усваивать знания, полученные мной из книги Llama.

Код 1:

#!perl 
use autodie; 
use warnings; 
use strict; 

open my $file1,'<','c:/file1.txt'; 
open my $file2,'<','c:/file2.txt'; 

while(defined(my $line1 = <$file1>) 
     and defined(my $line2 = <$file2>)){ 
    die "Files are different sizes!\n" unless eof(file1) == eof(file2); 
    $line1 .= $line2; 
    $line1 =~ s/\n/ /; 
    print "$line1 \n"; 
} 

Код 2:

#!perl 
use autodie; 
use warnings; 
use strict; 

open my $file1,'<','c:/file1.txt'; 
my @file1 = <$file1>; 

open my $file2,'<','c:/file2.txt'; 
my @file2 =<$file2>; 

for (my $n=0; $n<=$#file1; $n++) { 
    $file1[$n] .=$file2[$n]; 
    $file1[$n]=~s/\n/ /; 
    print $file1[$n]; 
} 

Код 3:

#!perl 
use autodie; 
use warnings; 
use strict; 

open my $file1,'<','c:/file1.txt'; 
open my $file2,'<','c:/file2.txt'; 

my %hash; 

while(defined(my $line1 = <$file1>) 
     and defined(my $line2 = <$file2>)) { 
    chomp $line1; 
    chomp $line2; 
    my ($key, $val) = ($line1,$line2); 
    $hash{$key} = $val; 
} 
print map { "$_ $hash{$_}\n" } sort keys %hash; 

Код 4:

#!perl 
use autodie; 
use warnings; 
use strict; 

open my $file1,'<','c:/file1.txt'; 
open my $file2,'<','c:/file2.txt'; 

while(defined(my $line1 = <$file1>) 
     and defined(my $line2 = <$file2>)) { 
    $line1 =~ s/(.+)/$1 $line2/; 
    print $line1; 
} 

Код 5:

#!perl 
use autodie; 
use warnings; 
use strict; 

open my $file1,'<','c:/file1.txt'; 
my @file1 =<$file1>; 

open my $file2,'<','c:/file2.txt'; 
my @file2 =<$file2>; 

while ((@file1) && (@file2)){ 
    my $m = shift (@file1); 
    chomp($m); 

    my $n = shift (@file2); 
    chomp($n); 

    $m .=" ".$n; 
    print "$m \n"; 
} 

Я пытался что-то вроде этого:

foreach $file1 (@file2) && foreach $file2 (@file2) {...} 

Но Perl дал мне синтаксический предупреждение об ошибке. Я был расстроен. Но можем ли мы одновременно запустить две петли foreach?

Спасибо, как всегда, за любые комментарии, предложения и, конечно, обмен щедрым код :)

+1

Не проводке почти идентичный вопрос в последнее время? – 2009-10-28 12:15:39

+2

На самом деле, это звучит как вопрос типа опроса, и никто не отвечает правильно, поэтому, вероятно, должен быть CW. –

+1

@ Kinopiko, этот вопрос, который я задал пару дней назад. «Как заменить столбец одного файла на столбец другого с помощью Perl?» было сложнее, чем эта. – Mike

ответ

12

Это работает для любого количества файлов:

use strict; 
use warnings; 
use autodie; 

my @handles = map { open my $h, '<', $_; $h } @ARGV; 

while (@handles){ 
    @handles = grep { ! eof $_ } @handles; 
    my @lines = map { my $v = <$_>; chomp $v; $v } @handles; 
    print join(' ', @lines), "\n"; 
} 

close $_ for @handles; 
+0

+1 Хорошее использование 'eof'. –

+0

BTW, я нахожу 'my @handles = map {открыть мои $ h, '<', $ _; $ h} @ARGV; 'предпочтительнее. Вы избавляетесь от массива '@ files', который вы не используете в другом месте. Хотелось бы, чтобы я мог несколько раз поддержать ваш ответ. –

+0

@Sinan Спасибо, это хорошая идея (отредактирован ответ). – FMc

2

Простой один с минимальной проверкой ошибок:

#!/usr/bin/perl -w 

use strict; 

open FILE1, '<file1.txt'; 
open FILE2, '<file2.txt'; 

while (defined(my $one = <FILE1>) or defined(my $twotemp = <FILE2>)){ 
    my $two = $twotemp ? $twotemp : <FILE2>; 
    chomp $one if ($one); 
    chomp $two if ($two); 
    print ''.($one ? "$one " : '').($two ? $two : '')."\n"; 
} 

И нет, вы не можете запустить две петли одновременно в одном и том же потоке, вам нужно будет fork, но это не будет гарантировано работать синхронно.

+1

Без тестирования я могу признать, что есть тернарный оператор, которого я ожидал. Ах, это круто. Я сам сделал несколько попыток использовать тернарный оператор для выполнения этой работы, но не повезло. Спасибо большое :) – Mike

+0

Протестировано сейчас. Он отлично работает! Еще раз спасибо! – Mike

+0

Не работает, если строка ввода - это номер нуля. Используйте 'defined' в условных операторах. –

2

Простейшее альтернатива вашему кодекс 5 который позволяет произвольным числом строк и не заботится, если файлы имеют разное количество линий (наконечник шляпы @FM):

#!/usr/bin/perl 

use strict; use warnings; 

use File::Slurp; 
use List::AllUtils qw(each_arrayref); 

my @lines = map [ read_file $_ ], @ARGV; 

my $it = each_arrayref @lines; 

while (my @lines = grep { defined and chomp and length } $it->()) { 
    print join(' ', @lines), "\n"; 
} 

И, без использования любые внешние модули:

#!perl 
use autodie; use warnings; use strict; 

my ($file1, $file2) = @ARGV; 

open my $file1_h,'<', $file1; 
my @file1 = grep { chomp; length } <$file1_h>; 

open my $file2_h,'<', $file2; 
my @file2 = grep { chomp; length } <$file2_h>; 

my $n_lines = @file1 > @file2 ? @file1 : @file2; 

for my $i (0 .. $n_lines - 1) { 
    my ($line1, $line2) = map { 
     defined $_ ? $_ : '' 
    } $file1[$i], $file2[$i]; 
    print $line1, ' ', $line2, "\n"; 
} 

Если вы хотите, чтобы сцепить только те строки, которые появляются в обоих файлах:

#!perl 
use autodie; use warnings; use strict; 

my ($file1, $file2) = @ARGV; 

open my $file1_h,'<', $file1; 
my @file1 = grep { chomp; length } <$file1_h>; 

open my $file2_h,'<', $file2; 
my @file2 = grep { chomp; length } <$file2_h>; 

my $n_lines = @file1 < @file2 ? @file1 : @file2; 

for my $i (0 .. $n_lines - 1) { 
    print $file1[$i], ' ', $file2[$i], "\n"; 
} 
+0

Протестировано не удалось. Perl говорит: «Невозможно найти List/Allutils.pm в @INC». Но я возьму модуль и проведу его снова. – Mike

+1

+1 для 'read_file' и' each_array'. Я тоже думал об этом подходе, но потом заметил ваш ответ в последнюю минуту. Человек может легко обобщить это, чтобы обрабатывать любые N файлов. – FMc

+0

@ Синан, спасибо! Модуль снова установлен и протестирован. он отлично работает, но есть неприятная проблема. Perl дает мне сообщение об ошибке «не может найти perl58.dll». Конечно, он не может найти perl58.dll, потому что теперь я запускаю Perl 5.10.1. Как я могу избавиться от этой ложной тревоги, не понижая свой Perl? – Mike

10

Самый элегантный способ не включает perl вообще:

paste -d' ' file1 file2 
+2

+1 Согласен, но я думаю, что целью OP является изучение Perl, работая с такими игрушечными программами. –

+0

@mouviciel, это не похоже на perl. но я согласен, что это лаконично :) – Mike

+0

есть рабочий Perl-вкладыш, как это? Мне любопытно. – Mike

7

Если бы я был играть в гольф человек, я мог бы переписать @FM's answer как:

($,,$\)=(' ',"\n");@[email protected];open $_,$_ for @_;print 
map{chomp($a=<$_>);$a} @_=grep{!eof $_} @_ while @_ 

, который вы, возможно, сможете превратить в однострочный, но это просто зло. ;-)

Ну, вот оно, под 100 символов:

C:\Temp> perl -le "$,=' ';@[email protected];open $_,$_ for @_;print map{chomp($a =<$_>);$a} @_=grep{!eof $_ }@_ while @_" file1 file2

Если это нормально чавкать (и почему, черт возьми, не — мы являются ищут различные способы) , я думаю, я обнаружил тот путь, маразм:

@[email protected];chomp($x[$.-1]{$ARGV}=$_) && eof 
and $.=0 while<>;print "@$_{@_}\n" for @x 

C:\Temp> perl -e "@[email protected];chomp($x[$.-1]{$ARGV}=$_) && eof and $.=0 while<>;print qq{@$_{@_}\n} for @x" file1 file2

Выход:

 
bye bye chao 
hello hola 
thank you gracias 
+0

Это не мое намерение использовать это! Хорошая производительность. – mouviciel

+1

+1 с лазерным огнем на голове! – DVK

+0

@Sinan :) Спасибо большое! Этот злой однострочный движок отлично работает: perl -le "$, = ''; @ _ = @ ARGV; open $ _, $ _ for @_; print map {chomp ($ a = <$_>); $ a} @_ = grep {! eof $ _} @_ while @_ "" c: /file1.txt "" c: /file2.txt ". – Mike

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