Chapter 11. Storing Structured Data

Étude 11-1: Using ETS

This étude will set up a small database that keeps track of phone calls.

Part One

Start by creating a record that defines these fields:

  • Phone number
  • Starting date (month, day, and year)
  • Starting time (hours, minutes, and seconds)
  • End date (month, day, and year)
  • End time (hours, minutes, and seconds)

You may name the record whatever you wish, and you may use any field names you wish. For the default values for dates and times, use the strings "1900-01-01" and "00:00:00".

In a module named PhoneETS, read a data file of phone call summaries and create an ETS table for the calls. The function that does this will be named setup/1, and its argument will be the name of the file containing the data.

Copy the following text into a file named call_data.csv and save the file in the same directory where you are going to write your module.

650-555-3326,2013-03-10,09:01:47,2013-03-10,09:05:11
415-555-7871,2013-03-10,09:02:20,2013-03-10,09:05:09
729-555-8855,2013-03-10,09:00:55,2013-03-10,09:02:18
729-555-8855,2013-03-10,09:02:57,2013-03-10,09:03:56
213-555-0172,2013-03-10,09:00:59,2013-03-10,09:03:49
946-555-9760,2013-03-10,09:01:20,2013-03-10,09:03:10
301-555-0433,2013-03-10,09:01:44,2013-03-10,09:04:06
301-555-0433,2013-03-10,09:05:17,2013-03-10,09:07:53
301-555-0433,2013-03-10,09:10:05,2013-03-10,09:13:14
729-555-8855,2013-03-10,09:04:40,2013-03-10,09:07:29
213-555-0172,2013-03-10,09:04:26,2013-03-10,09:06:00
213-555-0172,2013-03-10,09:06:59,2013-03-10,09:10:35
946-555-9760,2013-03-10,09:03:36,2013-03-10,09:04:23
838-555-1099,2013-03-10,09:00:43,2013-03-10,09:02:44
650-555-3326,2013-03-10,09:05:48,2013-03-10,09:09:08
838-555-1099,2013-03-10,09:03:43,2013-03-10,09:06:26
838-555-1099,2013-03-10,09:07:54,2013-03-10,09:10:10
301-555-0433,2013-03-10,09:14:07,2013-03-10,09:15:08
415-555-7871,2013-03-10,09:06:15,2013-03-10,09:09:32
650-555-3326,2013-03-10,09:10:12,2013-03-10,09:13:09

Read the file in the same way that you did in Étude 7-1. Hint: use File.open, IO.readline, and String.split

The phone number is the key for this data. Since there are multiple calls per phone number, you will need a bag type table. To get the individual items from each line, use String.split/2, much as you did in Étude 5-2.

Part Two

Write functions to summarize the number of minutes for a single phone number (summary/1) or for all phone numbers. (summary/0). These functions return a list of tuples in the form:

[{phoneNumber1, minutes]},{phoneNumber2, minutes}, …]

You could write your own code to do time and date calculations to figure out the duration of a phone call, but there’s a limit on how much you really want to re-invent the wheel, especially with something as complex as calendar calculations. Consider, for example, a call that begins on 31 December 2013 at 11:58:36 p.m. and ends on 1 January 2014 at 12:14:22 p.m. I don’t even want to think about calls that start on 28 February and go to the next day.

So, instead, use Erlang’s :calendar.datetime_to_gregorian_seconds/1 function to convert a date and time to the number of seconds since the year zero. (I swear I am not making this up.) The argument to this function is a tuple in the form:

{{year, month, day}, {hours, minutes, seconds}} # for example
{{2013, 07, 14}, {14, 49, 21}}

Round up any number of seconds to the next minute for ech call. Thus, if a call lasts 4 minutes and 6 seconds, round it up to 5 minutes. Hint: add 59 to the total number of seconds before you div 60.

Now might be the time to rewrite part one so that your dates and times are stored in the appropriate format. That way, you do the conversion from string to tuple only once, instead of every time you ask for a summary. The list_to_tuple function will come in handy here.

Here is the sample output, reformatted to avoid long lines

iex(1)> PhoneEts.setup("call_data.csv")
:ok
iex(2)> PhoneEts.summary()
[{"301-555-0433",12},{"946-555-9760",3},
{"213-555-0172",9},{"415-555-7871",7},
{"838-555-1099",9},{"650-555-3326",11},{"729-555-8855",6}]
iex(3)> PhoneEts.summary("415-555-7871")
[{"415-555-7871",7}]
iex(4)> PhoneEts.summary("111-222-3333")
[{"111-222-3333",0}]

Creating the summary for all the phone numbers will be easier if you use :ets.first/1 and :ets.next/2. The :ets.first/1 function takes a table name as its argument and returns the first key in the table. The :ets.next/2 function takes the table name as its first argument and the current key as its second argument. It returns the next key in the table. When there are no more keys, it returns the special atom :"$end_of_table".

See a suggested solution in Appendix A.