5
$\begingroup$

Tables often come with column headers. Indexing the columns with the header is a natural thing to do. Although a proper dictionary is the usual solution in such a case, I find it often practical and elegant to index these through the symbol corresponding to the column header string. So say: I have an array:

mtvguests = {{Last Name, First Name},{Kolmogorov, Andrei},{Milankovic, Milutin}}

mtvguests[[All, lastName]] is a fairly natural and readable syntax.

The following two functions help towards this end:

SetAttributes[SymbolValidWMMA, Listable];
Options[SymbolValidWMMA] := {camelCase -> False, RulesStringPatterns->{"/"->"or"}}
    SymbolValidWMMA[s_String, OptionsPattern[]] := 
     Fold[StringReplace[#1, #2] &, s, 
      Join[ OptionValue[RulesStringPatterns], If[OptionValue[camelCase], {Except[WordCharacter] ~~ x_ :> ToUpperCase[x], 
         StartOfString ~~ Whitespace ... ~~ x_ :> 
          ToLowerCase[x]}, {}], {Whitespace -> "", 
        StartOfString ~~ x:DigitCharacter :> "d"<>x, 
        Except[WordCharacter] -> "" }]];

(I am not a big fan of camel case but in this case it comes as handy).

Options[IndicesFromStrings] := 
Join[Options[SymbolValidWMMA], {Prfx -> "", Sfx -> "", 
                Ctxt -> ""}];
            IndicesFromStrings[ls_List, opts : OptionsPattern[]] := 
              (ToExpression[#1 <> "=" <> ToString[First@#2]]) & 
               ~MapIndexed~ (If[OptionValue[Ctxt] != "", OptionValue[Ctxt] <> "`", 
                    ""] <> OptionValue[Prfx] <> 
                   SymbolValidWMMA[#, 
                    Sequence @@ FilterRules[{opts}, Options[SymbolValidWMMA]]] <> 
                   OptionValue[Sfx] & /@ ls);

An example.

mtvguests = {{"Kolmogorov", "Andrei"}, {"Milankovic", "Milutin"}}; 
Prepend[mtvguests, {"Last Name", "First Name"} ] // TableForm

Then

IndicesFromStrings[{"Last Name", "First Name", "Cycles/Operators", 
  "2_2_Tu"}, camelCase -> True]

yields:

{"lastName", "firstName", "cyclesorOperators", "d22Tu"}

And

mtvguests[[All, lastName]]

would return:

{"Kolmogorov", "Milankovic"}

The symbol mangling can be tweaked:

IndicesFromStrings[{"Last Name", "First Name", "Cycles/Operators", 
  "_2_Tu"}, Ctxt -> "mtv", camelCase -> False, 
 RulesStringPatterns -> {DigitCharacter -> "nb", "/" -> "", 
   "_" -> "U"}]

would now return:

{"mtv`LastName", "mtv`FirstName", "mtv`CyclesOperators", "mtv`UnbUTu"}

The previous works. Now for the question: I am not sure how to make the following work:

IndicesFromStrings[ls_List, opts : OptionsPattern[]] := 
  (Set[ReleaseHold[ToExpression[#1]], ToString[First@#2]]) &
    ~MapIndexed~(If[OptionValue[Ctxt] != "", OptionValue[Ctxt] <> "`", 
        ""] <> OptionValue[Prfx] <> 
       SymbolValidWMMA[#, 
        Sequence @@ FilterRules[{opts}, Options[SymbolValidWMMA]]] <> 
       OptionValue[Sfx] & /@ ls);
$\endgroup$
8
  • 6
    $\begingroup$ If you've a problem with Set[], then post a minimal example. The rest of the code (notwithstanding how clever it could be) is just impedance to understand the question. $\endgroup$ Apr 20, 2014 at 17:49
  • 4
    $\begingroup$ I do not agree with the down-votes on this question. While a more clear and concise summary would indeed be helpful, it is also often helpful to include the context of the issue to illustrate the actual problem that needs to be solved, thereby facilitating answers of broader scope instead of mere bug-fixes for something that may be a poorly conceived approach to begin with. While in time a standard of more tightly written questions is good, this is ostensibly Trad Dog's first question here; please let us be more friendly. $\endgroup$
    – Mr.Wizard
    Apr 20, 2014 at 19:49
  • 2
    $\begingroup$ I can't follow your code, too advanced for me. But when I wanted once to index by column and give it a tag/name, I simply made few costants, like this: NAME=1;DATE=2;. then for matrix say m={{"joe doe","1/1/2001"},{"joe doe 2","1/1/2002"}}, I would write m[[All,NAME]] and m[[2,DATE]] and so on. it works and was easy to handle. Not as nice as dictionary ofcourse. Just had to remember to make sure the names of columns and the index matched what I wanted. $\endgroup$
    – Nasser
    Apr 20, 2014 at 22:42
  • $\begingroup$ @Mr.Wizard Appreciate the accolade. There are essentially two issues with the first implementation of IndicesFromStrings. First, it could be more elegant and straight-forward (this is just setting several generated symbols to some numerical values). And, in the current form, because the operation perform is akin to a "set" it is rather unexpected that it does return some expression. $\endgroup$
    – Trad Dog
    Apr 21, 2014 at 20:31
  • $\begingroup$ @Nasser. I appreciate your approach and that's what I would do for a long time. However, when importing tables repetitively, the boiler-plate code involved becomes tedious on the long run. Handling the process in an automatised way comes then naturally. $\endgroup$
    – Trad Dog
    Apr 21, 2014 at 20:35

2 Answers 2

1
$\begingroup$

Let me refer to the second IndicesFromStrings as IndicesFromStrings2.

To make it work, replace Set[ReleaseHold[ToExpression[#1]], ToString[First@#2]] with Clear[#1]; Set[Evaluate@ToExpression[#1], First@#2], i.e.

IndicesFromStrings2[ls_List, opts:OptionsPattern[]] := (
    Clear[#1]; 
    Set[Evaluate@ToExpression[#1], First@#2]
  ) & ~MapIndexed~ (
    If[
      OptionValue[Ctxt] != "",
      OptionValue[Ctxt] <> "`",
      ""
    ] <>
    OptionValue[Prfx] <>
    SymbolValidWMMA[#, Sequence @@ FilterRules[{opts}, Options[SymbolValidWMMA]]] <>
    OptionValue[Sfx] & /@ ls
  );

There are several reasons why your definition doesn't work.

  1. ReleaseHold is irrelevant. There is no Hold anywhere to be released.
  2. Set[ToExpression[#1], whatever] wouldn't work because of the same reason that ToExpression[whateverString] = whatever doesn't work.
  3. ToString[First@#2] gives, for example, String-typed "1" instead of Number-typed 1. The former can't be used as Part specification. (In other words, lastName, for example, in mtvguests[[All, lastName]] should be 1, not "1".)

What I have done is simply addressing each of the issues.

  1. ReleaseHold is removed.
  2. ToExpression[whateverString] = whatever can be made to work by wrapping the left hand-side in Evaluate: Evaluate@ToExpression[whateverString] = whatever. Likewise, Evaluate is put in Set. However, the following problem ensues: if the Symbol whateverString already exists and has a value (that is not another valueless symbol), Evaluate just gives the value and Set::setraw or Set::wrsym is encountered. We then need to Clear it first. As fortunately Clear accepts string patterns, we can just put #1 into it.
  3. No need to convert First@#2 to a String.
$\endgroup$
0
$\begingroup$

I can't follow your code. I suggest your question is not about "Set" but how to index data by strings. In the past, I have found code like this to be useful:

people = {
   {"First name", "Last name", "Birth"},
   {"Johannes", "Bach", 1685},
   {"Peter", "Sculthorpe", 1929}};

(peopleFn[First@#] = Rest@#) & /@ Transpose@people;

peopleFn["Last name"]

{"Bach", "Sculthorpe"}

By way of explanation, the rather opaque line

    (peopleFn[First@#] = Rest@#) & /@ Transpose@people;

works by generating the expressions

peopleFn[*column heading*] = *rest of column*

for each column in the data set.

$\endgroup$

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.