惯用地查找给定值在数组中出现的次数

Posted

技术标签:

【中文标题】惯用地查找给定值在数组中出现的次数【英文标题】: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,等等。

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

【问题讨论】:

【参考方案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

【讨论】:

以上是关于惯用地查找给定值在数组中出现的次数的主要内容,如果未能解决你的问题,请参考以下文章

冒泡排序数组去重判断每个值在数组中出现的次数。。

[剑指Offer] 37.数字在排序数组中出现的次数

过滤值在 PySpark 中出现的次数

计算选项集合中出现次数的惯用方法

给定一个集合,查找集合中一共多多少种不同的元素

搜索排序数组中出现次数超过一半的元素所需的最小比较