path combined with filter - with Ramda - javascript

I am querying data from an API which has nested object properties that I need to access.
const players = teamsData && teamsData.team && teamsData.team.players;
I am using path to get this data.
const players = path(['team', 'players'], teamsData);
This works but when I combine with filter I get an error.
Ideally I want to use pipe and combine this with Ramda's filter method.
My code looks like this:
const injuredPlayers = pipe(
path(['team', 'players'], teamsData),
filter(player => player.isInjured)
);

it does look like the players variable equals
const players = path(['team', 'players'], teamsData);
at least from the code you wrote
will continue here if it's not a problem
at first I would prefer to use pathOr instead is going to look like this
const pathTeamPlayers = R.pathOr([], ['team', 'players']);
const isInjured = player => player.injured
const filterInjured = R.filter(isInjured)
const teamDataToInjuredPlayers = R.pipe(
pathTeamPlayers,
filterInjured,
);
/* result */
teamDataToInjuredPlayers(teamData)

If you just want the list of injured players, you can write
const injuredPlayers =
filter (player => player.isInjured) (path (['team', 'players'], teamsData)
If you want a function that will retrieve that information from teamsData, then you can write
const getInjuredPlayers = pipe (
path('[team', 'players']),
filter (prop ('isInjured'))
)
(or use pathOr with [] to increase reliability) and call it with
const injuredPlayers = getInjuredPlayers (teamData)
Your code is combining these two distinct styles.

Related

How to replace the object array using simple filter and spread operators

I need a suggestion for the below code. The objective here is to remove the specific array if the mentioned menuKey is present.
Note: sdb.menu = 'account-links' (Declared in another file);
const { menu = [] } = remainingConfig[sdb.menu]?.params || {};
const keysToRemove = ['sidebarReferAFriend'];
const filteredMenu = menu.filter(({ menuKey }: IMenuLink) => !keysToRemove.includes(menuKey));
How can I assign the filteredMenu back to remainingConfig object?
I tried with some spread operator options and it's not giving the existing same structure. So please provide some help here when you have some time.
The object structure will be like attached image.
If you can directly assign to the remainingConfig[sdb.menu].params.menu property, then since presumably you don't need to create the array if it's not there, only do the work if the array and everything leading up to it exists, then just assign back to menu:
const menuConfigParams = remainingConfig[sdb.menu]?.params;
const menu = menuConfigParams?.menu;
if (menu) {
const keysToRemove = ['sidebarReferAFriend'];
menuConfigParams.menu = menu.filter(({ menuKey }/*: IMenuLink*/) => !keysToRemove.includes(menuKey));
}
If the remainingConfig structure is deeply immutable, then we have to create a new object to replace it at every level of the nesting:
const menuConfig = remainingConfig[sdb.menu];
const menuConfigParams = menuConfig?.params;
let menu = menuConfigParams?.menu;
if (menu) {
const keysToRemove = ['sidebarReferAFriend'];
menu = menu.filter(({ menuKey }/*: IMenuLink*/) => !keysToRemove.includes(menuKey));
const newConfig = {
...remainingConfig,
[sdb.menu]: {
...menuConfig,
params: {
...menuConfig.params,
menu,
}
}
};
// ...then use whatever mechanism is in your environment to replace `remainingConfig`
// with `newConfig`...
}
Notice how at each level we're making a shallow copy of the structure via spread syntax.

Fetching JSON objects based on key values

This code does a great job of fetching and rendering everything within the JSON array, but what if I am interested in only listing the objects with a particular key-value (like gender)? Would that happen during the fetch or the render?
const URL = "https://ghibliapi.herokuapp.com/people";
const main = document.getElementById("main");
main.innerHTML = "<p>Loading...";
fetch(URL).then((response) => response.json()).then((people) => main.innerHTML = getListOfNames(people));
const getListOfNames = (people) => {
const names = people.map((person) => `<li>${person.name} - ${person.gender} </li>`).join("\n");
return `<ul>${names}</ul>`;
};
The ideal case would be using GraphQL so you only fetch the data fields you need based on your criteria, in this case, there is no difference between changing the getListOfNames function for just outputting a person when its person.gender matches your criteria or simply passing to it a filtered array of people after fetching them all
You would have to configure the api endpoint to accept filters if you'd like to return filtered results. Otherwise, you'd filter on render.
With underscore you'd do _.where(response, {gender: 'male'})

