package com.freetymekiyan.algorithms.level.medium;

import org.junit.Assert;
import org.junit.Test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This is a follow up of Shortest Word Distance. The only difference is now you are given the list of words and your
 * method will be called repeatedly many times with different parameters. How would you optimize it?
 * <p>
 * Design a class which receives a list of words in the constructor, and implements a method that takes two words word1
 * and word2 and return the shortest distance between these two words in the list.
 * <p>
 * For example,
 * Assume that words = ["practice", "makes", "perfect", "coding", "makes"].
 * <p>
 * Given word1 = “coding”, word2 = “practice”, return 3.
 * Given word1 = "makes", word2 = "coding", return 1.
 * <p>
 * Note:
 * You may assume that word1 does not equal to word2, and word1 and word2 are both in the list.
 * <p>
 * Company Tags: LinkedIn
 * Tags: Hash Table, Design
 * Similar Problems: (E) Merge Two Sorted Lists, (E) Shortest Word Distance, (M) Shortest Word Distance III
 */
public class ShortestWordDistance2 {

    @Test
    public void testExamples() {
        WordDistance w = new WordDistance(new String[]{"a", "b"});
        Assert.assertEquals(1, w.shortest("a", "b"));
        Assert.assertEquals(1, w.shortest("b", "a"));
    }

    /**
     * Hash Table.
     * Store the word to all its indices mapping.
     * Get the shortest distance from two lists.
     */
    public class WordDistance {

        Map<String, List<Integer>> locs;

        public WordDistance(String[] words) {
            locs = new HashMap<>();
            for (int i = 0; i < words.length; i++) {
                if (!locs.containsKey(words[i])) {
                    locs.put(words[i], new ArrayList<>());
                }
                locs.get(words[i]).add(i);
            }
        }

        /**
         * Merge. O(m + n).
         * The indices are already sorted in the list.
         * To get shortest distance, just move the pointer with smaller value.
         * For i < indices1.size(), j < indices2.size():
         * | Get indices in two lists, index1 and index2.
         * | If index1 > index2:
         * |   Update shortest with min(shortest, index1 - index2).
         * |   j++.
         * | Else
         * |   Update shortest with min(shortest, index2 - index1).
         * |   i++.
         */
        public int shortest(String word1, String word2) {
            List<Integer> indices1 = locs.get(word1);
            List<Integer> indices2 = locs.get(word2);
            int shortest = Integer.MAX_VALUE;
            for (int i = 0, j = 0; i < indices1.size() && j < indices2.size(); ) {
                int index1 = indices1.get(i);
                int index2 = indices2.get(j);
                if (index1 > index2) {
                    shortest = Math.min(shortest, index1 - index2);
                    j++;
                } else {
                    shortest = Math.min(shortest, index2 - index1);
                    i++;
                }
            }gst
            return shortest;
        }
    }

// Your WordDistance object will be instantiated and called as such:
// WordDistance wordDistance = new WordDistance(words);
// wordDistance.shortest("word1", "word2");
// wordDistance.shortest("anotherWord1", "anotherWord2");
}

add shortest distance 2