【问题标题】:Idiomatically find the number of occurrences a given value has in an array惯用地查找给定值在数组中出现的次数
【发布时间】:2013-06-26 06:46:41
【问题描述】:

我有一个包含重复值的数组。我想找到任何给定值的出现次数。

例如,如果我有一个这样定义的数组:var dataset = [2,2,4,2,6,4,7,8];,我想找出某个值在数组中出现的次数。也就是说,程序应该显示如果我有 3 次出现值 2,1 次出现值 6,等等。

最惯用/最优雅的方式是什么?

【问题讨论】:

    标签: javascript arrays


    【解决方案1】:

    reduce 在这里比filter 更合适,因为它不会为计数而构建临时数组。

    var dataset = [2,2,4,2,6,4,7,8];
    var search = 2;
    
    var count = dataset.reduce(function(n, val) {
        return n + (val === search);
    }, 0);
    
    console.log(count);

    在 ES6 中:

    let count = dataset.reduce((n, x) => n + (x === search), 0);
    

    请注意,很容易将其扩展为使用自定义匹配谓词,例如,对具有特定属性的对象进行计数:

    people = [
        {name: 'Mary', gender: 'girl'},
        {name: 'Paul', gender: 'boy'},
        {name: 'John', gender: 'boy'},
        {name: 'Lisa', gender: 'girl'},
        {name: 'Bill', gender: 'boy'},
        {name: 'Maklatura', gender: 'girl'}
    ]
    
    var numBoys = people.reduce(function (n, person) {
        return n + (person.gender == 'boy');
    }, 0);
    
    console.log(numBoys);

    统计所有项目,即在javascript中制作一个像{x:count of xs}这样的对象很复杂,因为对象键只能是字符串,所以你不能可靠地统计一个混合类型的数组。不过,以下简单的解决方案在大多数情况下都可以很好地工作:

    count = function (ary, classifier) {
        classifier = classifier || String;
        return ary.reduce(function (counter, item) {
            var p = classifier(item);
            counter[p] = counter.hasOwnProperty(p) ? counter[p] + 1 : 1;
            return counter;
        }, {})
    };
    
    people = [
        {name: 'Mary', gender: 'girl'},
        {name: 'Paul', gender: 'boy'},
        {name: 'John', gender: 'boy'},
        {name: 'Lisa', gender: 'girl'},
        {name: 'Bill', gender: 'boy'},
        {name: 'Maklatura', gender: 'girl'}
    ];
    
    // If you don't provide a `classifier` this simply counts different elements:
    
    cc = count([1, 2, 2, 2, 3, 1]);
    console.log(cc);
    
    // With a `classifier` you can group elements by specific property:
    
    countByGender = count(people, function (item) {
        return item.gender
    });
    console.log(countByGender);

    2017 年更新

    在 ES6 中,您可以使用 Map 对象可靠地计算任意类型的对象。

    class Counter extends Map {
        constructor(iter, key=null) {
            super();
            this.key = key || (x => x);
            for (let x of iter) {
                this.add(x);
            }
        }
        add(x) {
          x = this.key(x);
          this.set(x, (this.get(x) || 0) + 1);
        }
    }
    
    // again, with no classifier just count distinct elements
    
    results = new Counter([1, 2, 3, 1, 2, 3, 1, 2, 2]);
    for (let [number, times] of results.entries())
        console.log('%s occurs %s times', number, times);
    
    
    // counting objects
    
    people = [
        {name: 'Mary', gender: 'girl'},
        {name: 'John', gender: 'boy'},
        {name: 'Lisa', gender: 'girl'},
        {name: 'Bill', gender: 'boy'},
        {name: 'Maklatura', gender: 'girl'}
    ];
    
    
    chessChampions = {
        2010: people[0],
        2012: people[0],
        2013: people[2],
        2014: people[0],
        2015: people[2],
    };
    
    results = new Counter(Object.values(chessChampions));
    for (let [person, times] of results.entries())
        console.log('%s won %s times', person.name, times);
    
    // you can also provide a classifier as in the above
    
    byGender = new Counter(people, x => x.gender);
    for (let g of ['boy', 'girl'])
       console.log("there are %s %ss", byGender.get(g), g);

    Counter 的类型感知实现可能如下所示(Typescript):

    type CounterKey = string | boolean | number;
    
    interface CounterKeyFunc<T> {
        (item: T): CounterKey;
    }
    
    class Counter<T> extends Map<CounterKey, number> {
        key: CounterKeyFunc<T>;
    
        constructor(items: Iterable<T>, key: CounterKeyFunc<T>) {
            super();
            this.key = key;
            for (let it of items) {
                this.add(it);
            }
        }
    
        add(it: T) {
            let k = this.key(it);
            this.set(k, (this.get(k) || 0) + 1);
        }
    }
    
    // example:
    
    interface Person {
        name: string;
        gender: string;
    }
    
    
    let people: Person[] = [
        {name: 'Mary', gender: 'girl'},
        {name: 'John', gender: 'boy'},
        {name: 'Lisa', gender: 'girl'},
        {name: 'Bill', gender: 'boy'},
        {name: 'Maklatura', gender: 'girl'}
    ];
    
    
    let byGender = new Counter(people, (p: Person) => p.gender);
    
    for (let g of ['boy', 'girl'])
        console.log("there are %s %ss", byGender.get(g), g);
    

    【讨论】:

    • @thg435 我尝试使用它并最终得到一个未定义的值......而且,reduce 方法仍然保留数组吗?我想保持数组不变。
    • @Bagavatu:发布您的代码/小提琴。不,reduce 不会改变数组。
    • @Narshe:添加了一个 Typescript 示例。
    • 这是一个非常快速的响应。太感谢了。但我有个问题。新版本丢失了密钥可以为空的选项。不确定是否有意:constructor(iter, key=null)this.key = key || (x =&gt; x);
    • @Narshe:这很难强制键入,因为x=&gt;x 的类型是T=&gt;T,而该类被设计为接受T=&gt;CounterKey。我建议将其分为两类:仅Counter(没有回调)和KeyCounter(强制回调)。
    【解决方案2】:
    array.filter(c => c === searchvalue).length;
    

    【讨论】:

    • 这是最好的答案。
    • 通俗易懂,为你+1!
    【解决方案3】:

    这是一次显示所有个计数的一种方法:

    var dataset = [2, 2, 4, 2, 6, 4, 7, 8];
    var counts = {}, i, value;
    for (i = 0; i < dataset.length; i++) {
        value = dataset[i];
        if (typeof counts[value] === "undefined") {
            counts[value] = 1;
        } else {
            counts[value]++;
        }
    }
    console.log(counts);
    // Object {
    //    2: 3,
    //    4: 2,
    //    6: 1,
    //    7: 1,
    //    8: 1
    //}
    

    【讨论】:

      【解决方案4】:

      更新的浏览器只是因为使用了Array.filter

      var dataset = [2,2,4,2,6,4,7,8];
      var search = 2;
      var occurrences = dataset.filter(function(val) {
          return val === search;
      }).length;
      console.log(occurrences); // 3
      

      【讨论】:

      • 或者,更短的:occurrences = dataset.filter(v =&gt; v === search).length
      【解决方案5】:
      const dataset = [2,2,4,2,6,4,7,8];
      const count = {};
      
      dataset.forEach((el) => {
          count[el] = count[el] + 1 || 1
      });
      
      console.log(count)
      
      //  {
      //    2: 3,
      //    4: 2,
      //    6: 1,
      //    7: 1,
      //    8: 1
      //  }
      

      【讨论】:

      • 他检查每个循环是否已经存在密钥。如果是,则将 的当前值加一。如果不为此键插入 1。
      【解决方案6】:

      使用正常循环,您可以一致且可靠地找到出现次数:

      const dataset = [2,2,4,2,6,4,7,8];
      
      function getNumMatches(array, valToFind) {
          let numMatches = 0;
          for (let i = 0, j = array.length; i < j; i += 1) {
              if (array[i] === valToFind) {
                  numMatches += 1;
              }
          }
          return numMatches;
      }
      
      alert(getNumMatches(dataset, 2)); // should alert 3
      

      演示: https://jsfiddle.net/a7q9k4uu/

      为了使其更通用,该函数可以接受具有自定义逻辑的谓词函数(返回true/false),这将确定最终计数。例如:

      const dataset = [2,2,4,2,6,4,7,8];
      
      function getNumMatches(array, predicate) {
          let numMatches = 0;
          for (let i = 0, j = array.length; i < j; i += 1) {
              const current = array[i];
              if (predicate(current) === true) {
                  numMatches += 1;
              }
          }
          return numMatches;
      }
      
      const numFound = getNumMatches(dataset, (item) => {
          return item === 2;
      });
      
      alert(numFound); // should alert 3
      

      演示: https://jsfiddle.net/57en9nar/1/

      【讨论】:

      • +1 用于包装语义上可理解和可读的函数。虽然我会称之为getNumMatchesgetNumOccurrencesfindOccurrences 听起来您正在尝试返回事件本身(一个数组),而不是它们有多少(一个数字)。
      • 另外,正如其他人所提到的,您可以传入谓词函数而不是值来测试匹配。演示:repl.it/repls/DecimalFrostyTechnicians
      • @AdamZerner 同意,感谢您的意见。更新了您的建议
      【解决方案7】:

      您可以使用reduce 在一行中计算数组中的所有项目。

      [].reduce((a,b) => (a[b] = a[b] + 1 || 1) && a, {})
      

      这将产生一个对象,其键是数组中的不同元素,值是数组中元素的出现次数。然后,您可以通过访问对象上的相应键来访问一个或多个计数。

      例如,如果您要将上述内容包装在一个名为 count() 的函数中:

      function count(arr) {
        return arr.reduce((a,b) => (a[b] = a[b] + 1 || 1) && a, {})
      }
      
      count(['example'])          // { example: 1 }
      count([2,2,4,2,6,4,7,8])[2] // 3
      

      【讨论】:

        【解决方案8】:

        您可以在JavaScript 1.8 中使用array.reduce(callback[, initialValue]) 方法

        var dataset = [2,2,4,2,6,4,7,8],
            dataWithCount = dataset.reduce( function( o , v ) {
        
                if ( ! o[ v ] ) {
                    o[ v ] = 1 ;  
                }  else {
                    o[ v ] = o[ v ] + 1;
                }      
        
                return o ;    
        
            }, {} );
        
        // print data with count.
        for( var i in  dataWithCount ){
             console.log( i + 'occured ' + dataWithCount[i] + 'times ' ); 
        }
        
        // find one number
        var search = 2,
            count = dataWithCount[ search ] || 0;
        

        【讨论】:

          【解决方案9】:

          我发现最后得到一个对象列表更有用,其中包含一个用于计数的键和一个用于计数的键:

          const data = [2,2,4,2,6,4,7,8]
          let counted = []
          for (var c of data) {
            const alreadyCounted = counted.map(c => c.name)
            if (alreadyCounted.includes(c)) {
              counted[alreadyCounted.indexOf(c)].count += 1
            } else {
              counted.push({ 'name': c, 'count': 1})
            }
          }
          console.log(counted)
          

          返回:

          [ { name: 2, count: 3 },
            { name: 4, count: 2 },
            { name: 6, count: 1 },
            { name: 7, count: 1 },
            { name: 8, count: 1 } ]
          

          这不是最干净的方法,如果有人知道如何使用reduce 达到相同的结果,请告诉我。但是,它确实产生了一个相当容易使用的结果。

          【讨论】:

            【解决方案10】:

            首先,您可以通过线性搜索来使用蛮力解决方案。

            public int LinearSearchcount(int[] A, int data){
              int count=0;
              for(int i=0;i<A.length;i++) {
                if(A[i]==data) count++;
              }
              return count;
            }
            

            但是,为此,我们得到的时间复杂度为 O(n)。但是通过使用二分搜索,我们可以提高复杂度。

            【讨论】:

              【解决方案11】:

              如果您尝试这样做,您可能会收到如下所示的错误。

              array.reduce((acc, arr) => acc + (arr.label === 'foo'), 0); // Operator '+' cannot be applied to type 'boolean'.
              

              一种解决方案是这样做

              array = [
                  { id: 1, label: 'foo' },
                  { id: 2, label: 'bar' },
                  { id: 3, label: 'foo' },
                  { id: 4, label: 'bar' },
                  { id: 5, label: 'foo' }
              ]
              
              array.reduce((acc, arr) => acc + (arr.label === 'foo' ? 1 : 0), 0); // result: 3
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2011-04-29
                • 1970-01-01
                • 2019-10-27
                • 2017-06-27
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多