Сортировать файл с разделителями-запятыми по трем столбцам с пользовательскими критериями в Perl - PullRequest
2 голосов
/ 11 февраля 2020

У меня текстовый файл с разделителями-запятыми. Я хочу отсортировать файл сначала по 3-му столбцу, затем по 2-му столбцу, затем по 1-му столбцу.

Однако я хочу, чтобы 3-й столбец сортировался по алфавиту, причем сначала будет самое длинное значение.

Например, AAA, затем AA, затем A, затем BBB, затем BB, затем B, затем CCC, затем CC и т. Д.

Input (alpha-sort-test2. txt):

JOHN,1,A
MARY,3,AA
FRED,5,BBB
SAM,7,A
JOHN,3,AAA
JOHN,2,AAA
BETTY,2,AAA
JARROD,7,AAA
JOANNE,2,BB
AMANDA,2,DD
AMY,5,B
PETE,7,CC
MATT,4,B
SARAH,3,CCC
GEORGE,3,CC
AMANDA,3,AAA

Код Perl, который у меня есть на данный момент, выглядит следующим образом:

$infile = "alpha-sort-test2.txt";
$outfile = "alpha-sort-test-sorted2.txt";

open (INFILE, "<$infile") or die "Could not open file $infile $!";
open (OUTFILE, ">$outfile");

my @array = sort howtosort <INFILE>;

foreach (@array)
{
   chomp;
   print "$_\n";
   print OUTFILE "$_\n"; 
}

sub howtosort 
{
   my @flds_a = split(/,/, $a);
   my @flds_b = split(/,/, $b);

   $flds_a[2] cmp $flds_b[2]; 
}

close INFILE;
close OUTFILE; 

Токовый выход (alpha-sort-test-sorted2.txt):

JOHN,1,A
SAM,7,A
MARY,3,AA
AMANDA,3,AAA
JOHN,3,AAA
JOHN,2,AAA
BETTY,2,AAA
JARROD,7,AAA
AMY,5,B
MATT,4,B
JOANNE,2,BB
FRED,5,BBB
PETE,7,CC
GEORGE,3,CC
SARAH,3,CCC
AMANDA,2,DD

Желаемый вывод:

BETTY,2,AAA
JOHN,2,AAA
AMANDA,3,AAA
JOHN,3,AAA
JARROD,7,AAA
MARY,3,AA
JOHN,1,A
SAM,7,A
FRED,5,BBB
JOANNE,2,BB
MATT,4,B
AMY,5,B
SARAH,3,CCC
GEORGE,3,CC
PETE,7,CC
AMANDA,2,DD

Заранее спасибо.

1 Ответ

5 голосов
/ 11 февраля 2020

Есть небольшое осложнение с этим критерием для третьего поля.

Лексикографическое сравнение идет символ за символом, поэтому abc меньше чем ax, но более длинные строки больше, а все остальные равны. Таким образом, ab меньше b, но ab больше a.

Таким образом, это требование для третьего поля смешивает эти две вещи и разбивает cmp прямо по центру. Если бы мы использовали cmp, тогда ab предшествует b (правильно), но aa следует после a (не требуется). Я не понимаю, как вообще использовать cmp для этого требования.

Итак, вот очень базовая c реализация этого, для этих критериев

use warnings;
use strict;
use feature 'say';
use Path::Tiny qw(path);  # convenience

my $file = shift // die "Usage: $0 file\n";
my @lines = path($file)->lines({ chomp => 1 });

my @sorted =
    map { $_->[0] }
    sort { custom_sort($a, $b) }
    map { [$_, split /,/]  }
    @lines;

say for @sorted;


sub custom_sort {
    my ($aa, $bb) = @_;

    # Last field for both terms, their lengths
    my ($af, $bf) = map { $_->[-1] } $aa, $bb;
    my ($len_a, $len_b) = map { length } $af, $bf;

    # Strip and return first characters and compare them lexicographically
    # Then compare lengths of original strings if needed
    # Keep going until difference is found or one string is depleted
    while (
        (my $ca = substr $af, 0, 1, "")  and
        (my $cb = substr $bf, 0, 1, "")    )
    {
        if ($ca gt $cb) {
            return 1
        }
        elsif ($ca lt $cb) {
            return -1;
        }
        elsif ($len_a < $len_b) {
            return 1
        }
        elsif ($len_a > $len_b) {
            return -1
        }
    }

    # Still here, so third field was the same; use other two criteria
    return
        $aa->[2] <=> $bb->[2]
            ||
        $aa->[1] cmp $bb->[1];
}

Это распечатывает нужный список.

Некоторые комментарии

  • При вызове sort мы сначала формируем arrayref со всей строкой и ее отдельными полями, чтобы впоследствии не пришлось разбивать строку при каждом отдельном сравнении; это преобразование Шварца

  • Критерий для третьего поля: сравнивать символ за символом по алфавиту, пока не будет найдена разница; если одна строка содержится в другой, то выигрывает более длинная. Таким образом, сравнение по символам abc и ab останавливается на b и abc 'wins'

  • (необязательный) четвертый аргумент в substr является заменой для возвращенной подстроки, найденной по второму и третьему аргументу. Поэтому здесь пустая строка заменяет одну длинную подстроку, которая начинается с 0 - она ​​удаляет и возвращает первый символ. Это очень похоже на использование shift в массиве

  • Если третьи поля абсолютно одинаковы, то вторые поля сравниваются численно, а если они совпадают, то сравниваются первые поля в алфавитном порядке

  • После сравнения мы извлекаем исходную строку из отсортированного массиваrefs

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...