Měl jsem níže uvedený problém v testu kódování a dostal jsem 28/30 testů a 2 selhaly kvůli do vypršení časového limitu.

Problém
Vytvořili jste programovací jazyk a nyní jste se rozhodli přidat k němu hashmap podporu. Bylo zjištěno, že v běžných programovacích jazycích je nemožné přidat číslo ke všem klíčům / hodnotám hashmap. Takže jste se rozhodli implementovat vlastní hashmap ve vašem novém jazyce s následujícími operacemi.

  • insert x y – vložte a objekt pomocí klíče x a hodnoty y
  • get x – vrátí hodnotu objektu pomocí klíče x
  • addToKey x – přidat x ke všem klíčům v mapě
  • addToValue y – přidat y ke všem hodnotám v mapě

Vaším úkolem je implementovat tento hashmap, použít zadané dotazy a vyhledat součet všech výsledků pro get operací

Například

  • Pro queryType=["insert","insert","addToValue","addToKey","get"] a query=[[1,2],[2,3],[2],[1],[3]], výstup by měl být .

Vysvětlení

  1. insert 1 2 – hashmap bude {1:2}
  2. insert 2 3 – hashmap bude {1:2,2:3}
  3. addToValue 2 – hashmap bude {1:4,2:5}
  4. addToKey 1 – hashmap bude {2:4,3:5}
  5. get 3 – hodnota je 5

vstup / Výstup

  • [časový limit provedení] 3 sekundy (Java)
  • [input] array.string queryType
    pole typy dotazů. je zaručeno, že každá queryType[i] kterákoli z výše uvedených operací
    1 < = queryType.length < = 10 ^ 5
  • [vstup] array.array.integer dotaz
    Pole dotazů, kde je každý dotaz uveden dvěma čísly pro vložení a jedním číslem pro ostatní Klíčové hodnoty jsou v rozsahu [-10 ^ 9,10 ^ 9]

Níže je moje řešení v Java

long hashMap(String[] queryType, int[][] query) { long sum = 0; Integer currKey = 0; Integer currValue = 0; Map<Integer, Integer> values = new HashMap<>(); for (int i = 0; i < queryType.length; i++) { String currQuery = queryType[i]; switch (currQuery) { case "insert": HashMap<Integer, Integer> copiedValues = new HashMap<>(); if (currKey != 0 || currValue != 0) { Set<Integer> keys = values.keySet(); for (Integer key : keys) { copiedValues.put(key + currKey, values.get(key) + currValue); } values.clear(); values.putAll(copiedValues); currValue = 0; currKey = 0; } values.put(query[i][0], query[i][1]); break; case "addToValue": currValue += values.isEmpty() ? 0 : query[i][0]; break; case "addToKey": currKey += values.isEmpty() ? 0 : query[i][0]; break; case "get": copiedValues = new HashMap<>(); if (currKey != 0 || currValue != 0) { Set<Integer> keys = values.keySet(); for (Integer key : keys) { copiedValues.put(key + currKey, values.get(key) + currValue); } values.clear(); values.putAll(copiedValues); currValue = 0; currKey = 0; } sum += values.get(query[i][0]); } } return sum; } 

Existuje nějaká jiná datová struktura, kterou mohu použít místo hashmap nebo mohu vylepšit můj kód je lineárnější?

Komentáře

  • Vítejte v Code Review. Nerozumím ‚ tomu, proč vytváříte nový Map pokaždé, když vytvoříte insert nebo get dotazy, pokud mi můžete vysvětlit, proč si toho vážím.
  • @dariosicily, protože jsem ‚ Při aktualizaci klíče nebo mapy nechcete přepsat stávající hodnotu. Příklad: Pro {2: 3,3: 1}, chcete-li přidat klíč 1 a hodnotu 1. V první iteraci se stane {3: 4}. Tady ztratím skutečný poměr 3: 1, což je další pár klíč-hodnota. Stručně řečeno, aby nedošlo k přepsání / kolizi párů klíčových hodnot.
  • Díky, teď to mám.

Odpovědět

Navrhuji, abyste si vytvořili vlastní OffsetIntegerMap, který může mapovat mezi celými čísly a zpracovávat offset klíčů a hodnot.

You nemusíte nutně implementovat HashMap od nuly, definujte si své vlastní omezené rozhraní a implementujte jej pomocí existujícího Map<Integer, Integer> prostřednictvím kompozice.

Zpracováním posunů odděleně od klíčů a hodnot končí složitost operací posunutí O (1) místo O (n) při přepočtech a Map<> operace put a get zůstanou na původním O (1).

Příklad “ OffsetIntegerMap „:

import java.util.HashMap; import java.util.Map; public class OffsetIntegerMap { private final Map<Integer, Integer> actualMap; private int keyOffset = 0; private int valueOffset = 0; public OffsetIntegerMap() { actualMap = new HashMap<>(); } public int size() { return actualMap.size(); } public boolean isEmpty() { return actualMap.isEmpty(); } public boolean containsKey(int key) { var keyWithoutOffset = key - keyOffset; return actualMap.containsKey(keyWithoutOffset); } public boolean containsValue(int value) { var valueWithoutOffset = value - valueOffset; return actualMap.containsValue(valueWithoutOffset); } public Integer get(int key) { var keyWithoutOffset = key - keyOffset; var value = actualMap.get(keyWithoutOffset); if (value == null) return null; return value + valueOffset; } public Integer put(int key, int value) { var keyWithoutOffset = key - keyOffset; var valueWithoutOffset = value - valueOffset; var oldValue = actualMap.put(keyWithoutOffset, valueWithoutOffset); if (oldValue == null) return null; return oldValue + valueOffset; } public Integer remove(int key) { var keyWithoutOffset = key - keyOffset; var oldValue = actualMap.remove(keyWithoutOffset); if (oldValue == null) return null; return oldValue + valueOffset; } public void clear() { actualMap.clear(); keyOffset = 0; valueOffset = 0; } public int getKeyOffset() { return keyOffset; } public void setKeyOffset(int keyOffset) { this.keyOffset = keyOffset; } public int getValueOffset() { return valueOffset; } public void setValueOffset(int valueOffset) { this.valueOffset = valueOffset; } public void addToValues(int toAdd) { this.valueOffset += toAdd; } public void addToKeys(int toAdd) { this.keyOffset += toAdd; } } 

Zapouzdřením ofsetové logiky se smyčka zpracování také stává mnohem jednodušší, aniž by co nejvíce refaktorovala ing:

static long hashMap(String[] queryType, int[][] query) { long sum = 0; var map = new OffsetIntegerMap(); for (int i = 0; i < queryType.length; i++) { String currQuery = queryType[i]; switch (currQuery) { case "insert": map.put(query[i][0], query[i][1]); break; case "addToValue": map.addToValues(query[i][0]); break; case "addToKey": map.addToKeys(query[i][0]); break; case "get": sum += map.get(query[i][0]); } } return sum; } 

Komentáře

  • Děkuji. Vypadá to na lepší implementaci. Zkontroluji tuto logiku.
  • Tuto odpověď považuji za nejlepší. Použití třídy oddělené od testování všech případů.keyWithoutOffset a valueWithoutOffset (myslím, že jsem viděl chybu v původním kódu w.r..t. k tomu). Jasné názvy (offset). Jen názvy metod jsou Map centrické místo těch v požadavcích.
  • Můžete použít příklad z otázky. Stačí nahradit [] znakem {}. queryType je String[] a query je int[][].
  • Ah, přehlédl jsem to. A já ‚ jsem příliš zkažený kódováním výzevných webů, které mi právě “ spustit “ knoflík :-). Upraveno toto řešení do mé vlastní odpovědi.
  • Ofset nebude stejný pro každý klíč v hashmapě! – začít se sadou klíčů (1,2,3) – přidat 10 ke všem klíčům, nyní sada klíčů je (10,11,12) – vložit nový klíč (5), nyní sada klíčů je (10,11,12,5) – přidat 10 na všechny klíče, nyní sada klíčů je (20,21,22,15). První 3 klíče tedy účinně obsahovaly offset 20, ale poslední klíč měl offset pouze 10 (tj. Přidání klíče provedené před vložením tohoto klíče (5) bude ignorováno).

Odpověď

Mám pro vás několik návrhů.

Extrahujte část logiky metod.

Ve vašem kód, když je dotaz insert a get, máte dva velké bloky kódu, které jsou podobné; můžete extrahovat na metodu a znovu ji použít v obou částech.

Navrhuji metodu, která vrací logickou hodnotu založenou na podmínce if, takže budete schopen nastavit proměnné currValue a currKey na nulu.

  long hashMap(String[] queryType, int[][] query) { //[...] switch (currQuery) { //[...] case "insert": if (didWeCopiedValuesToMap(currKey, currValue, values)) { currValue = 0; currKey = 0; } values.put(query[i][0], query[i][1]); break; //[...] } //[...] } private boolean didWeCopiedValuesToMap(Integer currKey, Integer currValue, Map<Integer, Integer> values, HashMap<Integer, Integer> copiedValues) { if (currKey != 0 || currValue != 0) { Set<Integer> keys = values.keySet(); for (Integer key : keys) { copiedValues.put(key + currKey, values.get(key) + currValue); } values.clear(); values.putAll(copiedValues); return true; } return false; }  

Chcete-li zkontrolovat aktuální dotaz currQuery, můžete každý z nich extrahovat metodou.

 private boolean isGet(String currQuery) { return "get".equals(currQuery); } private boolean isAddToKey(String currQuery) { return "addToKey".equals(currQuery); } private boolean isAddToValue(String currQuery) { return "addToValue".equals(currQuery); } private boolean isInsert(String currQuery) { return "insert".equals(currQuery); }  

Vždy, když je to možné, používejte primitiva

Když vězte, že není možné získat nulovou hodnotu s číslem, zkuste použít primitiva; zabírají méně paměti a jsou rychlejší než obalová třída.

Před

 Integer currKey = 0; Integer currValue = 0;  

Po

 int currKey = 0; int currValue = 0;  

Zkuste vložit méně kódu do switch bloků

Podle mého názoru se kód stává méně čitelným, pokud jsou v přepínacím bloku více než 3 řádky kódů; Navrhuji, abyste jej převedli na is-else-if. Díky této konverzi bude kód kratší a čitelnější.

Před

switch (currQuery) { case "insert": if (didWeCopiedValuesToMap(currKey, currValue, values)) { currValue = 0; currKey = 0; } values.put(query[i][0], query[i][1]); break; case "addToValue": currValue += values.isEmpty() ? 0 : query[i][0]; break; case "addToKey": currKey += values.isEmpty() ? 0 : query[i][0]; break; case "get": if (didWeCopiedValuesToMap(currKey, currValue, values)) { currValue = 0; currKey = 0; } sum += values.get(query[i][0]); }

Po

 if ("insert".equals(currQuery)) { if (didWeCopiedValuesToMap(currKey, currValue, values)) { currValue = 0; currKey = 0; } values.put(query[i][0], query[i][1]); } else if ("addToValue".equals(currQuery)) { currValue += values.isEmpty() ? 0 : query[i][0]; } else if ("addToKey".equals(currQuery)) { currKey += values.isEmpty() ? 0 : query[i][0]; } else if ("get".equals(currQuery)) { if (didWeCopiedValuesToMap(currKey, currValue, values)) { currValue = 0; currKey = 0; } sum += values.get(query[i][0]); }  

Refaktorovaný kód

  long hashMap(String[] queryType, int[][] query) { long sum = 0; int currKey = 0; int currValue = 0; Map<Integer, Integer> values = new HashMap<>(); for (int i = 0; i < queryType.length; i++) { String currQuery = queryType[i]; if (isInsert(currQuery)) { if (didWeCopiedValuesToMap(currKey, currValue, values)) { currValue = 0; currKey = 0; } values.put(query[i][0], query[i][1]); } else if (isAddToValue(currQuery)) { currValue += values.isEmpty() ? 0 : query[i][0]; } else if (isAddToKey(currQuery)) { currKey += values.isEmpty() ? 0 : query[i][0]; } else if (isGet(currQuery)) { if (didWeCopiedValuesToMap(currKey, currValue, values)) { currValue = 0; currKey = 0; } sum += values.get(query[i][0]); } } return sum; } private boolean isGet(String currQuery) { return "get".equals(currQuery); } private boolean isAddToKey(String currQuery) { return "addToKey".equals(currQuery); } private boolean isAddToValue(String currQuery) { return "addToValue".equals(currQuery); } private boolean isInsert(String currQuery) { return "insert".equals(currQuery); } private boolean didWeCopiedValuesToMap(int currKey, int currValue, Map<Integer, Integer> values) { HashMap<Integer, Integer> copiedValues = new HashMap<>(); if (currKey != 0 || currValue != 0) { Set<Integer> keys = values.keySet(); for (Integer key : keys) { copiedValues.put(key + currKey, values.get(key) + currValue); } values.clear(); values.putAll(copiedValues); return true; } return false; }  

odpověď

nejvíce nákladnou operací je addToKey x, který přidává x ke všem klíčům v mapě, protože v podstatě musíte ve svém hashmap a odstraňte starý vstupní klíč, hodnotu. Abyste se vyhnuli nutnosti ukládat do mezipaměti starou položku při iteraci mapy, můžete rozlišit dva případy:

x > 0, pokud tedy iterujete přes keyset seřazeno sestupně není třeba ukládat do mezipaměti staré záznamy

x < 0, stejný přístup, ale keyset je seřazeno vzestupně

Protože používáte hashmap, není zaručeno žádné pořadí klíčů, takže potřebujete data struktura pro uložení objednaných klíčů před iterací nad klíči, jak je uvedeno níže:

private static void addtoKey(Map<Integer, Integer> map, int i) { if (i != 0) { List<Integer> list = new ArrayList<>(map.keySet()); if (i > 0) { Collections.sort(list, Collections.reverseOrder()); } else { Collections.sort(list); } for(int key : list) { map.put(key + i, map.get(key)); map.remove(key); } } } 

Vyloučil jsem případ 0 protože map zůstává nedotčen. Jiné operace nepotřebují pořadí klíčů a jak již bylo navrženo, mohlo by být lepší pokusit se izolovat každou operaci soukromou metodou.

Komentáře

  • Děkuji @dariosicily za odpověď. Není ‚ t třídění pokaždé, když provádíme addToKey operace také nákladné? Nebo Mohu použít a SortedMap k udržení sestupné objednávky. Stejně jako SortedMap<Integer, Integer>values = new TreeMap<Integer, Integer>(Collections.reverseOrder());
  • @Praveen jste vítáni. Ano je třídění pokaždé, ale s ArrayList po seřazení postupujete lineárně.Byl jsem usvědčen, že můžete použít pouze HashMap; pokud můžete použít TreeMap místo HashMap, můžete použít iterátor a reverzní iterátor a iterovat přes TreeMap přímým způsobem.

Odpověď

Upravená verze Johnbotova odpověď bez extra třídy. Myslím, že ta extra třída je přehnaná a spíše odvádí pozornost od algoritmu, protože musím prohledávat hodně kódu (hodně z nich), abyste zjistili, co se děje. Není to taková extra třída, díky níž je smyčka zpracování mnohem jednodušší. Je to algoritmus.

Další změny:

  • keyOffset mi není jasné, kterým směrem je to odsazeno, tak jsem to přejmenoval na addedToKey (podobně pro hodnotu).
  • Objednal operaci názvy jako ve specifikaci problému, a to jednak proto, aby zůstaly blízké specifikaci, jednak proto, že mi toto pořadí dává větší smysl.
  • Zavedeno args pro uložení opakování kódu.
  • Použité long / Long pro všechno, nejen pro součet. Koneckonců, přidání do klíčů / hodnot by mohlo způsobit jejich přetečení, pokud použijeme int / Integer.
static long hashMap(String[] queryType, int[][] query) { Map<Long, Long> map = new HashMap<>(); long sum = 0, addedToKey = 0, addedToValue = 0; for (int i = 0; i < query.length; i++) { int[] args = query[i]; switch (queryType[i]) { case "insert": map.put(args[0] - addedToKey, args[1] - addedToValue); break; case "get": sum += map.get(args[0] - addedToKey) + addedToValue; break; case "addToKey": addedToKey += args[0]; break; case "addToValue": addedToValue += args[0]; } } return sum; } 

Komentáře

  • Proč jednotně přidává addedToKey do klíč hodnoty ‚ nefunguje, ale odečte jej pro akce insert a get práce?

Odpověď

Co takhle jen uložit hodnotu offsetu pro klíče a hodnoty a vytvořit obálkové metody kolem metody hashmaps get / put pro zohlednění tohoto posunu.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *