=head1 NAME contextSetOfSets.pl - Implements a MathObject context for sets of sets of numbers. =head1 DESCRIPTION This context implements a set of sets of numbers. To use it, first load the contextSetOfSets.pl file: loadMacros("contextSetOfSets.pl"); and then select the context Context("SetOfSets"); Once the context is selected, the values that can be parsed are sets of sets of numbers. For example: $n = Compute('{{1,2}, {}}'); produces the set consisting of the set {1,2} and the empty set. It is possible to name the emtpy set using Context()->constants->add(EmptySet => Compute("{}")); to make C represent the empty set. If you want to make sets of other values, like sets of letters, you can create constants for the needed letters, and add a C function to prevent the entries from being entered as numbers. You can improve the error messages by setting the entry_type for the sets. For example: @letters = ('A' .. 'C'); Context()->constants->add(map {$letters[$_] => $_} (0..$#letters)); Context()->flags->set(NumberCheck => sub { my $self = shift; $self->Error("An element of a set of letters can't be a number"); }); Context()->{cmpDefaults}{List}{entry_type} = "a set of letters"; will set up letters A, B, and C, disallow numbers in the sets, and use "a set of letters" in error messages about the sets being created. There is one context flag that controls the SetOfSets context: =over =item C 0 >>> This flag determines whether repeated sets are allowed in the set of sets. It is false by default, but can be set to true to allow the same set to appear more than once in the set of sets. =back =cut sub _contextSetOfSets_init { my $context = $main::context{SetOfSets} = Parser::Context->getCopy("Numeric"); $context->{name} = "SetOfSets"; $context->variables->clear(); $context->functions->undefine($context->functions->names()); $context->constants->clear(); $context->strings->clear(); $context->operators->undefine($context->operators->names()); $context->operators->redefine(',', from => "Numeric", using => ','); $context->parens->clear(); $context->parens->add('{' => { type => 'List', emptyOK => 1, formList => 1, function => 1, close => '}' }); $context->{value}{List} = 'value::SetOfSets::List'; $context->flags->set(allowRepeatedSets => 0); $context->{error}{msg}{"Can't use '*' in this context"} = "Did you leave out a comma?"; PG_restricted_eval("sub SetOfSets {value::SetOfSets::>List->new(\@_)}"); }; package value::SetOfSets::List; our @ISA = ('Value::List'); sub new { my $self = shift; $self = bless $self->SUPER::new(@_), value::SetOfSets::List; if (($self->typeRef->{entryType}{name} || '') eq 'Number') { $self = main::Set($self); } else { my $ans = $self->context->{answerHash} || {}; my $value = lc($ans->{entry_type} || 'a value'); $value =~ s/^an? //; my @sets = @{$self->{data}}; for my $i (0..$#sets) { my $ith = $self->NameForNumber($i+1); $self->Error("Your $ith entry is not $value (it seems to be " . lc($sets[$i]->showClass) . ")") unless $sets[$i]->classMatch("Set", "List") && (($sets[$i]->typeRef->{entryType}{name} || '') eq 'Number' || scalar(@{$sets[$i]->{data}}) == 0); my $error = $self->cmp_checkUnionReduce($sets[$i], $ans, " $ith", $value); $self->Error($error) if $error; } if (!$self->getFlag('allowRepeatedSets')) { for my $i (1..$#sets) { for my $j (0..$i-1) { if ($sets[$i]->typeMatch($sets[$j]) && $sets[$i]->compare($sets[$j]) == 0) { my $ith = $self->NameForNumber($i+1); my $jth = $self->NameForNumber($j+1); $self->Error("Your $ith set is the same as your $jth one (the sets must be unique)"); } } } } } return $self; } sub cmp_defaults { my $self = shift; return ( $self->SUPER::cmp_defaults(), entry_type => 'a set of numbers', list_type => 'a set of sets', requireParenMatch => 1, removeParens => 0, implicitList => 0, showEqualErrors => 0, formatStudentAnswer => 'parsed', typeMatch => sub { my $other = shift; return $other->type eq 'List' || $other->type eq 'Set'; } ); } sub cmp_equal { my $self = shift; my $ans = shift; my $result = $self->SUPER::cmp_equal($ans, @_); if ($ans->{error_message} && $ans->{entry_type} =~ m/s$/) { my $type = lc($ans->{entry_type}); $type =~ s/^an? //; my $ntype = $type; $ntype =~ s/set/sets/; $ans->{error_message} =~ s/${type}s/$ntype/; $ans->{error_message} =~ s/parentheses for/braces for/; $ans->{ans_message} = $ans->{error_message}; } return $result; } sub showClass { my $self = shift; my $type = Value::showType($self); $type =~ s/List/Set/g; return $type; } 1;