Utilize Ramda.js to transform commands into a functional programming approach

I have written a code to convert an array of numbers into a new datalist using imperative style. However, I am looking to convert it to functional style using a JavaScript library like ramdajs.

Code Background: Let's say we have 5 coins in total with values of 25 dollars, 20 dollars, ... 1 dollar. We need to exchange money for dollar coins while using the least amount of coins possible.

const data = [25, 20, 10, 5, 1];
const fn = n => data.map((v) => {
    const numberOfCoin = Number.parseInt(n / v, 10);
    const result = [v, numberOfCoin];
    n %= v;
    return numberOfCoin ? result : [];
  }).filter(i => i.length > 0);

The expected output of this code should be as follows:

fn(48) => [[25, 1], [20, 1], [1, 3]]
fn(100) => [[25, 4]]

Answer №1

It seems like you're off to a good start, but there are some adjustments that can be made to enhance functionality:

  1. Opt for expressions over statements (avoid using return)
  2. Avoid modifying data directly (for example, refrain from using n %= v)

While Ramda is an option, it's not necessary for achieving the desired outcome:

const coins = value =>
  [25, 20, 10, 5, 1].reduce(([acc, val], cur) =>
    val < cur ? [acc, val] : [[...acc, [cur, Math.floor(val / cur)]], val % cur],
    [[], value]
  )[0];

console.log(coins(48));
console.log(coins(100));

If your process involves transitioning from map to filter, consider utilizing reduce. In my coins function above, the iterator returns an array containing pairs of coin values and quantities as well as the reduced value at each iteration.

Note the use of destructuring assignment in capturing the pairs array and the reduced value separately at each step.

Ramda can also facilitate this process:

const {compose, filter, last, mapAccum, flip} = R;

const mapIterator = (a, b) => [a % b, [b, Math.floor(a / b)]];
const withCoins = ([coins, number]) => number > 0;
const coins = compose(filter(withCoins), last, flip(mapAccum(mapIterator))([25, 20, 10, 5, 1]));

console.log(coins(48));
console.log(coins(100));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>

EDIT: Following Scott's observation, any of the solutions provided will yield the minimum change required.

This task turned out more complex than anticipated, and I settled on a solution that could benefit from optimization:

I've defined sets of coins:

  1. [1]
  2. [5, 1]
  3. [10, 5, 1]
  4. [20, 10, 5, 1]
  5. [25, 20, 10, 5, 1]

The approach entails computing the change produced by each set and retaining the one generating the least amount.

For instance, changing 30:

  1. 1 × 30
  2. 5 × 6
  3. 10 × 3
  4. 20 × 1, 10 × 1 (Maintain this set)
  5. 25 × 1, 5 × 1

const {compose, pipe, sum, map, last, head, mapAccum, curry, flip, applyTo, sortBy, reject, not} = R;
const numCoins = compose(sum, map(last));
const changeFn = curry((coins, num) => mapAccum((cur, coin) => [cur % coin, [coin, Math.floor(cur / coin)]], num, coins)[1]);
const change1 = changeFn([1]);
const change2 = changeFn([5, 1]);
const change3 = changeFn([10, 5, 1]);
const change4 = changeFn([20, 10, 5, 1]);
const change5 = changeFn([25, 20, 10, 5, 1]);

const change = pipe(
  applyTo,
  flip(map)([
    change1,
    change2,
    change3,
    change4,
    change5]),
  sortBy(numCoins),
  head,
  reject(compose(not, last)));

console.log(change(30));
console.log(change(40));
console.log(change(48));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>

Answer №2

This unique approach to finding the minimum number of coins in a currency exchange scenario avoids the common technique of opting for larger denominations at the potential cost of using more coins overall.

It's worth noting that this method may require significantly more computations compared to traditional solutions like those provided by customcommander.

change function will return a list of coin values, so if, for example, you input 58, it will output [25, 25, 5, 1, 1, 1]. The makeChange function then transforms this into the format [ [25, 2], [5, 1], [1, 3] ]. By adjusting the minLength function from <= to <, you can generate

[ [25, 1], [20, 1], [10, 1], [1, 3] ]
, which maintains the same number of coins but with different denominations.

If the order of the output is not important to you, you have the option to remove the sort line.

The combination of styles used here may be considered less than ideal. While it's possible to replace the Ramda pipeline version of makeChange with something similar to change, transitioning change to a Ramda pipeline is more challenging due to recursion complexities.


Credits: Shoutout to customcommander for highlighting an issue in a previous iteration of this solution.


const minLength = (as, bs) =>
  as.length <= bs.length ? as : bs

const change = ([ c, ...rest ], amount = 0) => 
  amount === 0
    ? []
    : c === 1
      ? Array(amount).fill(1) 
      : c <= amount
        ? minLength (
          [ c, ...change ([c, ...rest], amount - c)],
          change (rest, amount)
        )
        : change (rest, amount)

const makeChange = pipe(
  change,
  countBy(identity),
  toPairs,
  map(map(Number)),
  sort(descend(head)) // if desired
)

const coins =
  [ 25, 20, 10, 5, 1 ]

console.log (makeChange (coins, 40))
//=> [ [ 20, 2 ] ]

console.log (makeChange (coins, 45))
//=> [ [ 25, 1 ], [ 20, 1 ] ]

console.log (change (coins, 48))
//=> [ [ 25, 1 ], [ 20, 1 ], [ 1, 3 ] ]

console.log (makeChange (coins, 100))
//=> [ [ 25, 4 ] ]
<script src = "https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script> const { pipe, countBy, identity, toPairs, map, sort, descend, head } = R </script>

Answer №3

It's common for people to turn to functions like map, filter, and reduce when working with data, but sometimes the outcome doesn't quite fit the problem at hand.

  • map might not be suitable because it always produces a one-to-one result; if you have 4 types of coins, you will always get 4 types of change, which may not be what you need. Using filter often requires additional processing to achieve the desired output.
  • reduce can help eliminate intermediate values from combinations of map and filter, but there are cases where the desired result is reached even before analyzing every coin in the list. For example, consider the function fn(100) returning [ [25, 4] ]; further reduction would be unnecessary since the result has already been obtained.

To me, functional programming is all about convenience. If I don't have a function that fits my needs, I create one myself, ensuring that my code clearly expresses its purpose. Sometimes this involves using constructs more suited to the data being processed -

const change = (coins = [], amount = 0) =>

  loop
    ( ( acc = []
      , r = amount
      , [ c, ...rest ] = coins
      ) =>

        r === 0
          ? acc

      : c <= r
          ? recur
              ( [ ...acc, [ c, div (r, c) ] ]
              , mod (r, c)
              , rest
              )

      : recur
          ( acc
          , r
          , rest
          )

    )

Unlike traditional methods such as map, filter, and reduce, our solution won't continue iterations once the result is determined. Here's how you can use it -

const coins =
  [ 25, 20, 10, 5, 1 ]

console.log (change (coins, 48))
// [ [ 25, 1 ], [ 20, 1 ], [ 1, 3 ] ]

console.log (change (coins, 100))
// [ [ 25, 4 ] ]

You can check the results in your browser below -

const div = (x, y) =>
  Math .round (x / y)

const mod = (x, y) =>
  x % y

const recur = (...values) =>
  ({ recur, values })

const loop = f =>
{ let acc = f ()
  while (acc && acc.recur === recur)
    acc = f (...acc.values)
  return acc
}

const change = (coins = [], amount = 0) =>
  loop
    ( ( acc = []
      , r = amount
      , [ c, ...rest ] = coins
      ) =>
        r === 0
          ? acc
      : c <= r
          ? recur
              ( [ ...acc, [ c, div (r, c) ] ]
              , mod (r, c)
              , rest
              )
      : recur
          ( acc
          , r
          , rest
          )

    )

const coins =
  [ 25, 20, 10, 5, 1 ]

console.log (change (coins, 48))
// [ [ 25, 1 ], [ 20, 1 ], [ 1, 3 ] ]

console.log (change (coins, 100))
// [ [ 25, 4 ] ]

Ramda users may opt for R.until, yet readability takes a hit due to its purely functional nature. Personally, I find the flexibility of loop and recur more appealing -

const change = (coins = [], amount = 0) =>
  R.until
    ( ([ acc, r, coins ]) => r === 0
    , ([ acc, r, [ c, ...rest ] ]) =>
        c <= r
          ? [ [ ...acc
              , [ c, Math.floor (R.divide (r, c)) ]
              ]
            ,  R.modulo (r, c)
            , rest
            ]
          : [ acc
            , r
            , rest
            ]
    , [ [], amount, coins ]
    )
    [ 0 ]

Another approach is to implement it as a recursive function -

const div = (x, y) =>
  Math .round (x / y)

const mod = (x, y) =>
  x % y

const change = ([ c, ...rest ], amount = 0) =>
  amount === 0
    ? []
: c <= amount
    ? [ [ c, div (amount, c) ]
      , ...change (rest, mod (amount, c))
      ]
: change (rest, amount)

const coins =
  [ 25, 20, 10, 5, 1 ]

console.log (change (coins, 48))
// [ [ 25, 1 ], [ 20, 1 ], [ 1, 3 ] ]

console.log (change (coins, 100))
// [ [ 25, 4 ] ]

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

``There seems to be an issue with the functionality of Passport.js' multiple login system

I'm encountering a strange issue with my login system. Everything seems to be working fine, including local login, Google login, and Facebook login. However, the problem arises when I attempt to register with Google after already registering with Face ...

Deciphering a JSON array by value or key

I have a JSON array that I need to parse in order to display the available locations neatly in a list format. However, I am struggling with where to start. The data should be converted to HTML based on the selected date. In addition, a side bar needs to s ...

Error: It is not possible to access the 'map' property of an undefined value 0.1

I'm currently experimenting with React.js and I encountered an error that's giving me trouble. I can't seem to pinpoint the root cause of this issue. Shout out to everyone helping out with errors: TypeError: Cannot read property 'map ...

Issue: The content of the text does not align with the HTML generated by the server

I'm struggling with an algorithm in next.js and encountering hydration errors. Here is the code I am using: import numbers from "../../functions/numberGenerators.js" export default function test() { ...

What is the best way to showcase a unique quote right when the webpage is opened?

I'm currently developing a random quote application. I want the quote to be displayed when the page loads instead of waiting for the user to click a button. I've tried invoking a function, but it's not working as expected. Any advice would b ...

I need assistance in locating an error; the error message states that $ is not defined and an object is expected

Issue with $ not being defined, object expected.. I am trying to verify if all sets of radio buttons are checked when a button is clicked! Please help. <script type="text/javascript> $(document).on('click', 'form', function () { ...

React's setState is not reflecting the changes made to the reduced array

I am currently working on a custom component that consists of two select lists with buttons to move options from the available list to the selected list. The issue I am facing is that even though the elements are successfully added to the target list, they ...

"Enhance the visual appeal of your Vue.js application by incorporating a stylish background image

Currently, I am utilizing Vue.js in a component where I need to set a background-image and wrap all content within it. My progress so far is outlined below: <script> export default { name: "AppHero", data(){ return{ image: { bac ...

Problem with laravel ajax request

I've spent the entire weekend trying to figure this out, but I can't seem to make it work. It works fine with the 'get' method, but not with 'post'. I'm using Laravel 4 and jQuery. Here's how my JavaScript looks: $ ...

Error in code - message gets sent immediately instead of waiting for timeout to occur

There seems to be an issue with the code where the message is sent instantly instead of waiting for the specified timeout duration. Based on the code, it should wait for the time mentioned in the message. I'm puzzled as to why it's not functioni ...

Error: The function you are trying to reference is undefined

I am facing an issue where I am receiving a "ReferenceError: doNotification is not defined" message when attempting to display a pop-up notification upon successful promise. Oddly enough, triggering doNotification on button click within my HTML works wit ...

In which location can one find the compiled TypeScript files within an Angular 2 project?

As a newcomer to learning Angular 2, I've come across tutorials that mention all compiled files should go into the dist folder. These compiled files refer to typescript files transpiled into JavaScript. However, upon creating my project using Angular ...

What could be causing all of my Bootstrap accordion panels to close at once instead of just the one I selected?

When implementing Bootstrap accordions in my projects, I encountered an issue where closing one accordion would cause the others to also close when reopened. To address this problem, I utilized the collapse and show classes in Bootstrap to enable users to ...

Specify the controller to be used dynamically in an Angular directive

I want to be able to specify the controller that a directive uses by adding an attribute to the element - in other words, dynamically: HTML <div data-mydirective data-ctrl="DynamicController"></div> Angular angular.module('app', [ ...

Clicking a link will trigger a page refresh and the loading of a new div

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script> $(window).load(function () { var divs = $( #add_new, #details"); $("li a").click(function () { ...

Can you include the dollar symbol in the currency format?

I currently have this code that formats numbers into currency format, however I would like to include the dollar sign ($) before the numbers. document.getElementById("numbers").onblur = function (){ this.value = parseFloat(this.value.r ...

Looking for a straightforward way to incorporate a "Read more" and "Show Less" functionality into a Wordpress site using Javascript?

As a last resort, I am seeking a quick and simple fix. I have been experimenting with Javascript to implement a 'Read more...' & '... Read Less' feature on six different sections of a single page. The goal is to display only the fi ...

Integrating a Find Pano functionality into a Kolor Panotour

I used a program called Kolor to create a panorama. Now, I am attempting to integrate the "find pano" feature, which involves searching through the panoramic images for display purposes. I have come across an HTML file that contains the search functionalit ...

Is it necessary to specify the JavaScript version for Firefox? (UPDATE: How can I enable `let` in FF version 44 and below)

Our recent deployment of JavaScript includes the use of the let statement. This feature is not supported in Firefox browsers prior to version 44, unless JavaScript1.7 or JavaScript1.8 is explicitly declared. I am concerned about the potential risks of usi ...

Combining all CSS files into one and consolidating all JavaScript files into a single unified file

Whenever I need to add a new CSS or JS file, I always place it in the header section like this Add CSS files <link rel="stylesheet" href="<?php echo URL; ?>public/css/header.css" /> <link rel="stylesheet" href="<?php echo URL; ?> ...