using System.Collections.Generic;
namespace PartTimeShifts.Classes
public static class Extensions
private static readonly Random rng = new Random();
public static void Shuffle<T>(this IList<T> list)
internal const string ClosedFilename = "chiusure";
internal const int DefaultCost = 100;
internal const int MinCost = 1;
internal const int MaxCost = 3 * DefaultCost;
public readonly string Tag;
public readonly int Cost;
public ShiftTag(char tag, int cost)
public class ShiftTags : List<ShiftTag>
new ShiftTag('C', Shared.MinCost),
new ShiftTag('O', Shared.DefaultCost / 4),
new ShiftTag('o', Shared.DefaultCost / 2),
new ShiftTag('-', Shared.DefaultCost),
new ShiftTag('x', Shared.DefaultCost * 2),
new ShiftTag('X', Shared.MaxCost),
public ShiftTag this[string tag] => this.FirstOrDefault(i => i.Tag == tag);
public readonly List<string> FileRows;
public List<int> Cost { get; private set; }
public bool IsFake => Tag == Shared.ClosedFilename;
public int DailyShifts => FileRows?.Count ?? 0;
public int Days => FileRows.FirstOrDefault()?.Length ?? 0;
FileRows = new List<string>();
public static Worker FromTextFile(string filename)
if (!File.Exists(filename)) throw new FileNotFoundException(filename);
var wrk = new Worker { Tag = Path.GetFileNameWithoutExtension(filename) };
var rows = File.ReadAllLines(filename)
.Where(r => !string.IsNullOrEmpty(r))
wrk.FileRows.AddRange(rows);
if (!rows.Any()) throw new Exception($"Empty shift file {filename}");
if (!rows.TrueForAll(r => r.Length == rows.First().Length))
throw new Exception($"Invalid shift file {filename}");
var tags = new ShiftTags();
for (var a = 0; a < rows.First().Length; a++)
for (var b = 0; b < rows.Count; b++)
var tag = tags[rows[b].Substring(a, 1)];
if (tag == null) throw new Exception($"Invalid char at {b},{a} in file {filename}");
public class Workers : List<Worker>
public Worker this[string name] =>
this.FirstOrDefault(s => s.Tag.Equals(name, StringComparison.CurrentCultureIgnoreCase));
public IEnumerable<Worker> RegularWorkers => this.Where(s => !s.IsFake);
public Worker FakeWorker => this.FirstOrDefault(s => s.IsFake);
var costs = new int[Count, Count];
foreach (var cost in s.Cost) costs[row, col++] = cost;
public Dictionary<string, List<int>> CalcResult;
public static Workers FromTextFolder(string dirname)
if (!Directory.Exists(dirname)) throw new DirectoryNotFoundException(dirname);
var shifts = new Workers();
shifts.AddRange(Directory
.GetFiles(dirname, "*.txt")
.Select(Worker.FromTextFile));
if (!shifts.TrueForAll(s => s.Cost.Count == shifts.First().Cost.Count))
throw new Exception($"Shifts files have different sizes");
var cs = shifts.FakeWorker;
foreach (var worker in shifts.RegularWorkers)
for (var a = 0; a < cs.Cost.Count; a++)
if (cs.Cost[a] != Shared.DefaultCost)
cs.Cost[a] = Shared.MinCost;
worker.Cost[a] = Shared.MaxCost;
var workers = new Workers();
if (!this.Any()) return workers;
var closedShifts = FakeWorker?.Cost.Count(s => s != Shared.DefaultCost) ?? 0;
for (var a = 0; a < closedShifts; a++)
var count = this.First().Cost.Count - closedShifts;
var repeats = count / RegularWorkers.Count();
var rs = RegularWorkers.ToList();
for (var a = 0; a < repeats; a++)
var rem = this.First().Cost.Count - workers.Count;
workers.AddRange(rs.Take(rem));
public Dictionary<string, List<int>> Calc()
var ret = new Dictionary<string, List<int>>();
var shifts = Costs.FindAssignments();
foreach (var shift in shifts)
var worker = this[row++].Tag;
if (ret.ContainsKey(worker)) ret[worker].Add(shift);
else ret.Add(worker, new List<int>(new[] { shift }));
CalcResult = new Dictionary<string, List<int>>();
foreach (var v in ret) CalcResult.Add(v.Key, v.Value.OrderBy(s => s).ToList());
public override string ToString()
if (CalcResult == null) return "";
var rows = this.FirstOrDefault()?.DailyShifts ?? 0;
var sb = new StringBuilder();
for (var row = 0; row < rows; row++)
for (var col = 0; col < cols; col++)
var index = col * rows + row;
var wk = CalcResult.First(w => w.Value.Contains(index));
var wkName = wk.Key == Shared.ClosedFilename ? "X" : wk.Key;
if (col > 0) sb.Append(" ");
static void Main(string[] args)
var folder = @"_folderproject_\bin\Debug\turni\S1";
var workers = Classes.Workers.FromTextFolder(folder);
var sqWorkers = workers.Square();
var shifts = sqWorkers.Calc();
foreach (var wk in shifts) Console.WriteLine($"[{wk.Key}] {string.Join(" ", wk.Value)}");
Console.WriteLine(sqWorkers.ToString());
File.WriteAllText(Path.Combine(folder, "results"), sqWorkers.ToString(), Encoding.Default);