Jeg havde nedenstående problem i en kodningstest, og jeg fik 28/30 testpas og 2 mislykkedes på grund til en time-out.

Problem
Du har oprettet et programmeringssprog, og nu har du besluttet at tilføje hashmap support til det. Det blev fundet, at det i almindelige programmeringssprog er umuligt at tilføje et tal til alle hashmap nøgler / værdier. Så du har besluttet at implementere din egen hashmap på dit nye sprog med følgende handlinger.

  • insert x y – indsæt og objekt med nøgle x og værdi y
  • get x – returner værdien af et objekt med nøglen x
  • addToKey x – tilføj x til alle nøgler på kortet
  • addToValue y – tilføj y til alle værdier på kort

Din opgave er at implementere denne hashmap, anvende de givne forespørgsler og finde sum af alle resultaterne for get operationer

For eksempel

  • For queryType=["insert","insert","addToValue","addToKey","get"] og query=[[1,2],[2,3],[2],[1],[3]], output skal være .

Forklaring

  1. insert 1 2 – hashmap vil være {1:2}
  2. insert 2 3 – hashmap vil være {1:2,2:3}
  3. addToValue 2 – hashmap vil være {1:4,2:5}
  4. addToKey 1 – hashmap vil være {2:4,3:5}
  5. get 3 – værdien er 5

Input / Output

  • [tidsbegrænsning for udførelse] 3 sekunder (Java)
  • [input] array.string queryType
    Array of forespørgselstyper. det er garanteret, at hver queryType[i] en af ovennævnte operationer
    1 < = queryType.length < = 10 ^ 5
  • [input] array.array.integer-forespørgsel
    Matrix med forespørgsler, hvor hver forespørgsel er nævnt med 2 tal til indsættelse og et nummer for andre Nøgleværdier er i området [-10 ^ 9,10 ^ 9]

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

Er der nogen anden datastruktur, jeg kan bruge i stedet for hashmap eller kan jeg forbedre min kode for at være mere lineær?

Kommentarer

  • Velkommen til Code Review. Jeg forstår ikke ‘ hvorfor du opretter en ny Map hver gang du laver insert eller get forespørgsler, hvis du kan forklare mig, hvorfor jeg sætter pris på det.
  • @dariosicily, Det er fordi jeg ikke ‘ t ønsker at overskrive den eksisterende værdi, mens du opdaterer en nøgle eller et kort. Eksempel: For {2: 3,3: 1}, hvis du vil tilføje nøgle 1 og værdi 1. I den første iteration bliver den {3: 4}. Her mister jeg det aktuelle 3: 1, som er det næste nøgleværdipar. Kort sagt for at undgå overskrivning / kollision af nøgleværdipar.
  • Tak, nu fik jeg det.

Svar

Jeg vil foreslå, at du opretter din egen OffsetIntegerMap, der kan kortlægge mellem heltal og håndtere en forskydning på tasterne og værdierne.

Du behøver ikke nødvendigvis at implementere HashMap fra bunden, definere din egen begrænsede grænseflade og implementere den med en eksisterende Map<Integer, Integer> gennem komposition.

Ved at håndtere forskydningerne adskilt fra tasterne og værdier ender kompleksiteten af forskydningsoperationerne O (1) i stedet for O (n), når du foretager genberegninger, og Map<> sæt og få operationer forblive ved deres oprindelige O (1).

Et eksempel på et ” 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; } } 

Ved at indkapsle forskydningslogikken bliver behandlingssløjfen også meget enklere uden at refactoring meget af noget 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

  • Tak. Ser ud til en bedre implementering. Jeg vil kontrollere denne logik.
  • Jeg finder dette svar øverst. Brug af en klasse, der er adskilt fra at teste alle sager.keyWithoutOffset og valueWithoutOffset (jeg tror, jeg så en fejl i den oprindelige kode w.r..t. til det). De klare navne (forskudt). Bare metodens navne er kortcentrerede i stedet for dem i kravene
  • Du kan bruge eksemplet fra spørgsmålet. Udskift bare [] med {}. queryType er String[] og query er int[][].
  • Ah, overset det. Og jeg ‘ er også forkælet med kodning af udfordringswebsteder, der bare giver mig en ” Kør ” knap :-). Ændret denne løsning til mit eget svar nu.
  • Forskydning vil ikke være den samme for hver tast i hashmap! – start med nøglesæt (1,2,3) – tilføj 10 til alle nøgler, nu er nøglesæt (10,11,12) – indsæt ny nøgle (5), nu er nøglesæt (10,11,12,5) – tilføj 10 til alle nøgler, nu er nøglesættet (20,21,22,15). Så de første 3 taster havde faktisk forskudt 20 tilføjet dem, men sidste nøgle havde kun forskudt 10 (dvs. nøgletilføjelser udført før denne nøgle (5) blev indsat vil blive ignoreret).

Svar

Jeg har nogle forslag til dig.

Uddrag noget af logikken til metoder.

I din kode, når forespørgslen er insert og get, har du to store blokke af kode, der er ens; du kan udtrække til en metode og genbruge metoden i begge sektioner.

Jeg foreslår en metode, der returnerer en boolsk baseret på if -tilstanden, så du bliver i stand til at sætte currValue og currKey variablerne til nul.

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

For at kontrollere den aktuelle forespørgsel currQuery kan du også udpakke hver af dem i en metode.

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

Brug altid primitiverne, når det er muligt

Når du ved, at det er umuligt at få en nulværdi med tallet, prøv at bruge primitiverne; de tager mindre hukommelse og er hurtigere end indpakningsklassen.

Før

 Integer currKey = 0; Integer currValue = 0;  

Efter

 int currKey = 0; int currValue = 0;  

Prøv at placere mindre kode i switch blokke

Efter min mening bliver koden mindre læselig, når der er mere end 3 linjer med koder i en switchblok; Jeg foreslår, at du konverterer det til en is-else-if. Denne konvertering gør koden kortere og mere læselig.

Før

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 er addToKey x, der tilføjer x til alle nøgler på kortet, fordi du i det væsentlige skal oprette en ny indtastningsnøgle, værdi + x i din hashmap og slet den gamle indtastningsnøgle, værdi. For at undgå behovet for at cache den gamle post, mens det gentages over kortet, kan du skelne mellem to tilfælde:

x > 0, så hvis du har iterering over en keyset bestilt faldende, der er ikke behov for at cache de gamle poster

x < 0, samme tilgang men keyset bestilles stigende

Fordi du bruger hashmap, er der ingen garanteret nøgleordre, så du har brug for data struktur for at gemme nøgler, der skal bestilles, før det gentages over nøgler som nedenfor:

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

Jeg udelukkede sagen 0 fordi map forbliver uberørt. Andre operationer behøver ikke rækkefølgen af nøglerne, og som allerede antydet kan det være bedre at prøve at isolere hver operation i en privat metode.

Kommentarer

  • Tak @dariosicily for svaret. Er ‘ ikke sortering hver gang, mens du foretager addToKey, er det også dyrt. Eller kan jeg bruge a SortedMap for at holde indsættelsesrækkefølgen faldende. Ligesom SortedMap<Integer, Integer>values = new TreeMap<Integer, Integer>(Collections.reverseOrder());
  • @Praveen Du er velkommen. Ja det er sortering hver gang, men med ArrayList efter sortering fortsætter du lineært.Jeg blev dømt for, at du kun kunne bruge HashMap; hvis du kan bruge TreeMap i stedet for HashMap kan du bruge en iterator og en omvendt iterator og iterere over din TreeMap på en lige måde.

Svar

Ændret version af Johnbots svar uden en ekstra klasse. Jeg synes, at den ekstra klasse er overkill og snarere distraherer fra algoritmen, da jeg er nødt til at søge gennem en masse kode (meget af det kedelplade) for at se, hvad der foregår. Det er ikke den ekstra klasse, der gør behandlingssløjfen meget enklere. Det er algoritmen.

Yderligere ændringer:

  • keyOffset er ikke klart for mig i hvilken retning det er forskudt, så jeg omdøbte det til addedToKey (ligeledes for værdi).
  • Bestilte operationen navne som i problemspecifikationen, både for at holde mig tæt på specifikationen, og fordi den rækkefølge giver mig mere mening.
  • Introduceret args for at gemme nogle gentagelser af kode.
  • Brugt long / Long til alt, ikke kun for summen. Når alt kommer til alt kan tilføjelse til nøglerne / værdierne få dem til at løbe over, hvis vi bare bruger 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

  • Hvorfor tilføjer addedToKey ensartet til værdi ‘ s nøgle fungerer ikke, men alligevel trækkes den fra insert og get handlinger arbejde?

Svar

Hvad med bare at gemme en forskydningsværdi for nøgler og værdier og opbygge indpakningsmetoder rundt om hashmaps få / sæt metoder til at tage højde for denne forskydning.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *