Skip to content

Instantly share code, notes, and snippets.

@Skorch
Created January 24, 2014 21:13
Show Gist options
  • Save Skorch/8606567 to your computer and use it in GitHub Desktop.
Save Skorch/8606567 to your computer and use it in GitHub Desktop.
/*
This was a coding challenge in which I had 2 hours to analyze the
requirements (comments at bottom of document) and implement as much
functionality as I could within the time limit.
The code is as-is and not modified in any way once the time limit
had expired.
Andrew Beaupre
*/
#define DEBUG
using System;
using System.Collections.Generic;
using System.Linq;
// To execute C#, please define "static void Main" on a class named Solution
class Solution {
private static string[] commands = new string[]
{
"DEPEND TELNET TCPIP NETCARD",
"DEPEND TCPIP NETCARD",
"DEPEND DNS TCPIP NETCARD",
"DEPEND BROWSER TCPIP HTML",
"INSTALL NETCARD",
"INSTALL TELNET",
"INSTALL foo",
"REMOVE NETCARD",
"INSTALL BROWSER",
"INSTALL DNS",
"LIST",
"REMOVE TELNET",
"REMOVE NETCARD",
"REMOVE DNS",
"REMOVE NETCARD",
"INSTALL NETCARD",
"REMOVE TCPIP",
"REMOVE BROWSER",
"REMOVE TCPIP",
"LIST"
};
static void Main(string[] args) {
PackageManager packageManager = new PackageManager();
foreach(var commandLine in commands)
{
var commandTokens = commandLine.Split(' ');
//TODO: need to parse args to get command components
var commandName = commandTokens.Length > 0 ? commandTokens[0] : "Help";
var commandArgs = commandTokens.Length > 1 ? commandTokens.Skip(1) : new string[0];
var command = CommandFactory.GetCommand(commandName);
if(command != null)
{
if(command.ValidateCommand(commandArgs.ToArray()))
{
command.ProcessCommand(commandArgs.ToArray(), packageManager);
}
else
{
System.Console.WriteLine(command.Syntax);
}
}
}
/*
TestDepend(new []{"item 1", "item 2", "item 3"}, packageManager);
TestRemove("item 2", packageManager);
TestInstall("item 1", packageManager);
TestInstall("item 2", packageManager);
TestList(packageManager);
TestRemove("item 1", packageManager);
*/
}
static void TestDepend(string[] packageNames, PackageManager packageManager)
{
var command = CommandFactory.GetCommand("DEPEND");
if(command.ValidateCommand(packageNames))
{
command.ProcessCommand(packageNames, packageManager);
}
else
{
System.Console.WriteLine(command.Syntax);
}
}
static void TestInstall(string packageName, PackageManager packageManager)
{
var commandArgs = new []{packageName};
var command = CommandFactory.GetCommand("INSTALL");
if(command.ValidateCommand(commandArgs))
{
command.ProcessCommand(commandArgs, packageManager);
}
else
{
System.Console.WriteLine(command.Syntax);
}
}
static void TestRemove(string packageName, PackageManager packageManager)
{
var commandArgs = new []{packageName};
var command = CommandFactory.GetCommand("REMOVE");
if(command.ValidateCommand(commandArgs))
{
command.ProcessCommand(commandArgs, packageManager);
}
else
{
System.Console.WriteLine(command.Syntax);
}
}
static void TestList(PackageManager packageManager)
{
var command = CommandFactory.GetCommand("LIST");
if(command.ValidateCommand(null))
{
command.ProcessCommand(null, packageManager);
}
else
{
System.Console.WriteLine(command.Syntax);
}
}
}
class InstalledPackage{
//was this package explicitly installed?
public bool Explicit {get; set;}
//reference to package object that was installed
public Package Package {get; set;}
}
class Package{
public string Name {get; set;}
public IEnumerable<Package> Dependencies {get; set;}
public static Package CreatePackage(string name, IEnumerable<Package> dependencies){
return new Package{
Name = name,
Dependencies = dependencies != null ? dependencies : new List<Package>()
};
}
}
class PackageManager
{
private Dictionary<string, InstalledPackage> InstalledPackages;
private Dictionary<string, Package> ConfiguredPackages;
private Dictionary<string, IEnumerable<InstalledPackage>> ReverseDependencies {get;set;}
public PackageManager()
{
InstalledPackages = new Dictionary<string, InstalledPackage>();
ConfiguredPackages = new Dictionary<string, Package>();
ReverseDependencies = new Dictionary<string, IEnumerable<InstalledPackage>>();
}
public Package GetPackage(string packageName)
{
Package package = null;
if(!ConfiguredPackages.TryGetValue(packageName, out package))
{
}
return package;
}
public IEnumerable<Package> GetAllConfiguredPackages()
{
return ConfiguredPackages.Values;
}
public IEnumerable<InstalledPackage> GetAllInstalledPackages()
{
return InstalledPackages.Values;
}
public InstalledPackage GetInstalledPackage(string packageName)
{
InstalledPackage package = null;
if(!InstalledPackages.TryGetValue(packageName, out package))
{
}
return package;
}
public IEnumerable<InstalledPackage> GetDependentInstalledPackages(string packageName)
{
IEnumerable<InstalledPackage> packages = null;
if(!ReverseDependencies.TryGetValue(packageName, out packages))
{
}
return packages;
}
public void InstallPackage(Package package, bool isExplicit)
{
var installedPackage = GetInstalledPackage(package.Name);
if(installedPackage == null)
{
#if (DEBUG)
System.Console.WriteLine(String.Format("Installing Package: {0}", package.Name));
#endif
installedPackage = new InstalledPackage
{
Package = package,
Explicit = isExplicit
};
InstalledPackages.Add(package.Name, installedPackage);
}
//Only if this is an explicit installation, make sure the package is marked as such
else if(isExplicit)
{
#if (DEBUG)
System.Console.WriteLine(String.Format("Setting Explicit on: {0}", package.Name));
#endif
installedPackage.Explicit = true;
}
}
public void RemoveInstalledPackage(InstalledPackage installedPackage)
{
InstalledPackages.Remove(installedPackage.Package.Name);
#if (DEBUG)
System.Console.WriteLine(String.Format("Removing Package: {0}", installedPackage.Package.Name));
#endif
}
public void ConfigurePackage(Package package)
{
if(!ConfiguredPackages.ContainsKey(package.Name))
{
#if (DEBUG)
System.Console.WriteLine(String.Format("Adding Package: {0}", package.Name));
#endif
ConfiguredPackages.Add(package.Name, package);
}
else{
#if (DEBUG)
System.Console.WriteLine(String.Format("Duplicate package: {0}", package.Name));
#endif
}
foreach(var dependent in package.Dependencies)
{
ConfigurePackage(dependent);
}
}
}
static class CommandFactory
{
private const string COMMAND_DEPEND = "DEPEND";
private const string COMMAND_INSTALL = "INSTALL";
private const string COMMAND_REMOVE = "REMOVE";
private const string COMMAND_LIST = "LIST";
public static ICommand GetCommand(string commandName)
{
switch(commandName)
{
case COMMAND_DEPEND:
return new DependCommand();
case COMMAND_INSTALL:
return new InstallCommand();
case COMMAND_LIST:
return new ListCommand();
case COMMAND_REMOVE:
return new RemoveCommand();
default:
#if (DEBUG)
System.Console.WriteLine(String.Format("Unsupported Command: {0}", commandName));
#endif
break;
}
return new HelpCommand();
}
}
interface ICommand
{
bool ValidateCommand(string[] args);
void ProcessCommand(string[] args, PackageManager packageManager);
string Syntax{get;}
}
class HelpCommand : ICommand
{
private string helpText = @"# =========================================================
# | COMMAND SYNTAX | INTERPRETATION/RESPONSE |
# =========================================================
# | DEPEND item1 item2 [item3 | item1 depends on item2 |
# | ...] | (and item3 ...) |
# ---------------------------------------------------------
# | INSTALL item1 | install item1 and those |
# | | on which it depends |
# ---------------------------------------------------------
# | REMOVE item1 | remove item1, and those |
# | | on whch it depends, if |
# | | possible |
# ---------------------------------------------------------
# | LIST | list the names of all |
# | | currently-installed |
# | | components in |
# | | alphabetical order. |
# ---------------------------------------------------------";
bool ICommand.ValidateCommand(string[] args)
{
return true;
}
void ICommand.ProcessCommand(string[] args, PackageManager packageManager)
{
System.Console.WriteLine(helpText);
}
string ICommand.Syntax
{
get
{
return helpText;
}
}
}
class ListCommand : ICommand
{
bool ICommand.ValidateCommand(string[] args)
{
return args == null || args.Length == 0;
}
void ICommand.ProcessCommand(string[] args, PackageManager packageManager)
{
foreach(var installedPackage in packageManager.GetAllInstalledPackages())
System.Console.WriteLine(
string.Format("Package: {0}{1} - Dependencies: {2}",
installedPackage.Package.Name,
installedPackage.Explicit ? "*" : "",
String.Join(", ",
installedPackage.Package.Dependencies
.Select(d=>d.Name).ToArray())));
}
string ICommand.Syntax
{
get
{
return "LIST";
}
}
}
internal class DependCommand : ICommand
{
bool ICommand.ValidateCommand(string[] args)
{
return args.Length > 0;
}
// | DEPEND item1 item2 [item3 | item1 depends on item2 |
// | ...] | (and item3 ...) |
void ICommand.ProcessCommand(string[] args, PackageManager packageManager)
{
string rootItemName = args[0];
//build the dependencies
var dependencies = new Package[args.Length-1];
//start at index 1 to skip the root
for(int i = 1; i < args.Length; i++)
{
dependencies[i-1] = Package.CreatePackage(args[i], null);
}
Package rootPackage = Package.CreatePackage(rootItemName, dependencies);
packageManager.ConfigurePackage(rootPackage);
}
string ICommand.Syntax
{
get
{
return "Usage: DEPEND item1 item2 [item3]";
}
}
}
internal class InstallCommand : ICommand
{
bool ICommand.ValidateCommand(string[] args)
{
return args.Length == 1;
}
void ICommand.ProcessCommand(string[] args, PackageManager packageManager)
{
string packageName = args[0];
//validate that the package exists in the repository
var package = packageManager.GetPackage(packageName);
if(package != null)
{
//TODO: loop through dependencies
foreach(var dependent in package.Dependencies)
{
packageManager.InstallPackage(dependent, false);
}
packageManager.InstallPackage(package, true);
}
else
{
//TODO: return error to the caller?
System.Console.WriteLine(String.Format("Package is not configured: {0}", packageName));
}
}
string ICommand.Syntax
{
get
{
return "Usage: DEPEND item1 item2 [item3]";
}
}
}
class RemoveCommand : ICommand
{
bool ICommand.ValidateCommand(string[] args)
{
return args.Length == 1;
}
void ICommand.ProcessCommand(string[] args, PackageManager packageManager)
{
var packageName = args[0];
var installedPackage = packageManager.GetInstalledPackage(packageName);
if(installedPackage != null)
{
//verify that package is not a dependency
var parentDependents = packageManager.GetDependentInstalledPackages(installedPackage.Package.Name);
if(parentDependents == null || parentDependents.Count() == 0)
{
packageManager.RemoveInstalledPackage(installedPackage);
//TODO: cleanup any unecessary dependencies
foreach(var dependent in installedPackage.Package.Dependencies)
{
var installedDependent = packageManager.GetInstalledPackage(dependent.Name);
if(installedDependent != null && !installedDependent.Explicit)
{
packageManager.RemoveInstalledPackage(installedDependent);
}
}
}
else
{
//TODO: return error to the caller?
System.Console.WriteLine(String.Format("Package is depended by: {0}",
String.Join(", ",
parentDependents.Select(d=>d.Package.Name).ToArray())
));
}
}
else
{
//TODO: return error to the caller?
System.Console.WriteLine(String.Format("Package is not installed: {0}", packageName));
}
}
string ICommand.Syntax
{
get
{
return "Usage: REMOVE item1";
}
}
}
/*
# =============================================
# || Coding Challenge: System Dependencies ||
# =============================================
#
# Components of computer systems often have
# dependencies--other components that must be installed
# before they will function properly. These dependencies
# are frequently shared by multiple components. For
# example, both the TELNET client program and the FTP
# client program require that the TCP/IP networking
# software be installed before they can operate. If you
# install TCP/IP and the TELNET client program, and later
# decide to add the FTP client program, you do not need to
# reinstall TCP/IP.
#
# It is useful to be able to remove components that are no
# longer needed. When this is done, components that only
# support the removed component may also be removed,
# freeing up disk space, memory, and other resources. But a
# supporting component, not explicitly installed, may be
# removed only if all components which depend on it are
# also removed. For example, removing the FTP client
# program and TCP/IP would mean the TELNET client program,
# which was not removed, would no longer operate. Likewise,
# removing TCP/IP by itself would cause the failure of both
# the TELNET and the FTP client programs. Also if we
# installed TCP/IP to support our own development, then
# installed the TELNET client (which depends on TCP/IP) and
# then still later removed the TELNET client, we would not
# want TCP/IP to be removed. Finally, any package
# explictly installed, i.e. named in an "INSTALL" command,
# must not be removed implicitly and can only be removed if
# named in a "REMOVE" command.
#
# If a package is first installed implicitly as a
# dependency of another package, then installed explictly
# with an install command, the package should be treated as
# if it were explicitly installed. For example, a user
# installs TELNET, which implicitly installs TCP/IP. If the
# user then explicitly installs TCP/IP, TCP/IP should be
# treated as an explicitly installed package; removing
# TELNET should no longer result in removing TCP/IP.
#
# We want a program to automate the process of adding and
# removing components. To do this we will maintain a record
# of installed components and component dependencies. A
# component can be installed explicitly in response to a
# command (unless it is already installed), or implicitly
# if it is needed for some other component being installed.
# Likewise, a component, not explicitly installed, can be
# explicitly removed in response to a command (if it is not
# needed to support other components) or implicitly removed
# if it is no longer needed to support another component.
#
# =============
# || Input ||
# =============
# The input will contain a sequence of commands (as
# described below), each on a separate line containing no
# more than eighty characters. Each item name is no longer
# than ten characters. Letters in item names are always
# uppercase, though names may contain characters other than
# letters. The command names (DEPEND, INSTALL, REMOVE and
# LIST) always appear in uppercase starting in column one,
# and item names are separated from the command name and
# each other by one or more spaces. All appropriate DEPEND
# commands will appear before the occurrence of any INSTALL
# dependencies. The end of the input is marked by a line
# containing only the word END.
#
# If you are coding this on a box where you have full
# control, you can read from input files or STDIN (or pipe
# an input file into STDIN). Or, if you're programming this
# with an interview tool like CoderPad, you can embed the
# input as a string constant in the code and parse that.
#
# =========================================================
# | COMMAND SYNTAX | INTERPRETATION/RESPONSE |
# =========================================================
# | DEPEND item1 item2 [item3 | item1 depends on item2 |
# | ...] | (and item3 ...) |
# ---------------------------------------------------------
# | INSTALL item1 | install item1 and those |
# | | on which it depends |
# ---------------------------------------------------------
# | REMOVE item1 | remove item1, and those |
# | | on whch it depends, if |
# | | possible |
# ---------------------------------------------------------
# | LIST | list the names of all |
# | | currently-installed |
# | | components in |
# | | alphabetical order. |
# ---------------------------------------------------------
#
# ==============
# || Output ||
# ==============
# Echo each line of input. Follow each echoed INSTALL or
# REMOVE line with the actions taken in response, making
# certain that the actions are given in the proper order.
# Also identify exceptional conditions (see Expected
# Output, below, for examples of all cases). For the LIST
# command, display the names of the currently installed
# components. No output, except the echo, is produced for a
# DEPEND command or the line containing END. There will be
# at most one dependency list per item.
#
# ====================
# || Sample Input ||
# ====================
# DEPEND TELNET TCPIP NETCARD
# DEPEND TCPIP NETCARD
# DEPEND DNS TCPIP NETCARD
# DEPEND BROWSER TCPIP HTML
# INSTALL NETCARD
# INSTALL TELNET
# INSTALL foo
# REMOVE NETCARD
# INSTALL BROWSER
# INSTALL DNS
# LIST
# REMOVE TELNET
# REMOVE NETCARD
# REMOVE DNS
# REMOVE NETCARD
# INSTALL NETCARD
# REMOVE TCPIP
# REMOVE BROWSER
# REMOVE TCPIP
# LIST
# END
#
# =====================
# || Sample Output ||
# =====================
# DEPEND TELNET TCPIP NETCARD
# DEPEND TCPIP NETCARD
# DEPEND DNS TCPIP NETCARD
# DEPEND BROWSER TCPIP HTML
# INSTALL NETCARD
# Installing NETCARD
# INSTALL TELNET
# Installing TCPIP
# Installing TELNET
# INSTALL foo
# Installing foo
# REMOVE NETCARD
# NETCARD is still needed.
# INSTALL BROWSER
# Installing HTML
# Installing BROWSER
# INSTALL DNS
# Installing DNS
# LIST
# BROWSER
# DNS
# HTML
# NETCARD
# TCPIP
# TELNET
# foo
# REMOVE TELNET
# Removing TELNET
# REMOVE NETCARD
# NETCARD is still needed.
# REMOVE DNS
# Removing DNS
# REMOVE NETCARD
# NETCARD is still needed.
# INSTALL NETCARD
# NETCARD is already installed.
# REMOVE TCPIP
# TCPIP is still needed.
# REMOVE BROWSER
# Removing BROWSER
# Removing HTML
# Removing TCPIP
# REMOVE TCPIP
# TCPIP is not installed.
# LIST
# NETCARD
# foo
# END
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment