javascript - Hvordan telle bestemte elementer i en array?


Jeg har et array:

[1, 2, 3, 5, 2, 8, 9, 2]

Jeg vil gjerne vite hvor mange 2er det er i arrayet.

Hva er den mest elegante måten å gjøre det på i JavaScript uten å bruke for-løkke?

Svar #1

[dette svaret er litt utdatert: les redigeringene, i betydningen av 'equal' i javascript er tvetydig]

Si hei til vennene dine: map og filter og reduce og forEach og every osv.

(Jeg skriver bare for-løkker i javascript av og til, på grunn av at blokk-nivå skoping mangler, så du må uansett bruke en funksjon som selve kroppen av løkken hvis du trenger å fange eller klone iterasjonsindeksen eller verdien din. For-løkker er generelt mer effektive, men noen ganger trenger du en closure.)

Den mest lesbare måten:

[....].filter(x => x==2).length

(Vi kunne ha skrevet .filter(function(x){return x==2}).length i stedet)

Følgende er mer plass-effektivt (O(1) i stedet for O(N)), men jeg er ikke sikker på hvor stor fordel/ulempe det kan være når det gjelder tid (ikke mer enn en konstant faktor siden du besøker hvert element nøyaktig én gang):

[....].reduce((total,x) => (x==2 ? total+1 : total), 0)

eller som en kommentator vennlig påpekte:

[....].reduce((total,x) => total+(x==2), 0)

(Hvis du trenger å optimalisere denne spesielle koden, kan en for-løkke være raskere på noen nettlesere... du kan teste ting på jsperf.com.)


Du kan deretter være elegant og gjøre det om til en prototype-funksjon:

[1, 2, 3, 5, 2, 8, 9, 2].count(2)

Slik:

Object.defineProperties(Array.prototype, {
    count: {
        value: function(value) {
            return this.filter(x => x==value).length;
        }
    }
});

Du kan også bruke den vanlige gamle for-løkke-teknikken (se andre svar) inne i den ovennevnte egenskapsdefinisjonen (igjen, det vil sannsynligvis være mye raskere).


2017 redigering:

Ops, dette svaret har blitt mer populært enn det riktige svaret. Egentlig, bare bruk det aksepterte svaret. Selv om dette svaret kan være søtt, vil sannsynligvis ikke js-kompilatorene (eller kan ikke på grunn av spesifikasjonen) optimalisere slike tilfeller. Så du bør egentlig skrive en enkel for-løkke:

Object.defineProperties(Array.prototype, {
    count: {
        value: function(query) {
            /* 
               Counts number of occurrences of query in array, an integer >= 0 
               Uses the javascript == notion of equality.
            */
            var count = 0;
            for(let i=0; i<this.length; i++)
                if (this[i]==query)
                    count++;
            return count;
        }
    }
});

Du kan definere en versjon .countStrictEq(...) som brukte ===-begrepet for likhet. Likhetsbegrepet kan være viktig for det du gjør! (for eksempel [1,10,3,'10'].count(10)==2, fordi tall som '4'==4 i javascript... derfor understreker det å kalle det .countEq eller .countNonstrict at det bruker ==-operatoren.)

Forbehold: Å definere et vanlig navn på prototypen bør gjøres med forsiktighet. Det er greit hvis du kontrollerer koden din, men dårlig hvis alle ønsker å erklære sin egen [].count-funksjon, spesielt hvis de oppfører seg forskjellig. Du kan spørre deg selv «men .count(query) høres jo ganske perfekt og kanonisk ut»... men vurder kanskje at du kunne gjøre noe som [].count(x=> someExpr of x). I så fall definerer du funksjoner som countIn(query, container) (under myModuleName.countIn), eller noe, eller [].myModuleName_count().

Vurder også å bruke din egen multiset-datastruktur (f.eks. som Pythons 'collections.Counter') for å unngå å måtte utføre tellingen i første omgang. Dette fungerer for eksakte treff av formen [].filter(x=> x==???).length (verste tilfelle O(N) ned til O(1)), og modifisert vil det gjøre spørringer av formen [].filter(filterFunction).length raskere (omtrent med en faktor #total/#duplikater).

class Multiset extends Map {
    constructor(...args) {
        super(...args);
    }
    add(elem) {
        if (!this.has(elem))
            this.set(elem, 1);
        else
            this.set(elem, this.get(elem)+1);
    }
    remove(elem) {
        var count = this.has(elem) ? this.get(elem) : 0;
        if (count>1) {
            this.set(elem, count-1);
        } else if (count==1) {
            this.delete(elem);
        } else if (count==0)
            throw `tried to remove element ${elem} of type ${typeof elem} from Multiset, but does not exist in Multiset (count is 0 and cannot go negative)`;
            // alternatively do nothing {}
    }
}

Demo :

> counts = new Multiset([['a',1],['b',3]])
Map(2) {"a" => 1, "b" => 3}

> counts.add('c')
> counts
Map(3) {"a" => 1, "b" => 3, "c" => 1}

> counts.remove('a')
> counts
Map(2) {"b" => 3, "c" => 1}

> counts.remove('a')
Uncaught tried to remove element a of type string from Multiset, but does not exist in Multiset (count is 0 and cannot go negative)

bemerkningsside: Men, hvis du fortsatt ønsket funksjonell-programmeringsmåten (eller en engangs one-liner uten å overstyre Array.prototype), kunne du skrive det mer kortfattet nå til dags som [...].filter(x => x==2).length. Hvis du bryr deg om ytelse, merk at selv om dette asymptotisk har samme ytelse som for-løkka (O(N) tid), kan det kreve O(N) ekstra minne (i stedet for O(1) minne) fordi det nesten helt sikkert vil generere et mellomliggende array og deretter telle elementene i det mellomliggende arrayet.

Svar #2

Moderne JavaScript:

Merk at du alltid bør bruke trippel likhet === når du gjør sammenligning i JavaScript (JS). Trippel likhet sørger for at JS-sammenligning oppfører seg som dobbelt likhet == i andre språk (det er én unntak, se nedenfor). Følgende løsning viser hvordan man kan løse dette på en funksjonell måte, som vil sikre at du aldri får out of bounds error:

// Let has local scope
let array = [1, 2, 3, 5, 2, 8, 9, 2]

// Functional filter with an Arrow function
// Filter all elements equal to 2 and return the length (count)
array.filter(x => x === 2).length  // -> 3

Følgende anonyme Arrow-funksjon (lambda-funksjon) i JavaScript:

(x) => {
   const k = 2
   return k * x
}

kan forenkles til denne konsise formen for én enkelt inngang:

x => 2 * x

der return er underforstått.

Bruk alltid trippel likhet: === for sammenligning i JS, med unntak av når du sjekker for nullverdi: if (noe == null) {} da det inkluderer en sjekk for undefined, hvis du kun bruker dobbelt likhet som i dette tilfellet.

Svar #3

Veldig enkelt:

var count = 0;
for(var i = 0; i < array.length; ++i){
    if(array[i] == 2)
        count++;
}

Svar #4

2017: Hvis noen fortsatt er interessert i spørsmålet, er min løsning følgende:

const arrayToCount = [1, 2, 3, 5, 2, 8, 9, 2];
const result = arrayToCount.filter(i => i === 2).length;
console.log('number of the found elements: ' + result);

Svar #5

Her er en ES2017+-måte å få tellene for alle array-elementene på i O(N):

const arr = [1, 2, 3, 5, 2, 8, 9, 2];
const counts = {};

arr.forEach((el) => {
  counts[el] = counts[el] ? (counts[el] + 1) : 1;
});

Du kan også valgfritt sortere utdataene:

const countsSorted = Object.entries(counts).sort(([_, a], [__, b]) => a - b);

console.log(countsSorted) for eksempel-arrayet ditt:

[
  [ '2', 3 ],
  [ '1', 1 ],
  [ '3', 1 ],
  [ '5', 1 ],
  [ '8', 1 ],
  [ '9', 1 ]
]

Svar #6

Hvis du bruker lodash eller underscore, vil _.countBy-metoden gi et objekt med summerte totaler nøkkelt med hver verdi i arrayen. Du kan gjøre dette om til en én-liner hvis du bare trenger å telle én verdi:

_.countBy(['foo', 'foo', 'bar'])['foo']; // 2

Dette fungerer også fint på arrayer av tall. Én-linjer løsningen for ditt eksempel ville være:

_.countBy([1, 2, 3, 5, 2, 8, 9, 2])[2]; // 3

Relaterte artikler