Jag hade problemet nedan i ett kodningstest och jag fick 28/30 testpass och 2 misslyckades på grund av till en timeout.

Problem
Du har skapat ett programmeringsspråk och nu har du beslutat att lägga till hashmap support till det. Det visade sig att det i vanliga programmeringsspråk är omöjligt att lägga till ett nummer till alla hashmap nycklar / värden. Så du har bestämt dig för att implementera din egen hashmap på ditt nya språk med följande åtgärder.

  • insert x y – infoga och objekt med nyckel x och värde y
  • get x – returnera värdet på ett objekt med tangenten x
  • addToKey x – lägg till x till alla nycklar på kartan
  • addToValue y – lägg till y till alla värden på kartan

Din uppgift är att implementera denna hashmap, tillämpa givna frågor och hitta summa av alla resultat för get operationer

Till exempel

  • För queryType=["insert","insert","addToValue","addToKey","get"] och query=[[1,2],[2,3],[2],[1],[3]] ska utgången vara .

Förklaring

  1. insert 1 2 – hashmap blir {1:2}
  2. insert 2 3 – hashmap blir {1:2,2:3}
  3. addToValue 2 – hashmap blir {1:4,2:5}
  4. addToKey 1 – hashmap blir {2:4,3:5}
  5. get 3 – värdet är 5

Input / Utdata

  • [tidsgräns för körning] 3 sekunder (Java)
  • [input] array.string queryType
    Array of frågetyper. det garanteras att varje queryType[i] någon av ovan nämnda operationer
    1 < = queryType.length < = 10 ^ 5
  • [input] array.array.integer fråga
    Array of queries, där varje fråga nämns av två siffror för infoga och ett nummer för andra Nyckelvärden ligger inom intervallet [-10 ^ 9,10 ^ 9]

Nedan är min lösning i 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; } 

Finns det någon annan datastruktur som jag kan använda istället för hashmap eller kan jag förbättra min kod för att vara mer linjär?

Kommentarer

  • Välkommen till Code Review. Jag förstår inte ’ varför du skapar en ny Map varje gång du gör insert eller get frågor om du kan förklara varför jag uppskattar det.
  • @dariosicily, det beror på att jag inte ’ t vill skriva över det befintliga värdet medan du uppdaterar en nyckel eller karta. Exempel: För {2: 3,3: 1}, om du vill lägga till nyckel 1 och värde 1. I den första iterationen blir den {3: 4}. Här kommer jag att förlora det faktiska 3: 1 som är nästa nyckelvärdepar. Kort sagt, för att undvika överskrivning / kollision av nyckelvärdepar.
  • Tack, nu fick jag det.

Svar

Jag föreslår att du skapar din egen OffsetIntegerMap som kan mappa mellan heltal och hantera en förskjutning på tangenter och värden.

Du behöver inte nödvändigtvis implementera HashMap från grunden, definiera ditt eget begränsade gränssnitt och implementera det med en befintlig Map<Integer, Integer> genom komposition.

Genom att hantera förskjutningarna separat från tangenterna och värdena kommer komplexiteten i förskjutningsoperationerna att hamna O (1) istället för O (n) vid omberäkningar och Map<> sätt och få operationer stanna vid sin ursprungliga O (1).

Ett exempel på ” 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; } } 

Genom att inkapsla offsetlogiken blir bearbetningsslingan också mycket enklare utan att refactorera mycket av någonting 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; } 

Kommentarer

  • Tack. Ser ut som ett bättre genomförande. Jag kommer att kontrollera den här logiken.
  • Jag hittar det här svaret högst upp. Använda en klass som är skild från att testa alla fall.keyWithoutOffset och valueWithoutOffset (jag tror att jag såg ett fel i den ursprungliga koden w.r..t. till det). De tydliga namnen (offset). Bara metodnamnen är kartcentrerade istället för de i kraven
  • Du kan använda exemplet från frågan. Byt bara ut [] med {}. queryType är String[] och query är int[][].
  • Ah, förbises det. Och jag ’ är också bortskämd av att koda utmaningssidor bara ger mig en ” Kör ” knapp :-). Modifierade den här lösningen till mitt eget svar nu.
  • Offset kommer inte att vara samma för varje nyckel i hashmap! – börja med tangentuppsättning (1,2,3) – lägg till 10 till alla tangenter, nu är tangentuppsättning (10,11,12) – infoga ny tangent (5), nu är tangentuppsättning (10,11,12,5) – lägg till 10 till alla nycklar, nu är nyckeluppsättningen (20,21,22,15). Så de första tre tangenterna hade faktiskt förskjutning 20 lagt till dem, men den sista tangenten hade bara förskjutit 10 (dvs att tangenttillägg gjorda innan denna nyckel (5) infördes ignoreras).

Svar

Jag har några förslag till dig.

Extrahera en del av logiken till metoder.

I din kod, när frågan är insert och get har du två stora kodblock som liknar; du kan extrahera till en metod och återanvända metoden i båda sektionerna.

Jag föreslår en metod som returnerar en boolean baserat på if -villkoret så att du blir kunna ställa in variablerna currValue och currKey.

  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; }  

För att kontrollera den aktuella frågan currQuery kan du extrahera var och en av dem med en metod.

 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); }  

Använd alltid primitiverna när det är möjligt

När du vet att det är omöjligt att få ett nollvärde med numret, försök att använda primitiverna. De tar mindre minne och är snabbare än omslagsklassen.

Före

 Integer currKey = 0; Integer currValue = 0;  

Efter

 int currKey = 0; int currValue = 0;  

Försök sätta mindre kod i switch block

Enligt min mening blir koden mindre läsbar när det finns mer än 3 rader koder i ett växelblock; Jag föreslår att du konverterar den till en is-else-if. Den här konverteringen gör koden kortare och mer läsbar.

Före

 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]); }  

Efter

 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]); }  

Refactored code

  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; }  

Svar

Det mest dyr operation är addToKey x som lägger till x till alla nycklar på kartan, eftersom du i huvudsak måste skapa en ny inmatningsnyckel, värde + x i din hashmap och ta bort den gamla inmatningsnyckeln, värdet. För att undvika behovet av att cacha den gamla posten medan den itererar över kartan kan du skilja mellan två fall:

x > 0, om du har iterera över en keyset beställt fallande, det finns inget behov av att cacha de gamla posterna

x < 0, samma tillvägagångssätt men keyset ordnas stigande

Eftersom du använder hashmap finns ingen nyckelordning garanterad, så du behöver en data struktur för att lagra nycklar som ska beställas, innan det går igenom nycklar som nedan:

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); } } } 

Jag utesluter fallet 0 eftersom map förblir orörd. Andra operationer behöver inte ordning på nycklarna och som redan föreslagits kan det vara bättre att försöka isolera alla operationer i en privat metod.

Kommentarer

  • Tack @dariosicily för svaret. Är inte ’ t sortering varje gång medan addToKey är också kostsamt ?. Eller kan jag använda a SortedMap för att hålla infogningsordningen fallande. Gilla, SortedMap<Integer, Integer>values = new TreeMap<Integer, Integer>(Collections.reverseOrder());
  • @Praveen Du är välkommen. Ja det är sortering varje gång, men med ArrayList efter sortering fortsätter du linjärt.Jag dömdes till att du bara kunde använda HashMap; om du kan använda TreeMap istället för HashMap kan du använda en iterator och en omvänd iterator och itera över din TreeMap på ett rakt sätt.

Svar

Ändrad version av Johnbots svar utan en extra klass. Jag tror att den extra klassen är överdriven och snarare distraherar från algoritmen, eftersom jag måste söka igenom mycket kod (en hel del panna) för att se vad som händer. Det är inte den extra klassen som gör bearbetningsslingan mycket enklare. Det är algoritmen.

Ytterligare ändringar:

  • keyOffset är inte klart för mig i vilken riktning det förskjuts, så jag döpte om det till addedToKey (även för värde).
  • Beställde operationen namn som i problemspecifikationen, både för att hålla mig nära specifikationen och för att den ordningen är mer vettig för mig.
  • Introducerade args för att spara lite kodupprepning.
  • Används long / Long för allt, inte bara för summan. När allt kommer omkring kan tillägg till nycklarna / värdena få dem att rinna över om vi bara använder 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; } 

Kommentarer

  • Varför lägger du enhetligt till addedToKey till värde ’ s-tangenten fungerar inte, men subtraherar den för insert och get -åtgärderna arbete?

Svar

Vad sägs om att bara lagra ett offsetvärde för nycklar och värden och bygga omslagsmetoder runt hashmaps get / put-metoder för att ta hänsyn till denna förskjutning.

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *