2

I am quite stuck in this scenario. I have a list of atoms representing bank transactions.

(#<Ref@29a71299: {:desc "DESC1", :amount 150, :date #<LocalDate 2017-01-10>}>)
(#<Ref@5a4ebf03: {:desc "DESC2", :amount 250, :date #<LocalDate 2017-01-10>}>)
(#<Ref@5a4ebf03: {:desc "DESC3", :amount -250, :date #<LocalDate 2017-01-11>}>)
(#<Ref@5a4ebf03: {:desc "DESC4", :amount 50, :date #<LocalDate 2017-01-12>}>)

I need calculate the balance account in the end of the day, so I should grab all transactions separated per day to know the balance in the end of the day.

Someone did it before ? What is the best way to filter dates and do this math ? I am still noob/student in clojure.

obs. I am using this library to work with date Jodatime

10
  • Just loop over the list, and break every time the date changes. Commented Jan 9, 2017 at 22:09
  • You may also wish to use this clojure lib: github.com/clj-time/clj-time Commented Jan 9, 2017 at 22:10
  • You mentioned you are using this problem to learn, how much of it do you want to work out yourself? How detailed an answer would you like? Commented Jan 9, 2017 at 22:23
  • @Carcigenicate Thanks I will try. Commented Jan 9, 2017 at 22:38
  • @AlanThompson Thanks I will use. Commented Jan 9, 2017 at 22:39

3 Answers 3

1

A great way to approach problems in Clojure is to think:

  1. How can I break this problem down (this is usually the hard part)
  2. How can I solve each problem alone
  3. How do I compose these solutions (this is usually the easy part)

Applying this to your problem I see these problems:

  • segmenting a list of maps by a property of one of the keys

    (partition-by ... something ...)

  • summing all the values of one of the keys in each of a sequence of maps

    (map (reduce ...))

  • making an output format with the data and the sum from each segment

    (map ... something)

And the composing part is likely just nesting each of these as nested function calls. Nested function calls can be written using the thread-last maco and will look something like this:

(->> data
    (... problem one solution ...)
    (problem two solution)
    (some output formatting for problem three))
Sign up to request clarification or add additional context in comments.

2 Comments

+1, but I'm not sure I agree with the "usually" diagnoses in the beginning. If you put off worrying about how to compose solutions, then you can often quite easily come up with a breaking-problems-down solution that's very easy, until you realize it's impossible to compose.
Really appreciate for your help Arthur
0

You may want to break it down this way:

(defn per-day-balance [txns]
  (->> txns
       (partition-by :date)
       (map (fn [[{date :date} :as txns]]
              {:date date :balance (reduce + (map :amt txns))}))))

Find the daily balance assuming everyday starts with 0. Sample run:

(def txns [{:date 1 :amt 10}
           {:date 1 :amt 3}
           {:date 2 :amt 9}
           {:date 2 :amt -11}
           {:date 3 :amt 13}])

user> (per-day-balance txns)
=> ({:date 1, :balance 13} {:date 2, :balance -2} {:date 3, :balance 13})

Now add a reduction function to get the running total. The reduction function simply 'update' the cumulative balance:

(defn running-balance [bals]
  (let [[day-1 & others] bals]
    (reductions
     (fn [{running :balance} e] (update e :balance + running))
     day-1
     others)))

Sample run:

user> (->> txns
           per-day-balance
           running-balance)
=> ({:date 1, :balance 13} {:date 2, :balance 11} {:date 3, :balance 24})

Note: You can use whatever data type for :date field. Secondly, I deliberately avoid atom to make the functions pure.

1 Comment

Really appreciate for your help, I am almost finishing all code. Could you evaluate my code ? I think its important to know if I can improve ...
0

This ended up getting more complicated than I thought it would. I looked at partition-by, and you should almost definitely use that instead. It's perfectly suited for this problem. This is just an example of how it could be done with a loop:

(defn split-dates [rows]
  (loop [[row & rest-rows] rows ; Split on the head
         last-date nil
         acc [[]]]
    (if row
      (let [current-date (last row)]
        (recur rest-rows current-date
          ; If the day is the same as the previous row
          (if (or (nil? last-date) (= current-date last-date))
            ; Update the current day list with the new row
            (update acc (dec (count acc))
                    #(conj % row))
            ; Else, create a new list of rows for the new date
            (conj acc [row]))))
      acc)))

(clojure.pprint/pprint
  (split-dates
    [[0 1 "16.12.25"]
     [2 3 "16.12.25"]
     [4 5 "16.12.26"]
     [6 7 "16.12.26"]
     [8 9 "16.12.26"]
     [1 9 "16.12.27"]]))

[[[0 1 "16.12.25"] [2 3 "16.12.25"]]
 [[4 5 "16.12.26"] [6 7 "16.12.26"] [8 9 "16.12.26"]]
 [[1 9 "16.12.27"]]]

Notes:

  • This assumes the dates are in the last column, and that the rows are sorted by date.

  • It returns [[]] when given given an empty list. This may or may not be what you want.

1 Comment

Really appreciate for your help @Carcigenicate

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.