18
$\begingroup$

I am teaching students how to use Mathematica. I want to create a problem set that has cells with a certain style (probably Input or some new style that is a variant of Input) locked down so that the students can not see them at all or just see an unevaluatable placeholder instead. But I also want a cell within the notebook that permits someone with a password (like me) to make these hidden cells visible and evaluatable.

Extra credit: it would be cool if I could relock these cells so that they reverted to their hidden form.

Concession: Maybe the unlocking cell does not have to be in the notebook but could be a function in an external notebook that worked on the problem set notebook.

Right now I accomplish the basic idea by having two notebooks, one with the answers and one without them. But this is ugly and leads to synchronization errors as I change my mind.

Ideas welcome. Obviously, simpler is better.

$\endgroup$
7
  • $\begingroup$ Are your students knowledgable enough to peek at the cell expression? $\endgroup$
    – rm -rf
    Jan 10, 2013 at 18:44
  • 2
    $\begingroup$ Your current approach of having two notebooks could be made robust by putting a button at the top of the master Q/A notebook that generates a question-only notebook when you need to distribute the questions. $\endgroup$
    – JxB
    Jan 10, 2013 at 19:02
  • $\begingroup$ Yes, my students are probably smart enough to peek at the cell expression. Or at least I will give them credit for being that industrious. $\endgroup$ Jan 10, 2013 at 19:22
  • $\begingroup$ Yes, JxB, I can always Option-Select and then delete the Input cells or whatever Style I give to cells that Students Should Not See. But I still wonder if there is not a one-document solution. I suppose the issue is going to be whether we can come up with a one-document solution that is simple enough so that the document-management advantages outweigh the extra overhead. $\endgroup$ Jan 10, 2013 at 19:26
  • 2
    $\begingroup$ With TaggingRules and Encode and so on one can probably do this, but it is quite some work I guess ... $\endgroup$ Jan 10, 2013 at 19:58

3 Answers 3

9
$\begingroup$

Here's an idea following up on the master-notebook model.

The master notebook has two additional cell styles: AnswerInput and AnswerOutput. You can write the questions as you like, using ItemNumbered, Text, or whatever style except those two new styles. You put your answer code in an AnswerInput cell, and evaluate it. Set the output cell to style AnswerOutput.

Master Notebook

The master notebook has a button at the top, which you can copy for each assignment, that generates the question document. You distribute that notebook to the students, but only ever have to edit the master. Consequently the question notebook is automatically synchronized to the master notebook.

Question Notebook

The question notebook does not contain the Button, and your answers are replaced with a placeholder Input cell.

I'm inclined to do it this way because

  1. I don't have to remember and protect an additional password.
  2. Since there is no password prompt, students will not be distracted trying to guess the password.
  3. The notebook itself is not subject to attack for purposes of cheating, since the answers are not in the notebook.

Here is the code for the button. Evaluate it at the top of a new notebook, and double-click on the button cell frame at the right to hide the code. Note: you need to add "ButtonCode" to the CellTags option on this cell (from the Cell menu) to ensure the button is not included in the question notebook.

(* Click this button to generate a notebook without answers *)
Button["Create question notebook", 
 Module[{copy, cursorposition = NotebookSelection[]},
  (* Copy the current notebook, inheriting the stylesheet *)
  copy = CreateDocument[{}, 
    NotebookFileName -> 
     FileNameJoin[{NotebookDirectory[], "Questions.nb"}], 
    StyleDefinitions -> CurrentValue[EvaluationNotebook[], 
      StyleDefinitions]];
  NotebookWrite[copy, 
   (SelectionMove[EvaluationNotebook[], All, Notebook, 
     AutoScroll -> False]; NotebookRead[EvaluationNotebook[]])];
  (* Return cursor to its initial position *)
  SelectionMove[cursorposition, After, Cell];
  (* Remove all "AnswerOutput"-Style cells *)
  NotebookFind[copy, "AnswerOutput", All, CellStyle];
  NotebookDelete[copy];
  (* Replace all "AnswerInput"-Style cells with a placeholder Input cell, framed with a Gray border *)
  NotebookFind[copy, "AnswerInput", All, CellStyle];
  Scan[
   NotebookWrite[#, Cell[BoxData["(* Your solution *)\n"], 
      "Input", CellFrame -> {{1, 1}, {1, 1}}, 
      CellFrameColor -> Gray]] &, SelectedCells[copy]];
  (* Remove the button code *)
  NotebookFind[copy, "ButtonCode", All, CellTags];
  NotebookDelete[copy];
  (* Return cursor to its initial position *)
  SelectionMove[cursorposition, After, Cell];
 ], ImageSize -> All]

Add these two styles to the notebook's stylesheet:

Cell[StyleData["AnswerInput", StyleDefinitions -> StyleData["Input"]], GeneratedCellStyles->{"Output"->"AnswerOutput"}, MenuSortingValue->10000]

and

Cell[StyleData["AnswerOutput", StyleDefinitions -> StyleData["Output"]], MenuSortingValue->10000]

The question notebook will keep a copy of any Output cells from your master, so you can include Output in the questions themselves. When you evaluate AnswerInput cells, however, the outputs are styled AnswerOutput, and those are removed from the question notebook.

$\endgroup$
2
  • $\begingroup$ This looks very useful (although question 3 looks kind of challenging). I'll give it a try and comment here later on what I discover. It does have the virtue of simplicity even if it does lead to two notebooks. Thanks very much for your efforts. I am quite impressed. $\endgroup$ Jan 11, 2013 at 13:30
  • $\begingroup$ @SethJ.Chandler I've found out how to make the AnswerInput styles automatically evaluate to AnswerOutput. See updated stylesheet definitions. No more manual editing of the outputs. $\endgroup$
    – JxB
    Jan 12, 2013 at 5:19
7
$\begingroup$

I don't claim this will do everything you want, but this presentation by Jason Harris on Advanced Typesetting includes a program that will Encrypt code and allow it to be unlocked with a short and simple password.

Apparently it was used live during a workshop presentation to encourage those in attendance to attempt the problem(s) at hand without first seeing the solution(s). (Did anyone on here attend??)

Note: You may have to dismiss a message box when launching the notebook. Everything works fine for me on v9.

$\endgroup$
1
  • $\begingroup$ That's a nice presentation and looks like it should be sufficient for the OP's needs. $\endgroup$
    – rm -rf
    Jan 11, 2013 at 2:29
5
$\begingroup$

This is a start that could be turned into a style and a package, and improved, etc.

First, some function to encrypt a string with a key. I'm not good at this, I only knew how to do it with Encode

encode[expr_, password_String] :=
 With[{fnameSource = FileNameJoin[{$TemporaryDirectory, "encrSource"}],
       fnameDest = FileNameJoin[{$TemporaryDirectory, "encrDest"}]},
  Block[{buff},
   Put[Unevaluated[buff = expr], fnameSource];
   Encode[fnameSource, fnameDest, password];
   With[{res = StringJoin@Import[fnameDest, {"Binary", "Character8"}]},
    Scan[DeleteFile, {fnameSource, fnameDest}];
    res
    ]
   ]]

To decode

decode[expr_String, password_String] := 
 With[{fname = FileNameJoin[{$TemporaryDirectory, "decr"}]},
      Export[fname, Characters@expr, {"Binary", "Character8"}];
      Block[{buff},
       Quiet@Check[Get[fname, password], buff = $Failed];
   With[{res = buff},
    DeleteFile@fname;
    res]
   ]]

The idea is simple: to provide a button in a CellFrameLabel, that encrypts or decrypts the cell's content

Let's overload our previous functions, letting them take a cell object

encode[co_CellObject] := 
 With[{pass = InputString["Please, enter the password"]},
  CurrentValue[co, {TaggingRules, "encoded"}] = True;
  NotebookWrite[co,
   Replace[NotebookRead[co], 
    Cell[data_, opts___] :> 
     Cell[BoxData@
       ToBoxes@Interpretation["Hidden cell", 
         Evaluate@encode[data, pass]], opts]]];
  SelectionMove[EvaluationNotebook[], After, Cell];
  ]

decode[co_CellObject] := 
 With[{pass = InputString["Please, enter the password"]},
  With[{newCell = Replace[
      NotebookRead[co],
      Cell[BoxData[int_], opts___] :> 
       Cell[decode[ToExpression@int, pass], opts]]},
   (CurrentValue[co, {TaggingRules, "encoded"}] = False;
   NotebookWrite[co, newCell];
   SelectionMove[EvaluationNotebook[], After, Cell]) /; 
    MatchQ[newCell, Cell[Except[$Failed], ___]]]
  ]

decode::fail = "Failed to decode cell";
decode[_CellObject] := (Message[decode::fail]; $Failed /; False)

These are the options an "encryptable cell" should have. Just a tagging rule with the state (encrypted or not encrypted), and the cell frame label specifications.

opts = Sequence[
  CellFrameLabels -> {{None, None}, {None, ToBoxes@Dynamic[
       CurrentValue[{TaggingRules, "encoded"}] /. {
          False :> {"Encode", encode},
          True :> {"Decode", decode}} /. {butName_, fun_} :>
         Button[butName, 
          fun[SelectionMove[EvaluationNotebook[], All, 
            EvaluationCell]; First@SelectedCells[]], 
          Method -> "Queued"]]}}, 
  TaggingRules -> {"encoded" -> False}];

So, for example, try

CellPrint@ExpressionCell[28 f[9], "Output", opts]

or

CellPrint@TextCell["The answer is 8", "Text", opts]

You can put this into a stylesheet, and the functions into a package.

$\endgroup$
6
  • $\begingroup$ It's probably nicer to use a palette or a docked cell than a style and CellFrameLabels. It should be quite easy to do from this code $\endgroup$
    – Rojo
    Jan 11, 2013 at 7:14
  • $\begingroup$ I'm also going to give this idea a shot and will report back on how it goes. Your idea of putting the cell to be encoded into a notebook and then using the built-in Encode function is clever, though I wish Wolfram Research would have a more general Encode function that worked on things other than a notebook. Thanks for your work on this problem. $\endgroup$ Jan 11, 2013 at 13:34
  • $\begingroup$ No problem @SethJ.Chandler. I probably will end up putting this in a palette for myself too: select a cell or piece of text, click, password, and hidden. $\endgroup$
    – Rojo
    Jan 11, 2013 at 13:41
  • $\begingroup$ @SethJ.Chandler I'll probably make the palette later and ping you to share it $\endgroup$
    – Rojo
    Jan 11, 2013 at 14:48
  • $\begingroup$ @SethJ.Chandler What do you mean by more general. Right now you can encode .m files or any text file, e.g.:Export["test.txt","secrect","Text"];Encode["test.txt","test.enc",MachineID->$MachineID];FilePrint@"test.enc" $\endgroup$ Jan 11, 2013 at 15:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.