Firebase real time database compound query combining text and date

I'm trying to make a compound query (combining two children). What I want to do is fetch objects that are older than 5 minutes AND also are of a type.
So I've combined the children type and date into a new child called type_date and I do my query like this:
const now = Date.now();
const fiveMinutesMs = 5*60*1000;
const fiveMinutesAgo = now - fiveMinutesMs;
const fiveMinutesAgoQuery = "type_" + fiveMinutesAgo;
ref.orderByChild('type_date').endAt(fiveMinutesAgoQuery).once('value');
Only thing is that it doesn't work. It's giving me results of objects which doesn't even have the type_date child.
Try startAt instead of endAt. I hope it works.
EDIT: Oops I was too quick.... it works only if I'm only filtering on the date string but not together with the prepended type which I want...
So this works:
const fiveMinutesAgoIso = new Date(fiveMinutesAgo).toISOString();
const fiveMinutesAgoQuery = String(fiveMinutesAgoIso);
But not:
const fiveMinutesAgoIso = new Date(fiveMinutesAgo).toISOString();
const fiveMinutesAgoQuery = "type_" + fiveMinutesAgoIso;
Just make a Compound queries as doc says :
const fiveMinutesAgoIso = new Date(fiveMinutesAgo).toISOString();
ref.where("type", "==", TYPE_YOU_WANT).orderByChild('type').endAt(fiveMinutesAgoQuery).once('value');

Refactor common combinators out of bacon.js streams

