code: public static Vec stringToCategorical(Vec vec) {
      final String[] vecDomain = new CollectStringVecDomain().domain(vec);
  
      MRTask task = new MRTask() {
        transient private java.util.HashMap<String, Integer> lookupTable;
  
        @Override
        protected void setupLocal() {
          lookupTable = new java.util.HashMap<>(vecDomain.length);
          for (int i = 0; i < vecDomain.length; i++) {
            // FIXME: boxing
            lookupTable.put(vecDomain[i], i);
          }
        }
  
        @Override
        public void map(Chunk c, NewChunk nc) {
          BufferedString bs = new BufferedString();
          for (int row = 0; row < c.len(); row++) {
            if (c.isNA(row)) {
              nc.addNA();
            } else {
              c.atStr(bs, row);
              String strRepresentation = bs.toString();
              if (strRepresentation.contains("\uFFFD")) {
                nc.addNum(lookupTable.get(bs.toSanitizedString()), 0);
              } else {
                nc.addNum(lookupTable.get(strRepresentation), 0);
              }
            }
          }
        }
      };
      // Invoke tasks - one input vector, one ouput vector
      task.doAll(new byte[] {Vec.T_CAT}, vec);
      // Return result
      return task.outputFrame(null, null, new String[][] {vecDomain}).vec(0);
    }
docs: Create a new {@link Vec} of categorical values from string {@link Vec}.
  
  FIXME: implement in more efficient way with Brandon's primitives for BufferedString manipulation
  
  @param vec a string {@link Vec}
  @return a categorical {@link Vec}