2009-09-22 10 views
6

Il codice che ho scritto è la seguente:Perché l'elenco la mia mappa Perl restituisce solo 1?

#!/usr/bin/perl 

my @input = ("a.txt" , "b.txt" , "c.txt") ; 
my @output = map { $_ =~ s/\..*$// } @input ; 

print @output ; 

La mia intenzione è quella di lasciare che il nome del file senza l'estensione memorizzata nella matrice @output. ma invece memorizza il valore restituito dal s/// piuttosto che il nome del file modificato in @output, quindi il risultato sembra

1 
1 
1 

quindi qual è il modo corretto di utilizzare map nell'ambito di questa situazione?

risposta

14

Ok, prima di tutto, probabilmente intendevi avere $_ =~ s/\..*$// - nota il s mancante nel tuo esempio. Inoltre, probabilmente intendi map non grep.

Secondo, quello non fa quello che vuoi. Ciò modifica in realtà @input! All'interno di grep (e map e in molti altri luoghi), $_ è in realtà un alias per ogni valore. Quindi stai effettivamente cambiando il valore.

Si noti inoltre che la corrispondenza del modello non restituisce il valore corrispondente; restituisce true (se c'è una corrispondenza) o false (se non c'è). Ecco tutti gli anni che stai vedendo.

Invece, fare qualcosa di simile:

my @output = map { 
    (my $foo = $_) =~ s/\..*$//; 
    $foo; 
} @input ; 

Le prime copie $_ a $foo, e quindi modifica $foo. Quindi, restituisce il valore modificato (memorizzato in $foo). Non è possibile utilizzare return $foo, perché è un blocco, non una subroutine.

+0

@derobert: quello mancante è un errore di modifica, l'ho risolto.e ho testato la tua soluzione e funziona! –

+0

Felice di sentirlo funzionare. – derobert

+4

Elenco :: MoreUtils http://search.cpan.org/perldoc/List::MoreUtils offre 'apply' che è perfetto per questo genere di cose. Funziona come una 'mappa', ma non altera i valori nell'argomento dell'array. 'usa List :: MoreUtils 'apply'; my @output = apply {s /\..*$//} @input; ' – daotoad

2

Mancano le "s" per la sostituzione.

$_ =~ /\..*$// 

dovrebbe essere

$_ =~ s/\..*$// 

Inoltre si potrebbe essere meglio usare s/\.[^\.]*$// come l'espressione regolare per assicurarsi che solo rimuovere l'estensione anche quando il nome del file contiene un '' (punto) carattere.

+0

@Nikhil: Sì, il tuo regex è meglio, grazie. –

0

Nell'esempio di corrispondenza manca un s nell'operatore di corrispondenza. Oltre a questo, ha funzionato bene per me:

$, = "\n"; 
my @input = ("a.txt" , "b.txt" , "c.txt"); 
my @output = grep { $_ =~ s/\..*$// } @input; 
print @output; 

uscita è:

 
a 
b 
c 
+2

Esegui un 'print @ input' e osserva come il tuo codice manipola' @ input', che è inaspettato e molto probabilmente indesiderato. – derobert

+0

Probabilmente è vero. – bobbymcr

7

Il problema di aliasing $_ valori della lista è stato già discusso.

Ma per di più: il titolo della tua domanda dice chiaramente "mappa", ma il tuo codice utilizza grep, anche se sembra proprio che debba usare la mappa.

grep valuterà ogni elemento nell'elenco che fornisci come secondo argomento. E in contesto lista restituirà una lista costituita da quegli elementi della lista originale per i quali la tua espressione è tornata vera.

map d'altra parte utilizza l'argomento expression o block per trasformare gli elementi dell'argomento list restituendo una nuova lista che consiste degli argomenti trasformati dell'originale.

Così il vostro problema potrebbe essere risolto con il codice come questo:

@output = map { m/(.+)\.[^\.]+/ ? $1 : $_ } @input; 

Ciò corrisponderà la parte del nome del file che non è un'estensione e restituirlo a seguito della valutazione o restituire il nome originale se non ci sono estensioni.

1

derobert mostra un modo corretto di mappare @input a @output.

Vorrei, tuttavia, consiglia di utilizzare File::Basename:

#!/usr/bin/perl 

use strict; 
use warnings; 

use File::Basename; 

my @input = qw(a.1.txt b.txt c.txt); 
my @output = map { scalar fileparse($_, qr/\.[^.]*/) } @input ; 

use Data::Dumper; 
print Dumper \@output; 

uscita:

 
C:\Temp> h 
$VAR1 = [ 
      'a.1', 
      'b', 
      'c' 
     ]; 
12

Fuori di tutte quelle risposte, nessuno semplicemente detto che map restituisce il risultato dell'ultimo espressione valutata Qualunque cosa tu faccia per ultima cosa è la cosa (o cose) map ritorna. È proprio come una subroutine o do che restituisce il risultato come ultima espressione valutata.

Perl v5.14 aggiunge la sostituzione non distruttiva, di cui scrivo in Use the /r substitution flag to work on a copy. Invece di restituire il numero di sostituzioni, restituisce la copia modificata. Utilizzare il flag /r:

my @output = map { s/\..*$//r } @input; 

Si noti che non è necessario utilizzare il $_ con l'operatore vincolante dato che è il tema di default.

+2

Grazie per aver segnalato la flag '/ r' di cui non ero a conoscenza. –

1

Come accennato, s /// restituisce il numero di sostituzioni eseguite e mappa restituisce l'ultima espressione valutata da ogni iterazione, quindi la mappa restituisce tutti gli 1. Un modo per ottenere ciò che si vuole è:

s/\..*$// for my @output = @input; 

Un altro modo è quello di utilizzare il filtro da Algorithm::Loops

+0

sono i piccoli dettagli che ti portano in Perl, un buon motivo per cui, a meno che tu non pensi di comprendere appieno la lingua, dovresti attenersi ai moduli quando le cose diventano rischiose. – osirisgothra

0

Il problema: sia the s/../.../ operator e Perl's map sono di importanza fondamentale, è in attesa di voler modificare ogni voce di input; Perl non ha infatti un built-in per il funzionale map che produce i suoi risultati senza modificare l'input.

Quando si utilizza s, una possibilità è quella di aggiungere il r modificatore:

#!/usr/bin/perl 

my @input = ("a.txt" , "b.txt" , "c.txt") ; 
my @output = map { s/\..*$//r } @input ; 

print join(' ', @output), "\n"; 

La soluzione generale (suggerita da derobert) è quello di utilizzare List::MoreUtils::apply:

#!/usr/bin/perl 

use List::MoreUtils qw(apply); 

my @input = ("a.txt" , "b.txt" , "c.txt") ; 
my @output = apply { s/\..*$// } @input ; 

print join(' ', @output), "\n"; 

o copiare la sua definizione nel vostro codice:

sub apply (&@) { 
    my $action = shift; 
    &$action foreach my @values = @_; 
    wantarray ? @values : $values[-1]; 
} 
Problemi correlati