Let's say I have two bacon.js streams:
const stream1 = $('#input1').asEventStream('input')
.map(e => e.target.value)
.filter(inp => inp.length > 3)
.log();
const stream2 = $('#input2').asEventStream('input')
.map(e => e.target.value)
.filter(inp => inp.length > 3)
.log();
I would like to extract the common step of filtering of by the input length into a variable and apply it to both of the streams afterwards, fantasy code:
const my_filter = Bacon.map(e => e.target.value).filter(inp => inp.length > 3)
const stream1 = $('#input1').asEventStream('input')
.apply(my_filter)
.log();
const stream2 = $('#input2').asEventStream('input')
.apply(my_filter)
.log();
Is there an idiomatic way to do that?
EDIT1: To clarify, my_filter is just an example. I would like to be able to refactor an arbitrary chain of combinators and apply them to multiple streams.
EDIT2: As Bless Yahu noticed, I need an additional map to get the value out of events. This demonstrates my problem even better.
To me this looks like a basic software refactoring problem.
Generally, if you want to apply an arbitrary transformation to a stream, you'll probably want to write a function that takes a stream as an argument and returns the modified stream as a result.
In your particular case I'd remove duplication by extracting steps into functions.
const getValue = input => input.target.value
const my_filter = stream => stream.filter(text => text.length > 3)
const filteredInputValue = ($input) => {
my_filter($input.asEventStream('input').map(getValue))
const stream1 = filteredInputValue($('#input1')).log()
const stream2 = filteredInputValue($('#input2')).log()
In this example, you can easily modify my_filter to apply any other transformations as well, like throttling and text trimming.
Add a mapping function to get the value out of the event (I think there is a Bacon convience function as well), change you my_filter to a function, and call it with filter in your stream:
const getValue = inp=>inp.target.value;
const my_filter = inp => inp.length > 3
const stream1 = $('#input1').asEventStream('input')
.map(getValue)
.filter(my_filter)
.log();
const stream2 = $('#input2').asEventStream('input')
.map(getValue)
.filter(my_filter)
.log();
example of this is here: http://jsbin.com/vahejuv/edit?html,js,console,output

Filter/Reject Array of strings against multiple values using underscore

I'd like to _.filter or _.reject the cities array using the filters array using underscore.
var cities = ['USA/Aberdeen', 'USA/Abilene', 'USA/Akron', 'USA/Albany', 'USA/Albuquerque', 'China/Guangzhou', 'China/Fuzhou', 'China/Beijing', 'China/Baotou', 'China/Hohhot' ... ]
var filters = ['Akron', 'Albuquerque', 'Fuzhou', 'Baotou'];
My progress so far:
var filterList;
if (reject) {
filterList = angular.copy(cities);
_.each(filters, (filter) => {
filterList = _.reject(filterList, (city) => city.indexOf(filter) !== -1);
});
} else {
filterList = [];
_.each(filters, (filter) => {
filterList.push(_.filter(cities, (city) => city.indexOf(filter) !== -1));
});
}
filterList = _.flatten(filterList);
return filterList;
I'd like to DRY this up and use a more functional approach to achieve this if possible?
A somewhat more functional version using Underscore might look like this:
const cities = ['USA/Aberdeen', 'USA/Abilene', 'USA/Akron', 'USA/Albany',
'USA/Albuquerque', 'China/Guangzhou', 'China/Fuzhou',
'China/Beijing', 'China/Baotou', 'China/Hohhot']
const filters = ['Akron', 'Albuquerque', 'Fuzhou', 'Baotou'];
var inList = names => value => _.any(names, name => value.indexOf(name) > -1);
_.filter(cities, inList(filters));
//=> ["USA/Akron", "USA/Albuquerque", "China/Fuzhou", "China/Baotou"]
_.reject(cities, inList(filters));
//=> ["USA/Aberdeen", "USA/Abilene", "USA/Albany",
// "China/Guangzhou", "China/Beijing", "China/Hohhot"]
I'm using vanilla JavaScript here (some() and filter()) but I hope you get the idea:
const isValidCity = city => filters.some(filter => city.indexOf(filter) > -1)
const filteredCities = cities.filter(isValidCity)
Please note that this is a loop over a loop. So the time complexity is O(n * m) here.
In your example all city keys share the same pattern: country + / + city. Your filters are all an exact match to the city part of these names.
If this is a certainty in your data (which it probably isn't...), you could reduce the number of loops your code makes by creating a Map or object that stores each city per filter entry:
Create an object with an entry for each city name
Make the key the part that you want the filter to match
Make the value the original name
Loop through the filters and return the name at each key.
This approach always requires one loop through the data and one loop through the filters. For small array sizes, you won't notice a performance difference. When one of the arrays has length 1, you'll also not notice any differences.
Again, note that this only works if there's a constant relation between your filters and cities.
var cities = ['USA/Aberdeen', 'USA/Abilene', 'USA/Akron', 'USA/Albany', 'USA/Albuquerque', 'China/Guangzhou', 'China/Fuzhou', 'China/Beijing', 'China/Baotou', 'China/Hohhot' ]
var filters = ['Akron', 'Albuquerque', 'Fuzhou', 'Baotou'];
const makeMap = (arr, getKey) => arr.reduce(
(map, x) => Object.assign(map, {
[getKey(x)]: x
}), {}
);
const getProp = obj => k => obj[k];
const getKeys = (obj, keys) => keys.map(getProp(obj));
// Takes the part after the "/"
const cityKey = c => c.match(/\/(.*)/)[1];
const cityMap = makeMap(cities, cityKey);
const results = getKeys(cityMap, filters);
console.log(results);
Since you seem to be using AngularJS, you could utilize the built-in filter functionality. Assuming both the cities and filters array exist on your controller and you're displaying the cities array using ng-repeat, you could have something like this on your controller:
function cityFilter(city) {
var cityName = city.split('/')[1];
if (reject) {
return filters.indexOf(cityName) === -1;
} else {
return filters.indexOf(cityName) > -1;
}
}
And then in your template, you'd do something like this:
<div ng-repeat="city in cities | filter : cityFilter"></div>
Of course you'd have to modify your syntax a bit depending on your code style (for example, whether you use $scope or controllerAs).

Categories