Perform grouping and reducing operations on an array of objects using a pointfree syntax

Recently delved into the world of Ramda and am on a quest to discover a pointfree method for reducing an array of objects.

Behold, the array of objects :

const someObj = [
    {
        name: 'A',
        city: 1,
        other: {
            playtime: 30
        }
    },
    {
        name: 'B',
        city: 2,
        other: {
            playtime: 20
        }
    },
    {
        name: 'c',
        city: 1,
        other: {
            playtime: 20
        }
    }
];

I aspire to achieve object reduction with the wondrous powers of Ramda, all while maintaining the pointfree style like so:

{
    '1': {
        count: 2,
        avg_play_time: 20 + 30 / count
    },
    '2': {
        count: 1,
        avg_play_time: 20 / count
    }
}

The conventional method involving array reduction is within my grasp, but the enigma lies in crafting the same utilizing Ramda's unmistakable pointfree elegance. Any guidance on this matter would be greatly valued.

Answer №1

To tackle this issue, a potential solution involves the following approach:

// Utilizing an optic to extricate the nested playtime value
// Paired with a `lift` operation enabling its application across a collection
// Essentially A -> B => A[] -> B[]
const playtimes = R.lift(R.path(['other', 'playtime']))

R.pipe(
  // Grouping the given array by the city attribute
  R.groupBy(R.prop('city')),
  // Providing a body specification that calculates each property based on the 
  // specified function value.
  R.map(R.applySpec({
    count: R.length,
    average: R.pipe(playtimes, R.mean)
  }))
)(someObj)

Answer №2

Ramda offers a unique function known as R.reduceBy, bridging the gap between reduce and groupBy. This function allows you to aggregate values with matching keys.

You can define a custom data type like the following that calculates averages of values:

const Avg = (count, val) => ({ count, val })
Avg.of = val => Avg(1, val)
Avg.concat = (a, b) => Avg(a.count + b.count, a.val + b.val)
Avg.getAverage = ({ count, val }) => val / count
Avg.empty = Avg(0, 0)

Next, utilize R.reduceBy to combine these components:

const avgCities = R.reduceBy(
  (avg, a) => Avg.concat(avg, Avg.of(a.other.playtime)),
  Avg.empty,
  x => x.city
)

Retrieve the average values from Avg and structure them into final objects:

const buildAvg = R.applySpec({
  count: x => x.count,
  avg_play_time: Avg.getAverage
})

Lastly, connect the two steps together by mapping buildAvg over the object values:

const fn = R.pipe(avgCities, R.map(buildAvg))
fn(someObj)

Answer №3

If you're looking for a solution, this code snippet might be helpful to you!

const statistics = R.pipe(
  R.groupBy(R.prop('city')),
  R.map(
    R.applySpec({
      total: R.length,
      average_play_time: R.pipe(
        R.map(R.path(['other', 'playtime'])),
        R.mean,
      ),
    }),
  ),  
);

const dataset = [
  { name: 'A', city: 1, other: { playtime: 30 } },
  { name: 'B', city: 2, other: { playtime: 20 } },
  { name: 'c', city: 1, other: { playtime: 20 } },
];

console.log('result', statistics(dataset));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>

Answer №4

One way to approach this is by utilizing the reduceBy function along with applying an applySpec method on each property of the resulting object:

The concept here is to convert someObj into the following structure using getPlaytimeByCity:

{ 1: [30, 20],
  2: [20]}

Afterwards, you can apply the stats function to each property of that object:

stats({ 1: [30, 20], 2: [20]});
// { 1: {count: 2, avg_play_time: 25}, 
//   2: {count: 1, avg_play_time: 20}}

const someObj = [
    { name: 'A',
      city: 1,
      other: { playtime: 30 }},
    { name: 'B',
      city: 2,
      other: { playtime: 20 }},
    { name: 'c',
      city: 1,
      other: { playtime: 20 }}
];

const city = prop('city');
const playtime = path(['other', 'playtime']);
const stats = applySpec({count: length, avg_play_time: mean});
const collectPlaytime = useWith(flip(append), [identity, playtime]);
const getPlaytimeByCity = reduceBy(collectPlaytime, [], city);

console.log(

  map(stats, getPlaytimeByCity(someObj))
  
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {prop, path, useWith, flip, append, identity, applySpec, length, mean, reduceBy, map} = R;</script>

Answer №5

I appreciate all the insightful responses shared thus far. As expected, I'd like to contribute my own perspective as well. 😉

Here's a unique approach that leverages the reduceBy function to maintain a continuous record of count and average. While this method may not be suitable for calculating the median or other statistics, it effectively computes the new count and average based on the given data. By doing so, we iterate through the data just once, albeit with some arithmetic computations in each iteration.

const transform = reduceBy(
  ({count, avg_play_time}, {other: {playtime}}) => ({
    count: count + 1,
    avg_play_time: (avg_play_time * count + playtime) / (count + 1)
  }),
  {count: 0, avg_play_time: 0},
  prop('city')
)
const someObj = [{city: 1, name: "A", other: {playtime: 30}}, {city: 2, name: "B", other: {playtime: 20}}, {city: 1, name: "c", other: {playtime: 20}}]

console.log(transform(someObj))
<script src="https://bundle.run/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="5d3c373b3b253408152319380320">[email protected]</a>"></script>
<script>
const {reduceBy, prop} = ramda
</script>

Although this implementation is not entirely point-free, I believe that utilizing a point-free style should only be done when appropriate. Pursuing it purely for the sake of it can be counterproductive.

It's worth noting that Scott Christopher's solution could be easily adjusted to incorporate this type of calculation as well.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Modifying the type of a DOM element based on the value of an AngularJS

My main goal is to dynamically change the type of a DOM element based on a scope variable. Imagine I have a scope variable like this: $scope.post = { title: 'Post title goes here!', slug: 'post-title-goes-here', category: ...

Utilizing sinon on unit tests for MongoDB in Node.js

My latest project involved creating a model function for mongodb using node-mongodb-native: 'use strict'; const mongo = require('mongodb'); class BlacklistModel { constructor(db, tenant_id, logger) { this._db = db; ...

The functionality of the function from the controller does not seem to be effective within the Angular directive

Hey everyone! I created a form using dynamic data and in the future, I will need to compile this form, fill it with data, and submit it. Here is how I built the form: <div> <form name="pageForm"> <div> <div class="form-group" ng-repe ...

Error message '[object Error]' is being returned by jQuery ajax

I'm currently facing a challenge with my HTML page that calls an API. Every time I attempt to run it, I encounter an [object Error] message. Below is the code snippet in question: jQuery.support.cors = true; jQuery.ajax({ type: "POST", url: ...

Encountered an uncaughtException in Node.js and mongoDB: User model cannot be overwritten once compiled

Currently, I am utilizing this import statement const User = require("./Usermodel")' However, I would like to modify it to const User = require("./UserModel") Despite my efforts to align the spelling of the import with my other i ...

determining the overall page displacement

I'm working with this code and I need help using the IF condition to check if the total page offset is greater-than 75%. How can I implement that here? function getLocalCoords(elem, ev) { var ox = 0, oy = 0; var first; var pageX, pageY; ...

Struggling with establishing recognition of a factory within an Angular controller

I am currently facing an issue while trying to transfer data from one controller to another using a factory in Angular Seed. My problem lies in the fact that my factory is not being recognized in the project. Below is my snippet from app.js where I declare ...

I'm all set to launch my express js application! What are the major security concerns that I need to keep in

As a beginner in deploying express applications, I find myself lacking in knowledge about the essential security measures that need to be taken before launching a web application. Here are some key points regarding my website: 1) It is a simple website ...

Tips for preventing the repetition of values when dynamically adding multiple values to a text box using JavaScript

I am working on a function that adds multiple unique values to a hidden field. It is currently functioning correctly, but I want to ensure that the values added to the hidden field are unique and not duplicated if entered more than once. The select box wh ...

shallow rendering does not recognize this.props as a function

I'm currently facing an issue while trying to test my (legacy) component using jest/enzyme. Here is a snippet of the component: export default class MyComponent extends Component { constructor( props ) { super( props ); this.handl ...

Automatically populate login credentials on a webpage using Javascript scripting

Can anyone provide insights on why the input credentials on a webpage cannot be autofilled with this code? Is there a solution to make it functional? Trying to automate login process using JavaScript: function Test() { var name = document.getElementBy ...

What is the method for configuring Vue to utilize a value other than the value property in form fields?

I am facing a unique challenge. Consider this: <select name="screw[type]" v-model="form.screw.type"> <option value="My value" ><?php _e('My value', 'fiam'); ?></option> //[...] Now, in another part of my ...

Tips on how to hold off the display of webpage content until all elements, including scripts and styles, have fully loaded

I have a variety of div elements set up like this - <div id='1' class='divs_nav'>My Dynamic Content</div> <div id='2' class='divs_nav'>My Dynamic Content</div> ... <div id='149' c ...

Tsyringe - Utilizing Dependency Injection with Multiple Constructors

Hey there, how's everyone doing today? I'm venturing into something new and different, stepping slightly away from the usual concept but aiming to accomplish my goal in a more refined manner. Currently, I am utilizing a repository pattern and l ...

Experiencing difficulties installing the MEAN stack

I've been attempting to set up the MEAN stack by following a tutorial on Bossable website. I'm using Webstorm and MongoDB for this installation. Unfortunately, I'm facing some issues and encountering errors. Every time I try to connect to l ...

I am still receiving an empty dropdown value despite implementing ng-selected

I am having issues with using ng-selected to retrieve the selected value from a dropdown. Instead of displaying the selected value, it appears blank. Here is the code snippet I have tried: <div> <select id="user_org" ng-model="selectedorg.all ...

Transferring checkbox status from HTML (JavaScript) to PHP

I am a beginner in JavaScript and I am trying to get the value from a variable in JS, send it via post (or ajax) to a PHP file, and display the text. However, I have attempted various methods but always encounter an undefined index error in PHP. Below is ...

Ways to Insert Text and Values into an Array

{{ "user": "randomuser", "message": "require assistance" }, { "user": "automated assistant", "message": "do you need any help?" }, { "user": "randomuser", "message": "another inquiry" } I am seeking to extract additional paragraphs ...

The markers on Google Maps are currently displaying in the wrong position, despite the latitude and longitude being correct

Utilizing the Google Maps API, I have implemented a system to dynamically add map markers tracking 2 of our company's vehicles. The website is developed in asp.net c# mvc with bootstrap 4.3.1. An ajax request retrieves the latest marker location from ...

Is it truly possible to return a reactive variable that updates its value asynchronously?

While reviewing the code of a frontend project developed in Vue3, I came across a unique construction that I have not encountered before. This has led to some confusion as I try to grasp how it operates. The concept involves assigning the result of an asyn ...