This is the repo containing my code for the reddit programming challenge (to be sent as an application for hire)

reddit.py 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. # Lily Carpenter
  2. # lily-redditchallenge@azrazalea.net
  3. # 2049 South Florence Apt. 8
  4. # Springfield, MO
  5. # 417-379-3326
  6. #
  7. # Copyright (c) 2013, Lily Carpenter
  8. # All rights reserved.
  9. #
  10. # Redistribution and use in source and binary forms, with or without modification,
  11. # are permitted provided that the following conditions are met:
  12. #
  13. # Redistributions of source code must retain the above copyright notice, this
  14. # list of conditions and the following disclaimer.
  15. #
  16. # Redistributions in binary form must reproduce the above copyright notice, this
  17. # list of conditions and the following disclaimer in the documentation and/or
  18. # other materials provided with the distribution.
  19. #
  20. # Neither the name of Lily Carpenter nor the names of its
  21. # contributors may be used to endorse or promote products derived from
  22. # this software without specific prior written permission.
  23. #
  24. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  25. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  26. # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  27. # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
  28. # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  29. # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  30. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  31. # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  32. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  33. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  34. #
  35. # Premise:
  36. # While I'm sure there is a fancy algorithm to do this very efficiently, I have not gotten far enough into
  37. # math/computer science theory in order to be able to come up with it effectively.
  38. #
  39. # Therefore, here is how my script works:
  40. # 1. Sorts all panels in descending order based on value
  41. # 2. Starting with the person with the highest yield (me) and going from there
  42. # it adds the highest value panels to the set panel list until we reach or surpass goal
  43. # 3. If we managed to hit goal exactly, we are done. Otherwise, start second process
  44. # 4. In the second process we resort data in ascending order and try until we reach or surpass goal
  45. #
  46. # This should theoretically result in using the lower yield people the least, as well as getting as close as possible
  47. # to the goal without going over.
  48. import argparse
  49. import sys
  50. import locale
  51. # Setup command line arguments
  52. # I did goal and days partly just because I wanted to play with the values
  53. parser = argparse.ArgumentParser(description='Determine panel distribution')
  54. parser.add_argument('file_name', metavar='<File Name>', type=str, help='file containing panel data')
  55. parser.add_argument('-g', '--goal', metavar='<Goal>', type=int, help='Goal we are trying to reach (default 250000)', default=250000)
  56. parser.add_argument('-d', '--days', metavar='<Days>', type=int, help='Number of days to reach goal (default 7)', default=7)
  57. # A list of dictionaries containing the necessary data on each person
  58. # Also stores the set of panels to be assigned to each person
  59. # This is pulled from the problem description, of course
  60. people = [
  61. {'name': 'M', 'yield': 1, 'pan_a_day': 1, 'panels': [] },
  62. {'name': 'GM', 'yield': .95, 'pan_a_day': 2, 'panels': [] },
  63. {'name': '?', 'yield': .85, 'pan_a_day': 3, 'panels': [] },
  64. {'name': 'GB', 'yield': .6, 'pan_a_day': 4, 'panels': [] },
  65. ]
  66. # Simple function, simply loads data from file_name and converts it to int, puts in list data, and returns data
  67. # The data list contains tuples of (index, value) to preserve original index later when sorting
  68. # If it gets something that isn't a number it'll tell you about it, make that entry 0, then continue like nothing happened
  69. def get_data(file_name):
  70. data = []
  71. with open(file_name, 'r') as _file:
  72. for line_num, line in enumerate(_file, 1):
  73. try:
  74. line = int(line)
  75. except ValueError:
  76. sys.stderr.write('Skipping invalid data at line: ' + str(line_num) + '\n')
  77. # This is so that we don't miss a panel index
  78. data.append(0)
  79. else:
  80. data.append((line_num - 1, int(line)))
  81. return data
  82. # Grab the data and arguments
  83. args = parser.parse_args()
  84. data = get_data(args.file_name)
  85. days = args.days
  86. goal = args.goal
  87. # Grab replacement cost of panels
  88. replace_cost = data.pop(0)[1]
  89. # Sort by the 'value' column, descending, to get list ready for processing
  90. data.sort(key=lambda datem: datem[1], reverse=True)
  91. current_money = 0
  92. panel = 0
  93. # This loop goes through each person, people is already setup to be descending by yield
  94. for person in people:
  95. for x in range(0, person['pan_a_day'] * days):
  96. # This is how much the current panel is worth, taking into account yield and
  97. # replacement cost
  98. additional_amt = data[panel][1] * person['yield'] - replace_cost
  99. # If the next panel in list does not meet or go past the goal, add to person's panel set
  100. # and increment current_money by additional_amt
  101. if current_money + additional_amt <= goal:
  102. current_money += additional_amt
  103. person['panels'].append((data[panel][0], data[panel][1]))
  104. panel += 1
  105. # Otherwise, we need to switch gears and look for the smallest possible panel to finish
  106. else:
  107. data.sort(key=lambda datem: datem[1])
  108. # Have to have this up here in case there was a perfect match
  109. if current_money >= goal:
  110. break;
  111. for y in range(0, len(data) - panel):
  112. additional_amt = data[y][1] * person['yield'] - replace_cost
  113. if current_money + additional_amt >= goal:
  114. current_money += additional_amt
  115. person['panels'].append((data[y][0], data[y][1]))
  116. break;
  117. if current_money >= goal:
  118. break;
  119. # Print the output, all pretty like
  120. for person in people:
  121. print ' '.join([person['name'], '=', repr(person['panels'])])
  122. # Add up how much money is left
  123. money_left = 0
  124. for x in range(panel, len(data)):
  125. money_left += data[x][1]
  126. # Print out how much money is left
  127. locale.setlocale(locale.LC_ALL, '')
  128. print 'Total cash left in bannana stand: $' + format(money_left, "n")