Skip to content

Instantly share code, notes, and snippets.

@deepti11
Created March 15, 2020 03:57
Show Gist options
  • Save deepti11/f0b6ec0a9d0048266ec00ae5b52eecdd to your computer and use it in GitHub Desktop.
Save deepti11/f0b6ec0a9d0048266ec00ae5b52eecdd to your computer and use it in GitHub Desktop.
Mixtape Application
{
"playlists":
[
{
"action": "add",
"user_id": "7",
"song_ids": [
"3",
"6"
]
},
{
"action": "update",
"id": "2",
"song_id": "20"
},
{
"action": "remove",
"id": "1"
}
]
}
import argparse
import json
class Mixtape:
def __init__(self, mixtape, changes):
self.mixtape = mixtape
self.changes = changes
self.playlists = {}
self.playlist_idx = []
self.songs = {}
self.users = {}
self.counter = 0
def _parse_songs(self, songs):
for song in songs:
self.songs[song["id"]] = {
"artist": song["artist"],
"title": song["title"]
}
def _is_valid_song(self, songids):
for songid in songids:
if songid not in self.songs:
return False
return True
def _is_valid_user(self, userid):
if userid in self.users:
return True
return False
def _parse_users(self, users):
for user in users:
self.users[user["id"]] = {
"name": user["name"]
}
def _parse_playlists(self, playlists):
for idx, playlist in enumerate(playlists):
self.playlist_idx.append({
"id": playlist["id"],
"user_id": playlist["user_id"],
"song_ids": playlist["song_ids"]
})
self.counter += 1
self.playlists[playlist["id"]] = len(self.playlist_idx) - 1
def _is_valid_playlist(self, playlistid):
if playlistid in self.playlists:
return True
return False
def parse_mixtape(self):
""" parse mixtape file """
self._parse_songs(self.mixtape["songs"])
self._parse_users(self.mixtape["users"])
self._parse_playlists(self.mixtape["playlists"])
def apply_changes(self):
""" apply changes specified in changes.json to mixtape-data.json """
self.parse_mixtape()
for change in self.changes["playlists"]:
if change["action"] == "add":
self.add_playlist(change)
elif change["action"] == "update":
self.update_playlist(change)
elif change["action"] == "remove":
self.remove_playlist(change)
def add_playlist(self, change):
""" add a new playlist in O(1)"""
if not change["song_ids"]:
return
if self._is_valid_song(change["song_ids"]) and self._is_valid_user(change["user_id"]):
new_id = self.counter + 1
self.playlist_idx.append({
"id": new_id,
"user_id": change["user_id"],
"song_ids": change["song_ids"]
})
self.playlists[new_id] = len(self.playlist_idx) - 1
def update_playlist(self, change):
""" adds an existing song to an existing playlist in O(1)"""
if self._is_valid_playlist(change["id"]) and self._is_valid_song([change["song_id"]]):
playlist_idx = self.playlists[change["id"]]
playlist_obj = self.playlist_idx[playlist_idx]
playlist_obj.update({
"song_ids": playlist_obj["song_ids"] + [change["song_id"]]
})
def remove_playlist(self, change):
""" removes an existing playlist in O(1) """
_id = change["id"]
if self._is_valid_playlist(_id):
remove_idx = self.playlists[_id]
last_obj = self.playlist_idx[-1]
self.playlist_idx[remove_idx], self.playlist_idx[-1] = self.playlist_idx[-1], self.playlist_idx[remove_idx]
self.playlists[last_obj["id"]] = remove_idx
del self.playlists[_id]
self.playlist_idx.pop()
def generate_output(self):
""" writes output to output.json """
mixtape = {"users": self.mixtape["users"],
"playlists": sorted(self.playlist_idx, key= lambda x: str(x["id"])),
"songs": self.mixtape["songs"]
}
with open("output.json", 'w') as fd:
json.dump(mixtape, fd, indent=4)
def main():
# Parse arguments
parser = argparse.ArgumentParser(description='mixtape application')
parser.add_argument("mixtape", type=argparse.FileType("r"))
parser.add_argument("changes", type=argparse.FileType("r"))
args = parser.parse_args()
handle_changes(args)
def handle_changes(args):
# Read mixtape and change file
with open(args.mixtape.name, 'r') as fd:
mixtape = json.load(fd)
with open(args.changes.name, 'r') as fd:
changes = json.load(fd)
m = Mixtape(mixtape, changes)
m.apply_changes()
m.generate_output()
if __name__ == '__main__':
main()
{
"users" : [
{
"id" : "1",
"name" : "Albin Jaye"
},
{
"id" : "2",
"name" : "Dipika Crescentia"
},
{
"id" : "3",
"name" : "Ankit Sacnite"
},
{
"id" : "4",
"name" : "Galenos Neville"
},
{
"id" : "5",
"name" : "Loviise Nagib"
},
{
"id" : "6",
"name" : "Ryo Daiki"
},
{
"id" : "7",
"name" : "Seyyit Nedim"
}
],
"playlists" : [
{
"id" : "1",
"user_id" : "2",
"song_ids" : [
"8",
"32"
]
},
{
"id" : "2",
"user_id" : "3",
"song_ids" : [
"6",
"8",
"11"
]
},
{
"id" : "3",
"user_id" : "7",
"song_ids" : [
"7",
"12",
"13",
"16",
"2"
]
}
],
"songs": [
{
"id" : "1",
"artist": "Camila Cabello",
"title": "Never Be the Same"
},
{
"id" : "2",
"artist": "Zedd",
"title": "The Middle"
},
{
"id" : "3",
"artist": "The Weeknd",
"title": "Pray For Me"
},
{
"id" : "4",
"artist": "Drake",
"title": "God's Plan"
},
{
"id" : "5",
"artist": "Bebe Rexha",
"title": "Meant to Be"
},
{
"id" : "6",
"artist": "Imagine Dragons",
"title": "Whatever It Takes"
},
{
"id" : "7",
"artist": "Maroon 5",
"title": "Wait"
},
{
"id" : "8",
"artist": "Bazzi",
"title": "Mine"
},
{
"id" : "9",
"artist": "Marshmello",
"title": "FRIENDS"
},
{
"id" : "10",
"artist": "Dua Lipa",
"title": "New Rules"
},
{
"id" : "11",
"artist": "Shawn Mendes",
"title": "In My Blood"
},
{
"id" : "12",
"artist": "Post Malone",
"title": "Psycho"
},
{
"id" : "13",
"artist": "Ariana Grande",
"title": "No Tears Left To Cry"
},
{
"id" : "14",
"artist": "Bruno Mars",
"title": "Finesse"
},
{
"id" : "15",
"artist": "Kendrick Lamar",
"title": "All The Stars"
},
{
"id" : "16",
"artist": "G-Eazy",
"title": "Him & I"
},
{
"id" : "17",
"artist": "Lauv",
"title": "I Like Me Better"
},
{
"id" : "18",
"artist": "NF",
"title": "Let You Down"
},
{
"id" : "19",
"artist": "Dua Lipa",
"title": "IDGAF"
},
{
"id" : "20",
"artist": "Taylor Swift",
"title": "Delicate"
},
{
"id" : "21",
"artist": "Calvin Harris",
"title": "One Kiss"
},
{
"id" : "22",
"artist": "Ed Sheeran",
"title": "Perfect"
},
{
"id" : "23",
"artist": "Meghan Trainor",
"title": "No Excuses"
},
{
"id" : "24",
"artist": "Niall Horan",
"title": "On The Loose"
},
{
"id" : "25",
"artist": "Halsey",
"title": "Alone"
},
{
"id" : "26",
"artist": "Charlie Puth",
"title": "Done For Me"
},
{
"id" : "27",
"artist": "Foster The People",
"title": "Sit Next to Me"
},
{
"id" : "28",
"artist": "Max",
"title": "Lights Down Low"
},
{
"id" : "29",
"artist": "Alice Merton",
"title": "No Roots"
},
{
"id" : "30",
"artist": "5 Seconds Of Summer",
"title": "Want You Back"
},
{
"id" : "31",
"artist": "Camila Cabello",
"title": "Havana"
},
{
"id" : "32",
"artist": "Logic",
"title": "Everyday"
},
{
"id" : "33",
"artist": "Drake",
"title": "Nice For What"
},
{
"id" : "34",
"artist": "Halsey",
"title": "Bad At Love"
},
{
"id" : "35",
"artist": "ZAYN",
"title": "Let Me"
},
{
"id" : "36",
"artist": "Khalid",
"title": "Love Lies"
},
{
"id" : "37",
"artist": "Post Malone",
"title": "rockstar"
},
{
"id" : "38",
"artist": "Rudimental",
"title": "These Days"
},
{
"id" : "39",
"artist": "Liam Payne",
"title": "Familiar"
},
{
"id" : "40",
"artist": "Imagine Dragons",
"title": "Thunder"
}
]
}
{
"users": [
{
"id": "1",
"name": "Albin Jaye"
},
{
"id": "2",
"name": "Dipika Crescentia"
},
{
"id": "3",
"name": "Ankit Sacnite"
},
{
"id": "4",
"name": "Galenos Neville"
},
{
"id": "5",
"name": "Loviise Nagib"
},
{
"id": "6",
"name": "Ryo Daiki"
},
{
"id": "7",
"name": "Seyyit Nedim"
}
],
"playlists": [
{
"id": "2",
"user_id": "3",
"song_ids": [
"6",
"8",
"11",
"20"
]
},
{
"id": "3",
"user_id": "7",
"song_ids": [
"7",
"12",
"13",
"16",
"2"
]
},
{
"id": 4,
"user_id": "7",
"song_ids": [
"3",
"6"
]
}
],
"songs": [
{
"id": "1",
"artist": "Camila Cabello",
"title": "Never Be the Same"
},
{
"id": "2",
"artist": "Zedd",
"title": "The Middle"
},
{
"id": "3",
"artist": "The Weeknd",
"title": "Pray For Me"
},
{
"id": "4",
"artist": "Drake",
"title": "God's Plan"
},
{
"id": "5",
"artist": "Bebe Rexha",
"title": "Meant to Be"
},
{
"id": "6",
"artist": "Imagine Dragons",
"title": "Whatever It Takes"
},
{
"id": "7",
"artist": "Maroon 5",
"title": "Wait"
},
{
"id": "8",
"artist": "Bazzi",
"title": "Mine"
},
{
"id": "9",
"artist": "Marshmello",
"title": "FRIENDS"
},
{
"id": "10",
"artist": "Dua Lipa",
"title": "New Rules"
},
{
"id": "11",
"artist": "Shawn Mendes",
"title": "In My Blood"
},
{
"id": "12",
"artist": "Post Malone",
"title": "Psycho"
},
{
"id": "13",
"artist": "Ariana Grande",
"title": "No Tears Left To Cry"
},
{
"id": "14",
"artist": "Bruno Mars",
"title": "Finesse"
},
{
"id": "15",
"artist": "Kendrick Lamar",
"title": "All The Stars"
},
{
"id": "16",
"artist": "G-Eazy",
"title": "Him & I"
},
{
"id": "17",
"artist": "Lauv",
"title": "I Like Me Better"
},
{
"id": "18",
"artist": "NF",
"title": "Let You Down"
},
{
"id": "19",
"artist": "Dua Lipa",
"title": "IDGAF"
},
{
"id": "20",
"artist": "Taylor Swift",
"title": "Delicate"
},
{
"id": "21",
"artist": "Calvin Harris",
"title": "One Kiss"
},
{
"id": "22",
"artist": "Ed Sheeran",
"title": "Perfect"
},
{
"id": "23",
"artist": "Meghan Trainor",
"title": "No Excuses"
},
{
"id": "24",
"artist": "Niall Horan",
"title": "On The Loose"
},
{
"id": "25",
"artist": "Halsey",
"title": "Alone"
},
{
"id": "26",
"artist": "Charlie Puth",
"title": "Done For Me"
},
{
"id": "27",
"artist": "Foster The People",
"title": "Sit Next to Me"
},
{
"id": "28",
"artist": "Max",
"title": "Lights Down Low"
},
{
"id": "29",
"artist": "Alice Merton",
"title": "No Roots"
},
{
"id": "30",
"artist": "5 Seconds Of Summer",
"title": "Want You Back"
},
{
"id": "31",
"artist": "Camila Cabello",
"title": "Havana"
},
{
"id": "32",
"artist": "Logic",
"title": "Everyday"
},
{
"id": "33",
"artist": "Drake",
"title": "Nice For What"
},
{
"id": "34",
"artist": "Halsey",
"title": "Bad At Love"
},
{
"id": "35",
"artist": "ZAYN",
"title": "Let Me"
},
{
"id": "36",
"artist": "Khalid",
"title": "Love Lies"
},
{
"id": "37",
"artist": "Post Malone",
"title": "rockstar"
},
{
"id": "38",
"artist": "Rudimental",
"title": "These Days"
},
{
"id": "39",
"artist": "Liam Payne",
"title": "Familiar"
},
{
"id": "40",
"artist": "Imagine Dragons",
"title": "Thunder"
}
]
}

