use strict; use warnings; use Math::Combinatorics; use List::Util qw{ all }; my $firstLetter = 'c'; my $str = 'a?1,a?2,a%3,b?2,b?3,b%5,c%4,c%5,c?6,d%2'; my $subset = 'a?,b%,c?'; print display($_, $firstLetter), "\n" for grep qualify($_, $subset), Math::Combinatorics::combine(4,split ",",$str); sub qualify { my($combo, $subset) = @_; # Check if numbers are all unique, using the "postincrement" trick my %seen; $seen{$_}++ or return 0 for map substr($_, 2), @$combo; # Find all the tokens in the combo. If all the ones we need in the # subset are represented, we're good. my %token = map +(substr($_, 0, 2) => 1), @$combo; return all { $token{$_} } split /,/, $subset; } sub display { my($combo, $first) = @_; # Use a Schwartzian transform over a reverse sort on our # calculated "firstness weight": -1 for elements with the wrong # first letter, else the element's weight. sort() is overkill here, # I'd use a different approach if we were considering larger sets. return join ',', map $_->[0], sort { $b->[1] <=> $a->[1] } map [ $_, substr($_, 0, 1) eq $first ? substr($_, 2) : -1 ], @$combo; } #### c?6,a?1,a?2,b%5 c?6,a?1,a%3,b%5 c?6,a?1,b?2,b%5 c?6,a?1,b?3,b%5 c?6,c%4,a?1,b%5 c?6,a?1,b%5,d%2 c?6,a?2,a%3,b%5 c?6,a?2,b?3,b%5 c?6,c%4,a?2,b%5