如何有条件地计算对象中元素的数量?
How to conditionally count the number of elements in an Object?
我有一个对象,例如
{
a: {
x: 1,
y: 2
},
b: {
x: 1,
y: 2
},
c: {
x: 100,
y: 2
},
}
我想统计满足条件x: 1
的元素个数。有没有直接的方法来做到这一点?
我可以走简单的路,但我想学习 JavaScriptonic 的路(如果有的话):
let data = {
a: {
x: 1,
y: 2
},
b: {
x: 1,
y: 2
},
c: {
x: 100,
y: 2
},
}
let counter = 0
for (k in data) {
if (data[k].x === 1) {
counter += 1
}
}
console.log(counter)
// 2
我能想到的最干净的方法是使用 Object.values
:
console.log(Object.values(data).filter(v => v.x === 1).length); // 2
您可以使用 .reduce()
function combined with Object.values()
,这是一个工作片段:
let data = {
a: {
x: 1,
y: 2
},
b: {
x: 1,
y: 2
},
c: {
x: 100,
y: 2
},
}
let counter = Object.values(data).reduce((acc,item) => {
(item.x === 1) ? acc++: 0;
return acc
}, 0);
console.log(counter)
正如 Aplet123 所指出的,可能最短的答案是
Object .values (data) .filter (v => v.x == 1) .length
虽然这很好,但它会创建一些不必要的对象,创建一个新的匹配对象数组,只是在检索其计数后将其丢弃。除非您经常这样做或使用大量数据集,否则这不太可能成为问题,但需要牢记这一点。
其他替代方案,无论是原始循环的某些变体还是基于 reduce
的解决方案都可以正常工作,尽管它们可能有点冗长。
不过,我建议您从可重用部件的角度考虑这一点。即使是这么简单的问题也可以进一步分解。让我们想象一下,我们正试图将这个练习变成一个可重用的函数。
Q: What does this proposed function do?
A: It counts all the object properties of our input which have an x
value of 1
.
Q: Well, we've already done that with
(data) => Object .values (data) .filter (v => v.x == 1) .length
Is this enough?
A: I don't see why not. It does what we need.
Q: So you think you'll never need to find all those with x
value of 2
?
A: Well, maybe, but if I do, I can just write a new version like
(data) => Object .values (data) .filter (v => v.x == 2) .length.
There's nothing wrong with that, is there?
Q: And then if you need x
value of 3
?
A: Then I can just do it again, right? This is easy
Q: (a dirty look)
A: Ok, I get it! We need to parameterize that value. How about writing
(xVal, data) => Object .values (data) .filter (v => v.x == xVal) .length
Q: Does that feel like progress?
A: Perhaps, but I feel like you're going to spring something now...
Q: Me? Never!
A: I think I can guess what's next. You're going to ask what happens if I need to count the objects with a y
value of 1
.
Q: You are learning, young Padawan! That's exactly right. So, how would you handle that?
A: I think we can just do
(name, val, data) => Object .values (data) .filter (v => v[name] == val) .length
(...thinks for a short while...) Yes, that should work.
Q: So now if you need to count those where both x
equals 1
and y
equals 3
. Is it easy to adapt this for that problem?
A: It's not trivial. But I can see how I might pass to our function something like {x: 1, y: 3}
and check each of the properties of that object. That is certainly doable. Is that how you think I should write it?
Q: Now, don't get impatient! We'll get there. That might be a useful function. But what happens if I want to count those where property x
is greater than property 'y', and both are greater than 42
?
A: Time out! This is straying really far from the original requirement. Why should I be concerned with a case like that?
Q: What if I were to tell you that you with about the same number of lines of code as in your original approach, we can solve all these problems at once? Would that intrigue you?
A: I suppose, but you make it sound as though we could count the matching properties for any condition at all. That sounds crazy.
Q: That's exactly what I'm suggesting. How could we go about that?
A: I don't know. It doesn't make much sense to me. You'd have to pass a whole program into our function. How do you do that?
Q: Well, perhaps not a program. How about a function?
A: I suppose that would do it too. But we can't pass functions around willy-nilly, can we?
Q: We did it above.
A: What do you mean?
Q: What did we pass to the filter
method of Array.prototype
in our answers so far?
A: An arrow function. Yes, I get it, but that's for some magic built-in part of the language. Just because we can pass a function to Array.prototype.filter
, it doesn't meant that we can pass one to our own function. How would that work?
Q: Let's try it and see. If we're going to have a parameter, we have to name it. What do you think we should call it?
A: I know. How about magicScrewyThingThatTeacherThinksWillWorkButIStillDoubt
?
Q: Sure, but you get to type it every time!
A: Seriously, though, I don't know. Do you have a suggestion?
Q: I often call such functions pred
.
A: Short for "predator"? "Predilection"? "Prednisone"?...
Q: "Predicate". A function that returns true
or false
.
A: Ok, and then we count the true
s, right?
Q: Exactly, so how would we write this?
A: Well, we would start with (data, pred) => Object .values (data) .filter (v =>
... But then what?
Q: Well, then we want to call our function, right? How do we call a function?
A: We just use the function name then the arguments inside parentheses. But we don't have the true function name here. All we have is that pred
.
Q: But inside our call, that is the function name. So we can do the same thing. What would that look like?
A: I suppose it would be just
(data, pred) => Object .values (data) .filter (v => pred (v)) .length
Q: And how would we apply that to our original problem?
A: Can I pass an arrow function like we did to filter
?
Q: Absolutely.
A: Then... something like this?
const countMatchingProps = (data, pred) => Object.values (data) .filter (v => pred (v)) .length
countMatchingProps (data, v => v.x == 1) //=> 2
Q: Does that work?
A: Yes! Yes it does. So we've reached a solution?
Q: Well no, not quite yet. First off, what have we been learning about currying?
A: That again? I know how to do it. I still don't really understand why. We didn't do that in our Java classes.
Q: Ok, but here we write 99% of our functions in a curried form. How would this function change?
A: So we put the parameter least likely to change first, then the one next most likely to change, and so on. For our two parameters, this means the predicate goes first, then the data. So how about this?
const countMatchingProps = (pred) => (data) =>
Object.values (data) .filter (v => pred (v)) .length
Does that look good?
Q: You've got to admit it's getting better... Getting so much better all the time.
A: Umm...
Q: Never mind (muttering "Youth these days!"). That's much nicer.
A: "...but"?
Q: There's one minor thing. What does v => pred(v)
do?
A: It's a function. It accepts a value, and returns true
or false
.
Q: Exactly. And what does pred
do?
A: It's a function too. It accepts a value, and returns true
or false
. Wait! What does that mean?
Q: It means one more simplification.
A: Can I really replace v => pred(v)
with just pred
? It really feels like cheating somehow.
Q: Let's try it and see.
A:
const countMatchingProps = (pred) => (data) => Object.values (data) .filter (pred) .length
countMatchingProps (v => v.x == 1) (data) //=> 2
Yes, that works fine. But I'm not sure I want to include that arrow function...
Q: remember that...
A: I know, I know, "remember that we prefer to call arrow functions lambdas." I still don't know why. Anyway, I'm not sure I want to include that lambda every time I need to count those with x
property of 1
. Can I make a specific function from this for my use-case?
Q: That's what the lessons about currying were supposed to be for. Don't you recall how we do this?
A: That's ... partial application? Of the curried function?
Q: (nods)
A: So, I can just write
const countX1s = countMatchingProps (v => v.x == 1)
// ... later
countX1s (data) //=> 2
Oh, that makes sense. Why didn't you just say this when we were discussing currying?
Q: (exasperated sigh)
A: Ok, so we've finally arrived at our answer!
Q: Well...
A: Oh no! More?
Q: Just one more step. What if we wanted to do something similar for arrays? Count all those which match some condition?
A: Well after that, it's easy:
const countMatches = (pred) => (arr) => arr .filter (pred) .length
Q: Do you see similarities between countMatches
and countMatchingProps
?
A: Well, yeah, it's as though countMatches
is embedded in countMatchingProps
.
Q: Exactly. So what do we do in cases like that?
A: We refactor.
Q: Exactly. How would that look?
A: I think it's easy enough:
const countMatches = (pred) => (arr) => arr .filter (pred) .length
const countMatchingProps = (pred) => (arr) => countMatches (pred) (Object .values (arr))
const countX1s = countMatchingProps (v => v.x == 1)
// ... later
countX1s (data) //=> 2
Q: And how do you feel about this code?
A: Well, we started from a simple requirement and what we wrote is much, much more powerful. That's got to be good.
Q: And has the code gotten much more complex?
A: Well, compared to my initial for
-loop version, it's probably less complex. But it's still more complex than the version from Aplet123.
Q: Yes, abstraction always has it's cost. But how do you like this version of the code?
A: I'm still absorbing it. But I think I really like it. Is this how you would write it?
Q: No, but that's a lesson for another day.
A: Come on, after you subjected me to your rendition of the Beatles? I think you owe me.
Q: Oh, so you did recognize the song? Perhaps there's still hope for today's youth... Ok. My version might look like this:
const count = compose (length, filter)
const countProps = compose2 (count, identity, values)
const countX1s = countProps (propEq ('x', 1))
A: Huh?
Q: As I said, a lesson for another day.
使用 forEach
let data = {
a: {
x: 1,
y: 2
},
b: {
x: 1,
y: 2
},
c: {
x: 100,
y: 2
},
}
let counter = 0
Object.values(data).forEach(item=>{
item.x===1?counter++:0;
});
console.log(counter)
我有一个对象,例如
{
a: {
x: 1,
y: 2
},
b: {
x: 1,
y: 2
},
c: {
x: 100,
y: 2
},
}
我想统计满足条件x: 1
的元素个数。有没有直接的方法来做到这一点?
我可以走简单的路,但我想学习 JavaScriptonic 的路(如果有的话):
let data = {
a: {
x: 1,
y: 2
},
b: {
x: 1,
y: 2
},
c: {
x: 100,
y: 2
},
}
let counter = 0
for (k in data) {
if (data[k].x === 1) {
counter += 1
}
}
console.log(counter)
// 2
我能想到的最干净的方法是使用 Object.values
:
console.log(Object.values(data).filter(v => v.x === 1).length); // 2
您可以使用 .reduce()
function combined with Object.values()
,这是一个工作片段:
let data = {
a: {
x: 1,
y: 2
},
b: {
x: 1,
y: 2
},
c: {
x: 100,
y: 2
},
}
let counter = Object.values(data).reduce((acc,item) => {
(item.x === 1) ? acc++: 0;
return acc
}, 0);
console.log(counter)
正如 Aplet123 所指出的,可能最短的答案是
Object .values (data) .filter (v => v.x == 1) .length
虽然这很好,但它会创建一些不必要的对象,创建一个新的匹配对象数组,只是在检索其计数后将其丢弃。除非您经常这样做或使用大量数据集,否则这不太可能成为问题,但需要牢记这一点。
其他替代方案,无论是原始循环的某些变体还是基于 reduce
的解决方案都可以正常工作,尽管它们可能有点冗长。
不过,我建议您从可重用部件的角度考虑这一点。即使是这么简单的问题也可以进一步分解。让我们想象一下,我们正试图将这个练习变成一个可重用的函数。
Q: What does this proposed function do?
A: It counts all the object properties of our input which have an
x
value of1
.Q: Well, we've already done that with
(data) => Object .values (data) .filter (v => v.x == 1) .length
Is this enough?
A: I don't see why not. It does what we need.
Q: So you think you'll never need to find all those with
x
value of2
?A: Well, maybe, but if I do, I can just write a new version like
(data) => Object .values (data) .filter (v => v.x == 2) .length.
There's nothing wrong with that, is there?
Q: And then if you need
x
value of3
?A: Then I can just do it again, right? This is easy
Q: (a dirty look)
A: Ok, I get it! We need to parameterize that value. How about writing
(xVal, data) => Object .values (data) .filter (v => v.x == xVal) .length
Q: Does that feel like progress?
A: Perhaps, but I feel like you're going to spring something now...
Q: Me? Never!
A: I think I can guess what's next. You're going to ask what happens if I need to count the objects with a
y
value of1
.Q: You are learning, young Padawan! That's exactly right. So, how would you handle that?
A: I think we can just do
(name, val, data) => Object .values (data) .filter (v => v[name] == val) .length
(...thinks for a short while...) Yes, that should work.
Q: So now if you need to count those where both
x
equals1
andy
equals3
. Is it easy to adapt this for that problem?A: It's not trivial. But I can see how I might pass to our function something like
{x: 1, y: 3}
and check each of the properties of that object. That is certainly doable. Is that how you think I should write it?Q: Now, don't get impatient! We'll get there. That might be a useful function. But what happens if I want to count those where property
x
is greater than property 'y', and both are greater than42
?A: Time out! This is straying really far from the original requirement. Why should I be concerned with a case like that?
Q: What if I were to tell you that you with about the same number of lines of code as in your original approach, we can solve all these problems at once? Would that intrigue you?
A: I suppose, but you make it sound as though we could count the matching properties for any condition at all. That sounds crazy.
Q: That's exactly what I'm suggesting. How could we go about that?
A: I don't know. It doesn't make much sense to me. You'd have to pass a whole program into our function. How do you do that?
Q: Well, perhaps not a program. How about a function?
A: I suppose that would do it too. But we can't pass functions around willy-nilly, can we?
Q: We did it above.
A: What do you mean?
Q: What did we pass to the
filter
method ofArray.prototype
in our answers so far?A: An arrow function. Yes, I get it, but that's for some magic built-in part of the language. Just because we can pass a function to
Array.prototype.filter
, it doesn't meant that we can pass one to our own function. How would that work?Q: Let's try it and see. If we're going to have a parameter, we have to name it. What do you think we should call it?
A: I know. How about
magicScrewyThingThatTeacherThinksWillWorkButIStillDoubt
?Q: Sure, but you get to type it every time!
A: Seriously, though, I don't know. Do you have a suggestion?
Q: I often call such functions
pred
.A: Short for "predator"? "Predilection"? "Prednisone"?...
Q: "Predicate". A function that returns
true
orfalse
.A: Ok, and then we count the
true
s, right?Q: Exactly, so how would we write this?
A: Well, we would start with
(data, pred) => Object .values (data) .filter (v =>
... But then what?Q: Well, then we want to call our function, right? How do we call a function?
A: We just use the function name then the arguments inside parentheses. But we don't have the true function name here. All we have is that
pred
.Q: But inside our call, that is the function name. So we can do the same thing. What would that look like?
A: I suppose it would be just
(data, pred) => Object .values (data) .filter (v => pred (v)) .length
Q: And how would we apply that to our original problem?
A: Can I pass an arrow function like we did to
filter
?Q: Absolutely.
A: Then... something like this?
const countMatchingProps = (data, pred) => Object.values (data) .filter (v => pred (v)) .length countMatchingProps (data, v => v.x == 1) //=> 2
Q: Does that work?
A: Yes! Yes it does. So we've reached a solution?
Q: Well no, not quite yet. First off, what have we been learning about currying?
A: That again? I know how to do it. I still don't really understand why. We didn't do that in our Java classes.
Q: Ok, but here we write 99% of our functions in a curried form. How would this function change?
A: So we put the parameter least likely to change first, then the one next most likely to change, and so on. For our two parameters, this means the predicate goes first, then the data. So how about this?
const countMatchingProps = (pred) => (data) => Object.values (data) .filter (v => pred (v)) .length
Does that look good?
Q: You've got to admit it's getting better... Getting so much better all the time.
A: Umm...
Q: Never mind (muttering "Youth these days!"). That's much nicer.
A: "...but"?
Q: There's one minor thing. What does
v => pred(v)
do?A: It's a function. It accepts a value, and returns
true
orfalse
.Q: Exactly. And what does
pred
do?A: It's a function too. It accepts a value, and returns
true
orfalse
. Wait! What does that mean?Q: It means one more simplification.
A: Can I really replace
v => pred(v)
with justpred
? It really feels like cheating somehow.Q: Let's try it and see.
A:
const countMatchingProps = (pred) => (data) => Object.values (data) .filter (pred) .length countMatchingProps (v => v.x == 1) (data) //=> 2
Yes, that works fine. But I'm not sure I want to include that arrow function...
Q: remember that...
A: I know, I know, "remember that we prefer to call arrow functions lambdas." I still don't know why. Anyway, I'm not sure I want to include that lambda every time I need to count those with
x
property of1
. Can I make a specific function from this for my use-case?Q: That's what the lessons about currying were supposed to be for. Don't you recall how we do this?
A: That's ... partial application? Of the curried function?
Q: (nods)
A: So, I can just write
const countX1s = countMatchingProps (v => v.x == 1) // ... later countX1s (data) //=> 2
Oh, that makes sense. Why didn't you just say this when we were discussing currying?
Q: (exasperated sigh)
A: Ok, so we've finally arrived at our answer!
Q: Well...
A: Oh no! More?
Q: Just one more step. What if we wanted to do something similar for arrays? Count all those which match some condition?
A: Well after that, it's easy:
const countMatches = (pred) => (arr) => arr .filter (pred) .length
Q: Do you see similarities between
countMatches
andcountMatchingProps
?A: Well, yeah, it's as though
countMatches
is embedded incountMatchingProps
.Q: Exactly. So what do we do in cases like that?
A: We refactor.
Q: Exactly. How would that look?
A: I think it's easy enough:
const countMatches = (pred) => (arr) => arr .filter (pred) .length const countMatchingProps = (pred) => (arr) => countMatches (pred) (Object .values (arr)) const countX1s = countMatchingProps (v => v.x == 1) // ... later countX1s (data) //=> 2
Q: And how do you feel about this code?
A: Well, we started from a simple requirement and what we wrote is much, much more powerful. That's got to be good.
Q: And has the code gotten much more complex?
A: Well, compared to my initial
for
-loop version, it's probably less complex. But it's still more complex than the version from Aplet123.Q: Yes, abstraction always has it's cost. But how do you like this version of the code?
A: I'm still absorbing it. But I think I really like it. Is this how you would write it?
Q: No, but that's a lesson for another day.
A: Come on, after you subjected me to your rendition of the Beatles? I think you owe me.
Q: Oh, so you did recognize the song? Perhaps there's still hope for today's youth... Ok. My version might look like this:
const count = compose (length, filter) const countProps = compose2 (count, identity, values) const countX1s = countProps (propEq ('x', 1))
A: Huh?
Q: As I said, a lesson for another day.
使用 forEach
let data = {
a: {
x: 1,
y: 2
},
b: {
x: 1,
y: 2
},
c: {
x: 100,
y: 2
},
}
let counter = 0
Object.values(data).forEach(item=>{
item.x===1?counter++:0;
});
console.log(counter)