-
Notifications
You must be signed in to change notification settings - Fork 0
/
bikes.py
346 lines (279 loc) · 11.3 KB
/
bikes.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
""" CSC108 Assignment 2 Starter code """
from typing import List, TextIO
# A set of constants, each representing a list index for station information.
ID = 0
NAME = 1
LATITUDE = 2
LONGITUDE = 3
CAPACITY = 4
BIKES_AVAILABLE = 5
DOCKS_AVAILABLE = 6
IS_RENTING = 7
IS_RETURNING = 8
####### BEGIN HELPER FUNCTIONS ####################
def is_number(value: str) -> bool:
"""Return True if and only if value represents a decimal number.
>>> is_number('csc108')
False
>>> is_number(' 108 ')
True
>>> is_number('+3.14159')
True
"""
return value.strip().lstrip('-+').replace('.', '', 1).isnumeric()
# It isn't necessary to call this function to implement your bikes.py
# functions, but you can use it to create larger lists for testing.
# See the main block below for an example of how to do that.
def csv_to_list(csv_file: TextIO) -> List[List[str]]:
"""Read and return the contents of the open CSV file csv_file as a list of
lists, where each inner list contains the values from one line of csv_file.
Docstring examples not given since results depend on data to be input.
"""
# Read and discard header.
csv_file.readline()
data = []
for line in csv_file:
data.append(line.strip().split(','))
return data
####### END HELPER FUNCTIONS ####################
### SAMPLE DATA TO USE IN DOCSTRING EXAMPLES ####
SAMPLE_STATIONS = [
[7087, 'Danforth/Aldridge', 43.684371, -79.316756, 23, 9, 14, True, True],
[7088, 'Danforth/Coxwell', 43.683378, -79.322961, 15, 13, 2, False, False]]
HANDOUT_STATIONS = [
[7000, 'Ft. York / Capreol Crt.', 43.639832, -79.395954, 31, 20, 11, True,
True],
[7001, 'Lower Jarvis St / The Esplanade', 43.647992, -79.370907,
15, 5, 10, True, True]]
#########################################
def clean_data(data: List[list]) -> None:
"""Convert each string in data to an int if and only if it represents a
whole number, a float if and only if it represents a number that is not a
whole number, True if and only if it is 'True', False if and only if it is
'False', and None if and only if it is either 'null' or the empty string.
>>> d = [['abc', '123', '45.6', 'True', 'False']]
>>> clean_data(d)
>>> d
[['abc', 123, 45.6, True, False]]
>>> d = [['ab2'], ['-123'], ['False', '3.2']]
>>> clean_data(d)
>>> d
[['ab2'], [-123], [False, 3.2]]
"""
for list_ in data:
for i in range(len(list_)):
element = list_[i]
if is_number(element):
if element.find(".") != -1:
list_[i] = float(element)
else:
list_[i] = int(element)
elif element == "True":
list_[i] = True
elif element == "False":
list_[i] = False
elif element == "null" or element == "":
list_[i] = None
def get_station_info(station_id: int, stations: List[list]) -> list:
"""Return a list containing the following information from stations
about the station with id number station_id:
- station name
- number of bikes available
- number of docks available
(in this order)
Precondition: station_id will appear in stations.
>>> get_station_info(7087, SAMPLE_STATIONS)
['Danforth/Aldridge', 9, 14]
>>> get_station_info(7088, SAMPLE_STATIONS)
['Danforth/Coxwell', 13, 2]
"""
station_info = []
for station in stations:
if station[ID] == station_id:
station_info.extend([station[NAME], station[BIKES_AVAILABLE], \
station[DOCKS_AVAILABLE]])
return station_info
def get_total(index: int, stations: List[list]) -> int:
"""Return the sum of the column in stations given by index.
Precondition: the items in stations at the position
that index refers to are ints.
>>> get_total(BIKES_AVAILABLE, SAMPLE_STATIONS)
22
>>> get_total(DOCKS_AVAILABLE, SAMPLE_STATIONS)
16
"""
total = 0
for station in stations:
total += station[index]
return total
def get_station_with_max_bikes(stations: List[list]) -> int:
"""Return the station ID of the station that has the most bikes available.
If there is a tie for the most available, return the station ID that appears
first in stations.
Precondition: len(stations) > 0
>>> get_station_with_max_bikes(SAMPLE_STATIONS)
7088
"""
bikes_list = []
for station in stations:
bikes_list.append(station[BIKES_AVAILABLE])
return stations[bikes_list.index(max(bikes_list))][ID]
def get_stations_with_n_docks(n: int, stations: List[list]) -> List[int]:
"""Return a list containing the station IDs for the stations in stations
that have at least n docks available, in the same order as they appear
in stations.
Precondition: n >= 0
>>> get_stations_with_n_docks(2, SAMPLE_STATIONS)
[7087, 7088]
>>> get_stations_with_n_docks(5, SAMPLE_STATIONS)
[7087]
"""
docks_list = []
for station in stations:
if station[DOCKS_AVAILABLE] >= n:
docks_list.append(station[ID])
return docks_list
def get_direction(start_id: int, end_id: int, stations: List[list]) -> str:
""" Return a string that contains the direction to travel to get from
station start_id to station end_id according to data in stations.
Precondition: start_id and end_id will appear in stations.
>>> get_direction(7087, 7088, SAMPLE_STATIONS)
'SOUTHWEST'
"""
lat_start = 0
long_start = 0
lat_end = 0
long_end = 0
lat_dir = ''
long_dir = ''
for station in stations:
if station[ID] == start_id:
lat_start = station[LATITUDE]
long_start = station[LONGITUDE]
if station[ID] == end_id:
lat_end = station[LATITUDE]
long_end = station[LONGITUDE]
if lat_start > lat_end:
lat_dir = "SOUTH"
elif lat_start < lat_end:
lat_dir = "NORTH"
else:
lat_dir = ""
if long_start > long_end:
long_dir = "WEST"
elif long_start < long_end:
long_dir = "EAST"
else:
long_dir = ""
return lat_dir + long_dir
def rent_bike(station_id: int, stations: List[list]) -> bool:
"""Update the available bike count and the docks available count
for the station in stations with id station_id as if a single bike was
removed, leaving an additional dock available. Return True if and only
if the rental was successful.
Precondition: station_id will appear in stations.
>>> station_id = SAMPLE_STATIONS[0][ID]
>>> original_bikes_available = SAMPLE_STATIONS[0][BIKES_AVAILABLE]
>>> original_docks_available = SAMPLE_STATIONS[0][DOCKS_AVAILABLE]
>>> rent_bike(station_id, SAMPLE_STATIONS)
True
>>> original_bikes_available - 1 == SAMPLE_STATIONS[0][BIKES_AVAILABLE]
True
>>> original_docks_available + 1 == SAMPLE_STATIONS[0][DOCKS_AVAILABLE]
True
>>> station_id = SAMPLE_STATIONS[1][ID]
>>> original_bikes_available = SAMPLE_STATIONS[1][BIKES_AVAILABLE]
>>> original_docks_available = SAMPLE_STATIONS[1][DOCKS_AVAILABLE]
>>> rent_bike(station_id, SAMPLE_STATIONS)
False
>>> original_bikes_available == SAMPLE_STATIONS[1][BIKES_AVAILABLE]
True
>>> original_docks_available == SAMPLE_STATIONS[1][DOCKS_AVAILABLE]
True
"""
for station in stations:
if station[ID] == station_id:
if station[BIKES_AVAILABLE] > 0 and station[IS_RENTING]:
station[BIKES_AVAILABLE] -= 1
station[DOCKS_AVAILABLE] += 1
return True
return False
def return_bike(station_id: int, stations: List[list]) -> bool:
"""Update the available bike count and the docks available count
for station in stations with id station_id as if a single bike was added,
making an additional dock unavailable. Return True if and only if the
return was successful.
Precondition: station_id will appear in stations.
>>> station_id = SAMPLE_STATIONS[0][ID]
>>> original_bikes_available = SAMPLE_STATIONS[0][BIKES_AVAILABLE]
>>> original_docks_available = SAMPLE_STATIONS[0][DOCKS_AVAILABLE]
>>> return_bike(station_id, SAMPLE_STATIONS)
True
>>> original_bikes_available + 1 == SAMPLE_STATIONS[0][BIKES_AVAILABLE]
True
>>> original_docks_available - 1 == SAMPLE_STATIONS[0][DOCKS_AVAILABLE]
True
>>> station_id = SAMPLE_STATIONS[1][ID]
>>> original_bikes_available = SAMPLE_STATIONS[1][BIKES_AVAILABLE]
>>> original_docks_available = SAMPLE_STATIONS[1][DOCKS_AVAILABLE]
>>> return_bike(station_id, SAMPLE_STATIONS)
False
>>> original_bikes_available == SAMPLE_STATIONS[1][BIKES_AVAILABLE]
True
>>> original_docks_available == SAMPLE_STATIONS[1][DOCKS_AVAILABLE]
True
"""
for station in stations:
if station[ID] == station_id:
if station[DOCKS_AVAILABLE] > 0 and station[IS_RETURNING]:
station[DOCKS_AVAILABLE] -= 1
station[BIKES_AVAILABLE] += 1
return True
return False
def balance_all_bikes(stations: List[list]) -> int:
"""Calculate the percentage of bikes available across all stations
and evenly distribute the bikes so that each station has as close to the
overall percentage of bikes available as possible. Remove bikes from a
station if and only if the station is renting and there is a bike
available to rent, and return a bike if and only if the station is
allowing returns and there is a dock available. Return the difference
between the number of bikes rented and the number of bikes returned.
>>> balance_all_bikes(HANDOUT_STATIONS)
0
>>> HANDOUT_STATIONS == [\
[7000, 'Ft. York / Capreol Crt.', 43.639832, -79.395954, 31, 17, 14, \
True, True], \
[7001, 'Lower Jarvis St / The Esplanade', 43.647992, -79.370907, \
15, 8, 7, True, True]]
True
"""
bikes_returned = 0
bikes_rented = 0
total_bikes = get_total(BIKES_AVAILABLE, stations)
capacity = get_total(CAPACITY, stations)
percentage = (total_bikes / capacity)
for station in stations:
bikes_wanted = round(percentage * station[CAPACITY])
is_returned = True
is_rented = True
while (station[BIKES_AVAILABLE] != bikes_wanted) and is_returned and \
is_rented:
if bikes_wanted > station[BIKES_AVAILABLE]:
is_returned = return_bike(station[ID], stations)
if is_returned:
bikes_returned += 1
elif bikes_wanted < station[BIKES_AVAILABLE]:
is_rented = rent_bike(station[ID], stations)
if is_rented:
bikes_rented += 1
return bikes_rented - bikes_returned
#if __name__ == '__main__':
#pass
## To test your code with larger lists, you can uncomment the code below to
## read data from the provided CSV file.
#stations_file = open('stations.csv')
#bike_stations = csv_to_list(stations_file)
#clean_data(bike_stations)
## For example,
#print('Testing get_station_with_max_bikes: ', \
#get_station_with_max_bikes(bike_stations) == 7033)