I have a simple data generator function like this. In summary, I have an array, and I am polling the data from the data generator, validating if the value can be added to the array, and then pushing the value to the array:
function* dataGeneratorFactory() {
while (true) {
for (const v of "abcdefghik") {
yield v;
}
}
}
const shouldExclude = (letter) => letter === "b" || letter === "e";
const items = [];
const maxItemsLength = 10;
const dataGenerator = dataGeneratorFactory();
while (items.length < maxItemsLength) {
const nextItem = dataGenerator.next();
if (shouldExclude(nextItem.value)) continue;
items.push(nextItem.value);
}
console.log(items);
My goal is to translate this logic into rxjs pattern. My only constraint is that the dataGeneratorFactory function is not modifiable. I came up with this:
// maxItemsLength, dataGenerator, and dataGeneratorFactory are
// from the code snippet above
import { from, map, mergeAll, range, takeWhile } from "rxjs";
const observeable = range(0, maxItemsLength).pipe(
map(() => from(dataGenerator).pipe(takeWhile(shouldExclude))),
mergeAll()
);
observeable.subscribe({
next: (e) => console.log(e),
});
However, I am not seeing anything by the console logger. What did I do wrong in this code?
The take operator should suffice:
import { filter, from, take, toArray } from "rxjs";
function* dataGeneratorFactory() {
while (true) {
for (const v of "abcdefghik") {
yield v;
}
}
}
const maxItemsLength = 10;
const shouldExclude = (letter) => letter !== "b" && letter !== "e";
from(dataGeneratorFactory())
.pipe(filter(shouldExclude), take(maxItemsLength), toArray())
.subscribe(console.log);
You may want to try something like this (the comments try to explain the logic).
// you start generating a stream of valued from the iterator using the from function
const observeable = from(dataGenerator).pipe(
// then take the first 10 elements of the stream and then complete upstream
take(10),
// filter out the values you do not want to keep
filter((v) => !shouldExclude(v))
);
// eventually you subscribe
observeable.subscribe({
next: (e) => console.log(e),
});
Here a stackblitz wit the full example.
Related
So I have been working on some extra credit for my classes. I am very new to programming and have already sought help for this same assignment, I started rolling through and now am absolutely lost.
I need to define the two functions groupBy() and arrayToObect() as asked in the below test.
I'm not necessarily looking for the answer but if someone could help point me in the right direction that would be awesome.
What I have deduced is as follows:
I need to be using the spread operator ...
I need to create a newObj = {}
a. and somehow push the element derived from the array into the obj
I need to take the individual values of the array and assign them as keys, with the variances as the properties of the key.
Bracket notation
I have been racking my brain for hours on this now and could really use some guidance!
describe('groupBy', function () {
const input = [4.2, 6.1, 6.3]
const result = groupBy(input, (el) => Math.floor(el))
it('returns an object', function () {
expect(result).to.be.an('object')
})
it('group array items together based on the callback return value', function () {
expect(result).to.be.eql({
4: [4.2],
6: [6.1, 6.3],
})
})
})
describe('arrayToObject', function () {
const input = ['cat', 'dog', 'bird']
const result = arrayToObject(input, (word) => word + 's')
it('returns an object', function () {
expect(result).to.be.an('object')
})
it('object has original array elements as keys and the result of the callback as values', function () {
expect(result).to.be.eql({
cat: 'cats',
dog: 'dogs',
bird: 'birds',
})
})
})
})
groupBy
Write a function called groupBy which takes an array and a callback. The function should return an object. Each return value of the callback should be a key of the object and the values should be the input element with which the callback was called.
arrayToObject
Write a function called arrayToObject which takes an array and a callback. The function should return an object. Each element of the input array should be a key of the returned object and the output from the callback with an element passed in as the corresponding value.
These questions have been answered a million times on stackoverflow. Essentially what you want to be doing here is using the common js array functions map, filter, reduce, flatten, ..., and think about how your problem can be expressed in terms of those.
A lot of real world code is transforming data like this, so it's good to be comfortable doing it.
Also realize that spread syntax copies the entire object which can be pretty inefficient. JavaScript doesn't have persistent data structures! It's usually better to just mutate — as long as your code is what "owns" the object.
const groupBy = (elts, keyfn) =>
elts.reduce((m, elt) => {
const key = keyfn(elt);
m[key] = m[key] || [];
m[key].push(elt);
return m;
}, {});
const arrayToObject = (elts, fn) =>
elts.reduce(
(obj, elt) => Object.assign(obj, { [elt]: fn(elt) }),
{},
);
I figured it out using a for loop!!
function groupBy(arr, callback) {
const newObj = {}
for (let i = 0; i < arr.length; i++) {
if (callback(arr[i])) {
const key = callback(arr[i])
newObj[key] = newObj[key] || []
newObj[key].push(arr[i])
}
}
return newObj
}
function arrayToObject(arr, callback) {
const obj = {}
for (let i = 0; i < arr.length; i++) {
if (callback(arr[i])) {
const key = callback(arr[i])
obj[arr[i]] = obj[key] || callback(arr[i])
}
}
return obj
}
I am trying to use the filter function to build a new array that provides only string values that are less than 10 in length. The function is however producing undefined values.
let move = (strings) => {
console.log('I got here');
let special = strings.length < 10;
if (special) {
console.log(strings);
return strings;
}
console.log(strings);
};
const username = [];
const validUserNames = (username) => {
username.filter(move);
};
//The code is expected to be called like this
//validUserNames(["Paul", "Barnabas", "Timothy", "Judas Iscariot"])
//Expected results: Paul, Barnabas, Timothy
In the move function, you need to return a boolean. Change it as follows :
let move = (string) => {
console.log('I got here');
let special = string.length < 10;
if (special) {
console.log(string);
return special;
}
console.log(string);
};
I have changed a few other things.
The function takes a single value in as filter goes through the array, so I changed strings to string.
Technically, it could be reduced to this.
let move = (string) => {
return string.length < 10;
};
I think there is also something else you aren't understanding. Having this code doesn't do anything.
const validUserNames = (usernames) => {
usernames.filter(move);
}
validUserNames(["Paul", "Barnabas", "Timothy", "Judas Iscariot"]);
Firstly, usernames.filter doesn't do anything to the usernames array, it returns the result of the filter.
Secondly, because you are not returning this result of the filter, validUserNames doesn't return anything.
Thirdly, unless you store the result of validUserNames in a variable, nothing will change in the state of the application.
I suggest you change the above to this.
const validUserNames = usernames.filter(move);
validUserNames will then store the array of strings with a length less than 10.
This is the correct solution. This function will take in any array of strings and will return string values that are lesser than 10 in length.
let move = (strings)=>{
console.log('I got here');
let special = strings.length < 10;
if(special){
console.log(strings);
return strings;
}
};
const username = [];
const validUserNames = (username)=>{let next = username.filter(move);
return next;
};
I have an object that I need to convert into an array. This is the object:
const dogCounts: {
maltese: 4,
poodle: 2,
labrador: 10,
corso: 0
}
And I send it to a component thru props and I have a useMemo hook to convert it into a structure like this: [ [maltese, 4], [poodle, 2], ... ]
const formatDogCounts = useMemo(() => {
const z = Object.keys(dogCounts || {})?.map(i => {
if (dogCounts[i] === 0) return; // Don't add it to map and skip it
return [i, dogCounts[i]]
})
}, [dogCounts])
When the number is zero I don't want to add it to the formatDogCounts variable. What I put above doesn't fit to eslints rules. Arrow function expected no return value.eslintconsistent-return.
Also I put that {} in the object.keys for the case when the counts haven't loaded yet is there a cleaner way to null check that?
map doesn't filter out values; doing a simple return; in map makes the corresponding entry in the result array undefined. If you want to do that, you'll need to filter first, or build the array another way (such as a simple loop).
Here's the filter approach:
const formatDogCounts = useMemo(() => {
const z = Object.keys(dogCounts || {})?
.filter(name => dogCounts[name] !== 0)
.map(name => [name, dogCounts[i]]);
}, [dogCounts]);
Note that Object.entries provides the very [name, value] pairs you want, so you could avoid map, and there's no reason for the conditional chaining operator as neither Object.keys nor Object.entries ever returns undefined or null:
const formatDogCounts = useMemo(() => {
const z = Object.entries(dogCounts || {})
.filter(([, value]) => value !== 0);
}, [dogCounts]);
Note the , prior to value in the [, value] destructuring pattern so we're grabbing the second array entry (the value), not the first (the name).
We can also avoid the calls to Object.entries and filter entirely when there is no dogCounts:
const formatDogCounts = useMemo(() => {
const z = dogCounts
? Object.entries(dogCounts).filter(([, value]) => value !== 0)
: [];
}, [dogCounts]);
In a comment you've said:
The entries solution worked really well for me! Is there a way now to return an object instead of an array with total dog counts and then an array of items? Ex: formatDogCounts: { totalDogs: 30, items: [...] }
Sure. If there will only ever be a reasonable number of dogs (fewer than hundreds of thousands), I'd just do it as a separate operation at the end:
const formatDogCounts = useMemo(() => {
const items = dogCounts
? Object.entries(dogCounts).filter(([, value]) => value !== 0)
: [];
return {
totalDogs: items.reduce((sum, [, value]) => sum + value, 0),
items,
};
}, [dogCounts]);
(A straight sum is the only ad hoc use of reduce I'll do, and even then I don't much care for it.)
Or you could make your filter callback slightly impure and count them as you go:
const formatDogCounts = useMemo(() => {
let totalDogs = 0;
const items = dogCounts
? Object.entries(dogCounts).filter(([, value]) => {
totalDogs += value;
return value !== 0;
})
: [];
return {
totalDogs,
items,
};
}, [dogCounts]);
If you want to perform a map and filter operation together, you can use flatMap, returning an empty array to skip an element.
const formatDogCounts = useMemo(() => {
const z = Object.keys(dogCounts || {})?.flatMap(i => {
if(dogCounts[i] === 0) return []; // Dont add it to map and skip it
return [[i, dogCounts[i]]];
})
}, [dogCounts])
You are trying to filter using a map which isn't possible. Map will return the same amount of values as you put in. You can do a foreach or you can do a combination of map and filter to get the expected results.
Foreach
const z = []
Object.keys(dogCounts).forEach((key) => {
if(dogCounts[key]) {
// if the value is truthy push the structure to the array.
z.push([key, dogCounts[key]]);
}
}
Map/Filter
const z = Object.keys(dogCount)
.map((key) => [key, dogCount[key]) // map to restructure object.keys
.filter(([key, value]) => value); // filter to remove falsey values (0, null, undefined)
I have a function that should filter the list of contact. Logic is that if its at least one of props passed will compare to input, it will pass. now i got only this code that compares to first+last name. I`m a bit confused, is there an effective way to do it with iteration through all props(which can be object with other props)?
handleSearch(event){
let CONTACTS = this.props.items;
let inputValue = event.target.value; //dan
var displayedUsers = CONTACTS.filter(el => {
var searchValue = el.general.firstName + el.general.lastName; //this should be changed to el.allProps
return searchValue.indexOf(inputValue) !== -1; //
});
this.setState({displayedUsers: displayedUsers}); //will return dan, danone dante etc.
}
You can use Object.values() and iterate over all values.
handleSearch(event){
const contracts = this.props.items;
const val = event.target.value;
var displayedUsers = contracts.filter(contract => Object.values(contract).find(val))
this.setState({displayedUsers});
}
If your contract object contains nested objects that should also be searched for this gets a little more tricky.
If you can use ES2017 covert the object to an array so you can use the array methods.
MDN Object.values()
Check if object has certain values:
const hasValue = Object.values(object).some(x => (x === searchValue)); // returns boolean
MDN Array.some()
MDN Array.every()
Return the found values in an array:
const hasValue = Object.values(object).filter(x => (x === searchValue)); // returns found values
MDN Filter
You can also use them in maps to search in two array's:
const newArray = array1.map(x => {
const exists = array2.some(y => y.value === x.value);
if (exists) { do something } // Or whatever you wanna do ofc.
}
You can use the Object.keys function to get an array of all the props in each element, and run another filter on that for a match. Since you mention that some of the props can be objects, you'll need to use a recursive function to search every prop in your object tree. If something matches, that array will have some entries in it so you will use that as the result for you outer filter.
Here's an example of how the handleSearch function could end up being:
handleSearch(event) {
let CONTACTS = this.props.items;
let inputValue = event.target.value;
let searchObject = (obj, searchTerm) => {
return Object.keys(obj).filter(prop => {
if (typeof obj[prop] === 'object') {
return searchObject(obj[prop], searchTerm).length > 0;
} else {
return obj[prop].indexOf(searchTerm) !== -1
}
}).length > 0;
}
let displayedUsers = CONTACTS.filter( el => searchObject(el, inputValue);
this.setState({displayedUsers});
}
This assumes you only have strings and objects in the CONTACTS variable. If you have functions, arrays, or something else, you'll have to update the if / else statements to account for those.
not single one of examples work for me, so i decided to turn my object into an array of props, and then just iterate them with for loop. I think it can be written better than i do, but it works for me so i just show you.
handleSearch(event){
let CONTACTS = this.props.items;
let inputValue = event.target.value;
let iterate = function(obj, callback) { //this is recursion iterator method with callback func
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
if (typeof obj[property] == "object") {
iterate(obj[property], callback);
} else {
callback(obj[property]);
}
}
}
}
var displayedUsers = CONTACTS.filter(el => { //here i leave object that will pass the indexOf method below.
//console.log(el);
let arr = [];
iterate(el, function (e) {
arr.push(e);
});
for (var i = 0; i < arr.length; i++) {
if(arr[i].toLowerCase().indexOf(inputValue) !== -1){
foundInfo = arr[i];
return arr[i];
}
}
});
this.setState({displayedUsers: displayedUsers});
}
For example:
$ node
> var x = {}
undefined
> x.x = x
{ x: [Circular] }
Wondering the sort of structures are they using to accomplish this, because it's not encoded directly into what I just did. It seems like they would do something like:
var graph = new Graph(object)
graph.detectCircularReferences()
And then it would get them, but not sure how that works. Hoping to learn how to implement that.
Taking into an account the ideas from the comments I came to this function. It traverses the passed object (over arrays and object) and returns an array of paths that point to the circular references.
// This function is going to return an array of paths
// that point to the cycles in the object
const getCycles = object => {
if (!object) {
return;
}
// Save traversed references here
const traversedProps = new Set();
const cycles = [];
// Recursive function to go over objects/arrays
const traverse = function (currentObj, path) {
// If we saw a node it's a cycle, no need to travers it's entries
if (traversedProps.has(currentObj)) {
cycles.push(path);
return;
}
traversedProps.add(currentObj);
// Traversing the entries
for (let key in currentObj) {
const value = currentObj[key];
// We don't want to care about the falsy values
// Only objects and arrays can produce the cycles and they are truthy
if (currentObj.hasOwnProperty(key) && value) {
if (value.constructor === Object) {
// We'd like to save path as parent[0] in case when parent obj is an array
// and parent.prop in case it's an object
let parentIsArray = currentObj.constructor === Array;
traverse(value, parentIsArray ? `${path}[${key}]` : `${path}.${key}`);
} else if (value.constructor === Array) {
for (let i = 0; i < value.length; i += 1) {
traverse(value[i], `${path}.${key}[${i}]`);
}
}
// We don't care of any other values except Arrays and objects.
}
}
}
traverse(object, 'root');
return cycles;
};
Then you can test it like this:
// Making some cycles.
const x = {};
x.cycle = x;
const objWithCycles = {
prop: {},
prop2: [1, 2, [{subArray: x}]]
};
objWithCycles.prop.self = objWithCycles;
console.log(getCycles(objWithCycles));
It produces the following output which is a list of cycles in the object:
[ 'root.prop.self', 'root.prop2[2][0].subArray.cycle' ]