Mixtape Application

Python version 3.6

Description

Mixtape Application reads data from mixtape-data.json, loads it to memory.
Also reads changes.json, loads to memory and applies them to mixtape-data.json and produces output.json

Input File Formats:

mixtape-data.json - JSON
changes.json - JSON

Output File Format:

output.json - JSON

Usage:

Tested and Verified in Windows, expected to work in Linux, Mac

Takes two arguments mixtape-data.sjon, changes.json

python mixtape-app.py mixtape-data.json changes.json

Supported Operations:

  1. Add a new playlist

     Create a dictionary object with song_ids, user_id, action: "add"
    
  2. Add a new song to an existing playlist

     Create a dictionary object with playlist id, song_id, action: "update"
     Supports only one song update at a time, can be easily expanded to support multiple songs by changing the data type
    
  3. Remove a playlist

     Create a dictionary object with id, action: "delete"
     Supports to remove only one playlist at a time, can be easily expanded to remove multiple playlists by changing the data type
    

Sample changes.json File:

{
  "playlists":
     [
      {
        "action": "add",
        "user_id": "7",
        "song_ids": [
          "3",
          "6"
        ]
      },
      {
        "action": "update",
        "id": "2",
        "song_id": "20"
      },
       {
         "action": "remove",
         "id": "1"
       }
     ]
}  

Time Complexity:

Now all the operations are done in O(1)

Scaling

As the files get larger, memory will be an issue and reading, writing to a file will not be efficient.

mixtape-data.json is the reference file, needs to be loaded somewhere first before applying changes.json one change at a time.

As there are relations between playlists, user, song, it would be efiicient to store it in a Relational DB like MySQL, 
distributed DB(if needed)

If changes.json gets larger, can be read in chunks and apply the changes iteratively to the mixtape-data.json file. 
Instaed of json, needs to be jsonl. changes.jsonl will have the json inputs in lines.

Assumptions

mixtape-data.json will always have some songs, users, playlists
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment