public String explainScore(StringWrapper s, StringWrapper t) {
        BagOfTokens sBag = (BagOfTokens) s;
        BagOfTokens tBag = (BagOfTokens) t;
        StringBuilder buf = new StringBuilder("");
        PrintfFormat fmt = new PrintfFormat("%.3f");
        buf.append("Common tokens: ");
        for (Iterator<Token> i = sBag.tokenIterator(); i.hasNext();) {
            Token tok = i.next();
            if (tBag.contains(tok)) {
                buf.append(" " + tok.getValue() + ": ");
                buf.append(fmt.sprintf(sBag.getWeight(tok)));
                buf.append("*");
                buf.append(fmt.sprintf(tBag.getWeight(tok)));
            } else {
                // find best matching token
                double matchScore = tokenMatchThreshold;
                Token matchTok = null;
                for (Iterator<Token> j = tBag.tokenIterator(); j.hasNext();) {
                    Token tokJ = j.next();
                    double distItoJ = tokenDistance.score(tok.getValue(), tokJ.getValue());
                    if (distItoJ >= matchScore) {
                        matchTok = tokJ;
                        matchScore = distItoJ;
                    }
                }
                if (matchTok != null) {
                    buf.append(" '" + tok.getValue() + "'~='" + matchTok.getValue() + "': ");
                    buf.append(fmt.sprintf(sBag.getWeight(tok)));
                    buf.append("*");
                    buf.append(fmt.sprintf(tBag.getWeight(matchTok)));
                    buf.append("*");
                    buf.append(fmt.sprintf(matchScore));
                }
            }
        }
        buf.append("\nscore = " + score(s, t));
        return buf.toString();
    }