Parece que você está pedindo uma solução para o problema de empacotamento . Até onde sabemos, não há uma maneira de fazer isso de forma otimizada e rápida (mas também não sabemos se há uma, é uma questão em aberto).
Mas você pode se aproximar. Um utilitário para isso é chamado de datapacker . Não usei aquele; achei pesquisando. Tenho certeza que tem mais.
Pessoalmente, eu uso um que eu mesmo escrevi. (Observe que todos eles produzirão a lista de arquivos que vão para cada bin; você pode usar facilmente cat
para reuni-los em um arquivo de texto.) O comentário se refere a bfpp
, que é outro programa que escrevi, que encontra a solução ideal, contanto que você tenha apenas alguns (como, menos de 10) arquivos:
#!/usr/bin/perl -w
#
# Sane bin packer. Attempts to finds a way to arrange a set of items
# into the fewest bins. Uses first-fit decreasing, which will get within
# 11⁄9⋅OPT + 6⁄9 (where OPT is the optimal number of discs, as found by
# bfpp). Unlike bfpp, this program will finish quickly.
#
# Also, this program features many more non-ASCII characters than bfpp.
# This is probably its most important feature.
use strict;
use 5.010;
use Data::Dump qw(pp);
use File::Next;
use List::Util qw(sum);
use String::ShellQuote qw(shell_quote);
use Carp qw(confess croak carp);
sub usage($) {
say STDERR "Usage: $0 bin-size-megabytes file1 file2 ...";
exit shift;
}
sub parse_command_line() {
usage(1) if @ARGV < 3;
usage(0) if $ARGV[0] =~ /^--?[h?]/;
my $size = shift @ARGV;
given ($size) {
when (/^dvd5?$/) {
$size = 4011;
}
when (/^dvd9$/) {
$size = 7291;
}
when (/^bd(-r)?1?$/) {
$size = 21360;
}
when (/^bd(-r)?2$/) {
$size = 42720;
}
when (/^\d+$/) {
# do nothing, already number
}
default {
say STDERR "ERROR: Size must be number or known size constant (dvd, dvd9, bd, bd2)";
usage(1);
}
}
return {
bin_size => $size * 1024 * 1024,
items => get_item_info(@ARGV),
};
}
sub get_item_info {
my %items;
my ($in_group, $group_no) = (0, 0);
foreach my $item (@_) {
if ('(' eq $item ) {
$in_group and confess "Nested groups not supported";
$in_group = 1;
++$group_no;
next;
} elsif (')' eq $item) {
$in_group or croak "Can't close a group when none open";
$in_group = 0;
next;
}
my $key;
$in_group and $key = "!!__GROUP${group_no}__!!";
if (-f $item) {
defined $key or ($key = $item) =~ s/\..{2,4}$//;
push @{$items{$key}{members}}, $item;
$items{$key}{size} += -s _;
} elsif (-d $item) {
$key //= $item;
my $files = File::Next::files($item);
while (defined(my $file = $files->())) {
push @{$items{$key}{members}}, $file;
$items{$key}{size} += -s $file;
}
} else {
confess "Not sure how to handle $item (weird type or doesn't exist)"
}
}
$in_group and carp "WARNING: Group wasn't closed";
return \%items;
}
sub check_sanity($) {
my $info = shift;
my $_;
my $binsize = $info->{bin_size};
my @dontfit = grep $info->{items}{$_}{size} > $binsize,
keys %{$info->{items}};
if (@dontfit) {
say "\nWARNING! WARNING! WARNING! WARNING!";
say "The following items are larger than the bin size:";
say pp(\@dontfit);
say "This is going to lead to some funny results.\n";
say "WARNING! WARNING! WARNING! WARNING!\n";
}
return $info;
}
sub approximate_best {
my $info = shift;
my @sorted
= sort { $info->{items}{$::b}{size} <=> $info->{items}{$::a}{size} }
keys %{$info->{items}};
my @bins;
FILE: foreach my $file (@sorted) {
my $size = $info->{items}{$file}{size};
BIN: foreach my $bin (@bins) {
next BIN unless $bin->{remaining} >= $size;
push @{$bin->{contents}}, $file;
$bin->{remaining} -= $size;
next FILE;
}
# didn't find a bin, open a new one
push @bins,
{
remaining => $info->{bin_size} - $size,
contents => [$file],
};
}
$info->{bins} = \@bins;
return $info;
}
sub print_bins($) {
my $info = shift;
my $_;
my $bins = $info->{bins};
#<<< [Hide this mess from PerlTidy]
use locale;
my @bins_full = map { # for each disk
[ sort( # sort each disk's fileset
map { # for each fileset
@{$info->{items}{$_}{members}}
} @{$_->{contents}}
) ];
} @$bins;
#>>>
for (my $d = 0; $d < @bins_full; ++$d) {
print <<DISC
DISC #@{[$d + 1]}: (@{[ int($bins->[$d]{remaining}/1024/1024) ]} MiB empty)
@{[ join(qq{\n }, @{$bins_full[$d]}) ]}
DISC
}
say "As space-separated, escaped values (for shell):";
for (my $d = 0; $d < @bins_full; ++$d) {
say $d+1, q{: }, shell_quote @{$bins_full[$d]};
}
return undef;
}
# believe it or not, the below is code.
print_bins approximate_best check_sanity parse_command_line;