001/*
002 * $Id: Word.java 5317 2015-11-01 17:42:27Z kredel $
003 */
004
005package edu.jas.poly;
006
007
008import java.util.SortedMap;
009import java.util.TreeMap;
010
011import edu.jas.structure.MonoidElem;
012import edu.jas.structure.MonoidFactory;
013import edu.jas.structure.NotInvertibleException;
014
015
016/**
017 * Word implements strings of letters for polynomials.
018 * @author Heinz Kredel
019 */
020
021public final class Word implements MonoidElem<Word> {
022
023
024    /**
025     * Defining alphabet in WordFactory.
026     */
027    public final WordFactory mono;
028
029
030    /**
031     * The data structure is a String of characters.
032     */
033    /*package*/final String val;
034
035
036    /**
037     * Stored hash code.
038     */
039    protected int hash = 0;
040
041
042    /**
043     * Constructor for Word.
044     * @param m factory for words.
045     */
046    public Word(WordFactory m) {
047        this(m, "");
048    }
049
050
051    /**
052     * Constructor for Word.
053     * @param m factory for words.
054     * @param s String
055     */
056    public Word(WordFactory m, String s) {
057        this(m, s, true);
058    }
059
060
061    /**
062     * Constructor for Word.
063     * @param m factory for words.
064     * @param s String
065     * @param translate indicator if s needs translation
066     */
067    public Word(WordFactory m, String s, boolean translate) {
068        mono = m;
069        hash = 0;
070        if (s == null) {
071            throw new IllegalArgumentException("null string not allowed");
072        }
073        if (translate) {
074            if (mono.translation != null) {
075                //System.out.println("s = " + s);
076                String[] S = GenPolynomialTokenizer.variableList(s);
077                //System.out.println("S = " + Arrays.toString(S));
078                val = mono.translate(S);
079                //System.out.println("val = " + val);
080            } else {
081                val = WordFactory.cleanSpace(s); //??
082            }
083        } else {
084            val = s;
085        }
086    }
087
088
089    /**
090     * Get the corresponding element factory.
091     * @return factory for this Element.
092     * @see edu.jas.structure.Element#factory()
093     */
094    public MonoidFactory<Word> factory() {
095        return mono;
096    }
097
098
099    /**
100     * Copy this.
101     * @return copy of this.
102     */
103    @Override
104    public Word copy() {
105        return new Word(mono, val, false);
106    }
107
108
109    /**
110     * Get the word String.
111     * @return val.
112     */
113    /*package*/String getVal() {
114        return val;
115    }
116
117
118    /**
119     * Get the letter at position i.
120     * @param i position.
121     * @return val[i].
122     */
123    public char getVal(int i) {
124        return val.charAt(i);
125    }
126
127
128    /**
129     * Get the length of this word.
130     * @return val.length.
131     */
132    public int length() {
133        return val.length();
134    }
135
136
137    /**
138     * Get the string representation.
139     * @see java.lang.Object#toString()
140     */
141    @Override
142    public String toString() {
143        if (val.length() == 0) {
144            return "";
145        }
146        StringBuffer s = new StringBuffer("\"");
147        if (mono.translation == null) {
148            for (int i = 0; i < length(); i++) {
149                if (i != 0) {
150                    s.append(" ");
151                }
152                s.append(getVal(i));
153            }
154        } else {
155            for (int i = 0; i < length(); i++) {
156                if (i != 0) {
157                    s.append(" ");
158                }
159                s.append(mono.transVar(getVal(i)));
160            }
161        }
162        s.append("\"");
163        return s.toString();
164    }
165
166
167    /**
168     * Get a scripting compatible string representation.
169     * @return script compatible representation for this Element.
170     * @see edu.jas.structure.Element#toScript()
171     */
172    @Override
173    public String toScript() {
174        if (val.length() == 0) {
175            return "";
176        }
177        StringBuffer s = new StringBuffer("");
178        if (mono.translation == null) {
179            for (int i = 0; i < length(); i++) {
180                if (i != 0) {
181                    s.append("*"); // checked for python vs ruby
182                }
183                s.append(getVal(i));
184            }
185        } else {
186            for (int i = 0; i < length(); i++) {
187                if (i != 0) {
188                    s.append("*"); // checked for python vs ruby
189                }
190                s.append(mono.transVar(getVal(i)));
191            }
192        }
193        s.append("");
194        return s.toString();
195    }
196
197
198    /**
199     * Get a scripting compatible string representation of the factory.
200     * @return script compatible representation for this ElemFactory.
201     * @see edu.jas.structure.Element#toScriptFactory()
202     */
203    @Override
204    public String toScriptFactory() {
205        // Python case
206        return mono.toString();
207    }
208
209
210    /**
211     * Comparison with any other object.
212     * @see java.lang.Object#equals(java.lang.Object)
213     */
214    @Override
215    public boolean equals(Object B) {
216        if (!(B instanceof Word)) {
217            return false;
218        }
219        Word b = (Word) B;
220        // mono == b.mono ??
221        int t = this.compareTo(b);
222        //System.out.println("equals: this = " + this + " B = " + B + " t = " + t);
223        return (0 == t);
224    }
225
226
227    /**
228     * hashCode.
229     * @see java.lang.Object#hashCode()
230     */
231    @Override
232    public int hashCode() {
233        if (hash == 0) {
234            hash = val.hashCode();
235        }
236        return hash;
237    }
238
239
240    /**
241     * Is Word one.
242     * @return If this is the empty word then true is returned, else false.
243     */
244    public boolean isONE() {
245        return val.isEmpty();
246    }
247
248
249    /**
250     * Is Word unit.
251     * @return If this is a unit then true is returned, else false.
252     */
253    public boolean isUnit() {
254        return isONE();
255    }
256
257
258    /**
259     * Word multiplication.
260     * @param V other word.
261     * @return this * V.
262     */
263    public Word multiply(Word V) {
264        return new Word(mono, this.val + V.val, false);
265    }
266
267
268    /**
269     * Word divide.
270     * @param V other word.
271     * @return this / V.
272     */
273    public Word divide(Word V) {
274        return divideLeft(V);
275    }
276
277
278    /**
279     * Word divide left.
280     * @param V other word.
281     * @return this / V = left, with left * V = this.
282     */
283    public Word divideLeft(Word V) {
284        Word[] ret = divideWord(V,false);
285        // TODO: fail if both non zero?
286        if (!ret[1].isONE()) {
287            throw new IllegalArgumentException("not simple left dividable: left = " + ret[0] + ", right = "
288                            + ret[1] + ", use divideWord");
289        }
290        return ret[0];
291    }
292
293
294    /**
295     * Word divide right.
296     * @param V other word.
297     * @return this / V = right, with V * right = this.
298     */
299    public Word divideRight(Word V) {
300        Word[] ret = divideWord(V,true);
301        // TODO: fail if both non zero?
302        if (!ret[0].isONE()) {
303            throw new IllegalArgumentException("not simple right dividable: left = " + ret[0] + ", right = "
304                            + ret[1] + ", use divideWord");
305        }
306        return ret[1];
307    }
308
309
310    /**
311     * Word divide with prefix and suffix.
312     * @param V other word.
313     * @return [left,right] with left * V * right = this.
314     */
315    public Word[] divideWord(Word V) {
316        return divideWord(V,true); 
317    }
318
319    /**
320     * Word divide with prefix and suffix.
321     * @param V other word.
322     * @param first is true for first index, false for last index.
323     * @return [left,right] with left * V * right = this.
324     */
325    public Word[] divideWord(Word V, boolean first) {
326        int i;
327        if (first) {
328            i = this.val.indexOf(V.val);
329        } else {
330            i = this.val.lastIndexOf(V.val);
331        }
332        if (i < 0) {
333            throw new NotInvertibleException("not dividable: " + this + ", other " + V);
334        }
335        int len = V.val.length();
336        String pre = this.val.substring(0, i);
337        String suf = this.val.substring(i + len);
338        Word[] ret = new Word[2];
339        ret[0] = new Word(mono, pre, false);
340        ret[1] = new Word(mono, suf, false);
341        return ret;
342    }
343
344
345    /**
346     * Word remainder.
347     * @param V other word.
348     * @return this (this/V). <b>Note:</b> not useful.
349     */
350    public Word remainder(Word V) {
351        int i = this.val.indexOf(V.val);
352        if (i < 0) {
353            throw new NotInvertibleException("not dividable: " + this + ", other " + V);
354        }
355        return V;
356    }
357
358
359    /**
360     * Quotient and remainder by division of this by S.
361     * @param S a Word
362     * @return [this/S, this - (this/S)*S]. <b>Note:</b> not useful.
363     */
364    public Word[] quotientRemainder(Word S) {
365        return new Word[] { divide(S), remainder(S) };
366    }
367
368
369    /**
370     * Word inverse.
371     * @return 1 / this.
372     */
373    public Word inverse() {
374        if (val.length() == 0) {
375            return this;
376        }
377        throw new NotInvertibleException("not inversible " + this);
378    }
379
380
381    /**
382     * Word signum.
383     * @return 0 if this is one, 1 if it is non empty.
384     */
385    public int signum() {
386        int i = val.length();
387        if (i > 0) {
388            i = 1;
389        }
390        assert i >= 0;
391        return i;
392    }
393
394
395    /**
396     * Word degree.
397     * @return total degree of all letters.
398     */
399    public long degree() {
400        return val.length();
401    }
402
403
404    /**
405     * Word dependency on letters.
406     * @return sorted map of letters and the number of its occurences.
407     */
408    public SortedMap<String, Integer> dependencyOnVariables() {
409        SortedMap<String, Integer> map = new TreeMap<String, Integer>();
410        for (int i = 0; i < val.length(); i++) {
411            String s = String.valueOf(val.charAt(i));
412            Integer n = map.get(s);
413            if (n == null) {
414                n = 0;
415            }
416            n = n + 1;
417            map.put(s, n);
418        }
419        return map;
420    }
421
422
423    /**
424     * Word leading exponent vector.
425     * @return an ExpVector for the first power of a letter.
426     */
427    public ExpVector leadingExpVector() {
428        long n = 0;
429        char letter = ' ';
430        for (int i = 0; i < val.length(); i++) {
431            char s = val.charAt(i);
432            if (n == 0) {
433                letter = s;
434                n++;
435            } else if (letter == s) {
436                n++;
437            } else {
438                break;
439            }
440        }
441        int k = mono.length();
442        if (n == 0L){ // == isONE()
443            return ExpVector.create(k);
444        }
445        int j = k - mono.indexOf(letter) - 1;
446        return ExpVector.create(k,j,n);
447    }
448
449
450    /**
451     * Word without leading exponent vector.
452     * @return an Word without the first power of a letter.
453     */
454    public Word reductum() {
455        if (isONE()) {
456            return this;
457        }
458        int n = 0;
459        char letter = ' ';
460        for (int i = 0; i < val.length(); i++) {
461            char s = val.charAt(i);
462            if (n == 0) {
463                letter = s;
464                n++;
465            } else if (letter == s) {
466                n++;
467            } else {
468                break;
469            }
470        }
471        // n != 0
472        String r = val.substring(n); // n-1+1
473        return new Word(mono, r, false);
474    }
475
476
477    /**
478     * Word multiple test.
479     * @param V other word.
480     * @return true if this is a multiple of V, else false.
481     */
482    public boolean multipleOf(Word V) {
483        return this.val.contains(V.val);
484    }
485
486
487    /**
488     * Word divides test.
489     * @param V other word.
490     * @return true if this divides V, else false.
491     */
492    public boolean divides(Word V) {
493        return V.val.contains(this.val);
494    }
495
496
497    /**
498     * Word compareTo. Uses
499     * 
500     * <pre>
501     * String.compareTo
502     * </pre>
503     * 
504     * .
505     * @param V other word.
506     * @return 0 if U == V, -1 if U &lt; V, 1 if U &gt; V.
507     */
508    @Override
509    public int compareTo(Word V) {
510        return this.val.compareTo(V.val);
511    }
512
513
514    /**
515     * Word graded comparison. Compares first be degree, then lexicographical.
516     * @param V other word.
517     * @return 0 if U == V, -1 if U &lt; V, 1 if U &gt; V.
518     */
519    public int gradCompareTo(Word V) {
520        long e = this.degree();
521        long f = V.degree();
522        if (e < f) {
523            return 1;
524        } else if (e > f) {
525            return -1;
526        }
527        return this.compareTo(V);
528    }
529
530
531    /**
532     * Word graded comparison. Compares first be degree, then inverse
533     * lexicographical.
534     * @param V other word.
535     * @return 0 if U == V, -1 if U &lt; V, 1 if U &gt; V.
536     */
537    public int gradInvlexCompareTo(Word V) {
538        long e = this.degree();
539        long f = V.degree();
540        if (e < f) {
541            return 1;
542        } else if (e > f) {
543            return -1;
544        }
545        return -this.compareTo(V);
546    }
547
548
549    /**
550     * Is word overlap.
551     * @param ol = [l1,r1,l2,r2] an Overlap container of four words
552     * @param V word
553     * @return true if l1 * this * r1 = l2 * V * r2, else false.
554     */
555    public boolean isOverlap(Overlap ol, Word V) {
556        return ol.isOverlap(this, V);
557    }
558
559
560    /**
561     * Word overlap list.
562     * @param V other word.
563     * @return list of overlaps [l1,r1,l2,r2] with l1 * this * r1 = l2 * V * r2.
564     *         If no such overlaps exist the empty overlap list is returned.
565     */
566    public OverlapList overlap(Word V) {
567        OverlapList ret = new OverlapList();
568        Word wone = mono.getONE();
569        String a = this.val;
570        String b = V.val;
571        int ai = a.length();
572        int bi = b.length();
573        int j = b.indexOf(a);
574        if (j >= 0) {
575            while (j >= 0) {
576                String pre = b.substring(0, j);
577                String suf = b.substring(j + ai);
578                Word wpre = new Word(mono, pre, false);
579                Word wsuf = new Word(mono, suf, false);
580                ret.add(new Overlap(wpre, wsuf, wone, wone));
581                j = b.indexOf(a, j + ai); // +1 also inner overlaps ?
582            }
583            return ret;
584        }
585        j = a.indexOf(b);
586        if (j >= 0) {
587            while (j >= 0) {
588                String pre = a.substring(0, j);
589                String suf = a.substring(j + bi);
590                Word wpre = new Word(mono, pre, false);
591                Word wsuf = new Word(mono, suf, false);
592                ret.add(new Overlap(wone, wone, wpre, wsuf));
593                j = a.indexOf(b, j + bi); // +1 also inner overlaps ?
594            }
595            return ret;
596        }
597        if (ai >= bi) {
598            for (int i = 0; i < bi; i++) {
599                String as = a.substring(0, i + 1);
600                String bs = b.substring(bi - i - 1, bi);
601                //System.out.println("i = " + i + ", bs = " + bs + ", as = " + as);
602                if (as.equals(bs)) {
603                    Word w1 = new Word(mono, b.substring(0, bi - i - 1), false);
604                    Word w2 = new Word(mono, a.substring(i + 1), false);
605                    ret.add(new Overlap(w1, wone, wone, w2));
606                    break;
607                }
608            }
609            for (int i = 0; i < bi; i++) {
610                String as = a.substring(ai - i - 1, ai);
611                String bs = b.substring(0, i + 1);
612                //System.out.println("i = " + i + ", bs = " + bs + ", as = " + as);
613                if (as.equals(bs)) {
614                    Word w1 = new Word(mono, b.substring(i + 1), false);
615                    Word w2 = new Word(mono, a.substring(0, ai - i - 1), false);
616                    ret.add(new Overlap(wone, w1, w2, wone));
617                    break;
618                }
619            }
620        } else { // ai < bi
621            for (int i = 0; i < ai; i++) {
622                String as = a.substring(ai - i - 1, ai);
623                String bs = b.substring(0, i + 1);
624                //System.out.println("i = " + i + ", bs = " + bs + ", as = " + as);
625                if (as.equals(bs)) {
626                    Word w1 = new Word(mono, b.substring(i + 1), false);
627                    Word w2 = new Word(mono, a.substring(0, ai - i - 1), false);
628                    ret.add(new Overlap(wone, w1, w2, wone));
629                    break;
630                }
631            }
632            for (int i = 0; i < ai; i++) {
633                String as = a.substring(0, i + 1);
634                String bs = b.substring(bi - i - 1, bi);
635                //System.out.println("i = " + i + ", bs = " + bs + ", as = " + as);
636                if (as.equals(bs)) {
637                    Word w1 = new Word(mono, b.substring(0, bi - i - 1), false);
638                    Word w2 = new Word(mono, a.substring(i + 1), false);
639                    ret.add(new Overlap(w1, wone, wone, w2));
640                    break;
641                }
642            }
643        }
644        return ret;
645    }
646
647
648    /**
649     * Word pseudo least common multiple.
650     * @param V other word.
651     * @return w = l1*this*r1, with l1*this*r1 == l2*V*r2, if l1, r1, l2, r2
652     *         exist, else null is returned.
653     */
654    public Word lcm(Word V) {
655        OverlapList oll = overlap(V);
656        if (oll.ols.isEmpty()) {
657            return null;
658        }
659        Overlap ol = oll.ols.get(0);
660        Word w = ol.l1.multiply(this).multiply(ol.r1);
661        return w;
662    }
663
664}