Merge interval with merge distance in C# - c#

Given a list of intervals [start, end] I have to merge them based on a specified merge distance. The intervals are arriving in a particular order, and as they arrive it should merge them according to the specified merge distance as each interval is received. Some of these intervals will be removed (in the arrival stream they will be marked as removed) – in that situation I've to treat the original interval as if it never existed. Example:
Merge distance is 7 – in the following example the input is arriving in order
I've tried the following algorithm to merge them but my Output isn't coming like the above example. Can somebody assists me, what I'm missing here!
Here is my code.
class Program
{
static void Main(string[] args)
{
var intervals = new List<Interval>
{
new Interval
{
start = 1,
end = 20
},
new Interval
{
start = 55,
end = 58
},
new Interval
{
start = 60,
end = 89
},
new Interval
{
start = 15,
end = 31
},
new Interval
{
start = 10,
end = 15
},
new Interval
{
start = 1,
end = 20
}
};
var mergedIntervals = Merge(intervals, 7);
foreach (var item in mergedIntervals)
{
Console.WriteLine($"[{item.start}, {item.end}]");
}
Console.ReadKey();
}
public static List<Interval> Merge(List<Interval> intervals, int mergeDistance)
{
var result = new List<Interval>();
for (int i = 0; i < intervals.Count; i++)
{
var newInterval = new Interval(intervals[i].start, intervals[i].end);
//while (i < intervals.Count - 1 && newInterval.end >= intervals[i + 1].start)
while (i < intervals.Count - 1 && newInterval.end <= mergeDistance) // intervals[i + 1].start)
{
newInterval.end = Math.Max(newInterval.end, intervals[i + 1].end);
i++;
}
result.Add(newInterval);
}
return result;
}
}
public class Interval
{
public int start { get; set; }
public int end { get; set; }
public Interval()
{
}
public Interval(int start, int end)
{
this.start = start;
this.end = end;
}
}

Can you please try this code, working demo code here
class Program
{
static void Main(string[] args)
{
var intervals = new List<Interval>
{
new Interval
{
start = 1,
end = 20,
isAdded = true
},
new Interval
{
start = 55,
end = 58,
isAdded = true
},
new Interval
{
start = 60,
end = 89,
isAdded = true
},
new Interval
{
start = 15,
end = 31,
isAdded = true
},
new Interval
{
start = 10,
end = 15,
isAdded = true
},
new Interval
{
start = 1,
end = 20,
isAdded = false
}
};
var mergedIntervals = Merge(intervals, 7);
foreach (var item in mergedIntervals)
{
Console.WriteLine($"[{item.start}, {item.end}]");
}
Console.ReadKey();
}
public static List<Interval> Merge(List<Interval> intervals, int mergeDistance)
{
var result = new List<IntervalGroup>();
var group = new IntervalGroup();
foreach (var item in intervals)
{
group = result.Where(c => c.Groups.Any(g =>
Math.Abs(g.end - item.start) <= mergeDistance ||
Math.Abs(g.end - item.end) <= mergeDistance)).FirstOrDefault();
if (group != null && item.isAdded)
{
group.Groups.Add(item);
}
else if(item.isAdded)
{
group = new IntervalGroup();
group.Groups = new List<Interval>();
result.Add(group);
group.Groups.Add(item);
}
else if(item.isAdded == false)
{
group.Groups.Remove(group.Groups.Where(c => c.start == item.start && c.end == item.end).First());
}
}
var finalResult = result.Select(s => new Interval { start = s.Groups.Min(min => min.start), end = s.Groups.Max(min => min.end) });
return finalResult.ToList();
}
}
public class Interval
{
public int start { get; set; }
public int end { get; set; }
public bool isAdded { get; set; }
public Interval()
{
}
public Interval(int start, int end, bool isAdded)
{
this.start = start;
this.end = end;
this.isAdded = isAdded;
}
}
public class IntervalGroup
{
public List<Interval> Groups { get; set; }
}

When a problem is beyond your grasp, it is very helpful to break it up into smaller pieces and code the bits that you do understand. Here's how I break it down, step by step.
First, I think it would be convenient to be able to check the length of an interval, so let's add a property.
class Interval
{
/* Prior code */
public int Length => this.end - this.start;
Now let's write a method that merges two intervals:
class Interval
{
/* Prior code */
static public Interval Merge(Interval a, Interval b )
{
return new Interval(Math.Min(a.start, b.start), Math.Max(a.end, b.end));
}
Now we need to write the code that decides if two intervals are capable of being merged. The prototype could look like this:
static public bool CanMerge(Interval a, Interval b, int mergeDistance)
What logic do we need inside? Well, we could check for overlaps and check the merge distance from both ends, but I know a shortcut. Given a merge A + B = C, the merge is allowed if and only if the length of C is less than or equal to the sum of A + B + the merge distance. So we can write this:
class Interval
{
/* Prior code */
static public bool CanMerge(Interval a, Interval b, int mergeDistance)
{
var merged = Merge(a,b);
var canMerge = merged.Length <= a.Length + b.Length + mergeDistance;
return canMerge;
}
From there you can add to a list by checking for mergeable items. Note that recursion is required because the act of merging an interval could result in another interval becoming mergeable.
void AddToList(List<Interval> list, Interval newInterval, int mergeDistance)
{
var target = list.FirstOrDefault( x => Interval.CanMerge(x, newInterval, mergeDistance) );
if (target == null)
{
list.Add(newInterval);
return;
}
list.Remove(target);
AddToList(list, Interval.Merge(target, newInterval), mergeDistance);
}

Related

Get two teams greatest lead throughout game

I have a Game that involves 2 teams. The Game has a List of ScoreEvents. Each ScoreEvent is a 1 point for the Team that scored. I need to know what the Max lead score was for each team (0 if they never had the lead). The ScoreEvents List is ordered by TimeSinceStart.
public class ScoreEvent
{
public int TeamId { get; set; }
public TimeSpan TimeSinceStart { get; set; }
}
public void GetMaxScoreLead()
{
var ScoreEvents = new List<ScoreEvent>
{
new ScoreEvent { TeamId = 0, TimeSinceStart = new TimeSpan(100)},
new ScoreEvent { TeamId = 0, TimeSinceStart = new TimeSpan(200)},
new ScoreEvent { TeamId = 0, TimeSinceStart = new TimeSpan(300)},
//Score at 300 ticks is 3-0 to TeamdId = 0
new ScoreEvent { TeamId = 1, TimeSinceStart = new TimeSpan(400)},
new ScoreEvent { TeamId = 1, TimeSinceStart = new TimeSpan(500)},
new ScoreEvent { TeamId = 1, TimeSinceStart = new TimeSpan(600)},
new ScoreEvent { TeamId = 1, TimeSinceStart = new TimeSpan(700)},
//Score at 700 ticks is a 3-4 to TeamId = 1
new ScoreEvent { TeamId = 0, TimeSinceStart = new TimeSpan(800)},
new ScoreEvent { TeamId = 0, TimeSinceStart = new TimeSpan(900)},
new ScoreEvent { TeamId = 0, TimeSinceStart = new TimeSpan(1000)},
new ScoreEvent { TeamId = 0, TimeSinceStart = new TimeSpan(1100)}
//Score at 1100 ticks is 7-4 to TeamId 0
};
}
So for the example above the answers for greatest lead per team would be:
TeamId (0) = 3 greatest lead
TeamId (1) = 1 greatest lead
EDIT: Code that I've got to. I know I need to keep track of the current score somewhere.
var teamZeroLargestLead = 0;
var teamOneLargestLead = 0;
var internalTeamZeroLargestLead = 0;
var internalTeamOneLargestLead = 0;
foreach (var scoreEvent in scoreEvents.OrderBy(x => x.TimeSinceStart))
{
if (scoreEvent.TeamId == 0)
{
if (internalTeamOneLargestLead > teamOneLargestLead)
{
teamOneLargestLead = internalTeamOneLargestLead;
internalTeamOneLargestLead = 0;
}
internalTeamZeroLargestLead += 1;
}
else
{
if(internalTeamZeroLargestLead > teamZeroLargestLead)
{
teamZeroLargestLead = internalTeamZeroLargestLead;
internalTeamZeroLargestLead = 0;
}
internalTeamOneLargestLead += 1;
}
}
I've slightly updated and simplified your algorithm with foreach loop and now it returns the correct result - teamZeroLead is 3, teamOneLead is 1.
var teamZeroLead = 0;
var teamOneLead = 0;
var teamZeroScore = 0;
var teamOneScore = 0;
foreach (var scoreEvent in scoreEvents.OrderBy(x => x.TimeSinceStart))
{
if (scoreEvent.TeamId == 0)
{
teamZeroScore++;
teamZeroLead = Math.Max(teamZeroLead, teamZeroScore - teamOneScore);
}
else
{
teamOneScore++;
teamOneLead = Math.Max(teamOneLead, teamOneScore - teamZeroScore);
}
}
At every loop iteration you are calculating the current score of every team, then calculate the lead value and assign it to the result value, if it's greater then previously calculated.
The same logic can be written using Aggregate method and value tuple, you can choose what is more readable and convenient for you
var result = scoreEvents.Aggregate((teamZeroLead: 0, teamOneLead: 0, teamZeroScore: 0, teamOneScore: 0),
(scores, scoreEvent) =>
{
if (scoreEvent.TeamId == 0)
{
scores.teamZeroScore++;
scores.teamZeroLead = Math.Max(scores.teamZeroLead, scores.teamZeroScore - scores.teamOneScore);
}
else
{
scores.teamOneScore++;
scores.teamOneLead = Math.Max(scores.teamOneLead, scores.teamOneScore - scores.teamZeroScore);
}
return scores;
});
After execution you can get the result values using result.teamZeroLead and result.teamOneLead
var leftTeamId = ScoreEvents.First().TeamId
var res = ScoreEvents
.OrderBy(x => x.TimeSinceStart)
.Aggregate(
(max: 0, min: 0, curr: 0),
(acc, currSE) => {
var curr = currSE.TeamId == leftTeamId
? acc.curr +1
: acc.curr - 1;
if(curr > acc.max)
{
return (curr, acc.min, curr);
}
else if (curr < acc.min)
{
return (acc.max, curr, curr);
}
return (acc.max, acc.min, curr);
});
And for "left" team with id leftTeamId you use res.max for "right" team you use Math.Abs(res.min):
TeamId (0) = res.max greatest lead
TeamId (1) = Math.Abs(res.min) greatest lead
I did not get rightTeamId, cause in theory only one team could have scored (but assumed that at least one did =).
Since there are only two teams see if this approach satisfies your needs.
int team1Score = 0;
int team2Score = 0;
int maximumLead = 0;
int maximumLeadTeamId = -1;
for (int i = 0; i < ScoreEvents.Count; i++)
{
if (ScoreEvents[i].TeamId == 0)
{
team1Score++;
}
else
{
team2Score++;
}
int currentLead = Math.Abs(team1Score - team2Score);
if (currentLead > maximumLead)
{
maximumLead = currentLead;
maximumLeadTeamId = ScoreEvents[i].TeamId;
}
}
maximumLeadTeamId is the Id of the team with maximum lead throughout the game and maximumLead is the maximum goals difference between the two teams.
You can use this to get a dictionary of team id to score at the specified timespan:
public static Dictionary<int, int> GetMaxScoreLead(IEnumerable<ScoreEvent> scoreEvents, TimeSpan time)
{
var scoreDictionary = new Dictionary<int, int>();
var grouping = scoreEvents.Where(e => e.TimeSinceStart <= time).GroupBy(e => e.TeamId);
foreach (var group in grouping)
{
scoreDictionary.Add(group.Key, group.Count());
}
return scoreDictionary;
}
This code does not depend on the number of teams. You can trivially determine the winning team from this dictionary structure. For example:
var winningTeam = getScores.OrderByDescending(x => x.Value).FirstOrDefault();

Build up List from another List

I've got a list of Players. Each Player has a Marketvalue. I need to build up a second list which iterates through the player list and builds up a team. The tricky thing is the new team should have at least 15 players and a maximum Marketvalue of 100 Mio +/- 1%.
Does anyone know how to do that elegantly?
private Result<List<Player>> CreateRandomTeam(List<Player> players, int startTeamValue)
{
// start formation 4-4-2
// Threshold tw 20 mio defender 40 mio Midfielder 40 Mio Striker 50 Mio
var playerKeeperList = players.FindAll(p => p.PlayerPosition == PlayerPosition.Keeper);
var playerDefenderList = players.FindAll(p => p.PlayerPosition == PlayerPosition.Defender);
var playerMidfieldList = players.FindAll(p => p.PlayerPosition == PlayerPosition.Midfield);
var playerStrikerList = players.FindAll(p => p.PlayerPosition == PlayerPosition.Striker);
List<Player> keeperPlayers = AddRandomPlayers(playerKeeperList, 2, 0, 20000000);
List<Player> defenderPlayers = AddRandomPlayers(playerDefenderList, 4, 0, 40000000);
List<Player> midfieldPlayers = AddRandomPlayers(playerMidfieldList, 4, 0, 40000000);
List<Player> strikerPlayers = AddRandomPlayers(playerStrikerList, 2, 0, 50000000);
List<Player> team = new List<Player>();
team.AddRange(keeperPlayers);
team.AddRange(defenderPlayers);
team.AddRange(midfieldPlayers);
team.AddRange(strikerPlayers);
var currentTeamValue = team.Sum(s => s.MarketValue);
var budgetLeft = startTeamValue - currentTeamValue;
players.RemoveAll(p => team.Contains(p));
var player1 = AddRandomPlayers(players, 2, 0, budgetLeft);
team.AddRange(player1);
players.RemoveAll(p => player1.Contains(p));
currentTeamValue = team.Sum(t => t.MarketValue);
budgetLeft = startTeamValue - currentTeamValue;
var player2 = players.Aggregate((x, y) => Math.Abs(x.MarketValue - budgetLeft) < Math.Abs(y.MarketValue - budgetLeft) ? x : y);
team.Add(player2);
players.Remove(player2);
return Result<List<Player>>.Ok(team);
}
private static List<Player> AddRandomPlayers(List<Player> players, int playerCount, double minMarketValue, double threshold)
{
// TODO: AYI Implement Random TeamName assign logic
Random rnd = new Random();
var team = new List<Player>();
double assignedTeamValue = 0;
while (team.Count < playerCount)
{
var index = rnd.Next(players.Count);
var player = players[index];
if ((assignedTeamValue + player.MarketValue) <= threshold)
{
team.Add(player);
players.RemoveAt(index);
assignedTeamValue += player.MarketValue;
}
}
return team;
}`
This isn't really a C# question so much as an algorithm question, so there may be a better place for it. AIUI, you want to pick 15 numbers from a list, such that the total adds up to 99-101.
It's likely that there are many solutions, all equally valid.
I think you could do it like this:
Build a list of the 14 cheapest items.
Pick the highest value, so long as the remaining space is greater than the total of the 14 cheapest.
Repeat the above, skipping any players that won't fit.
Fill the remaining places with players from the 'cheapest' list.
This will probably give you a team containing the best and worst players, and one middle-ranking player that just fits.
If you want to do some more research, this sounds like a variant of the coin change problem.
Just to show my solution if someone need's it.
var selection = new EliteSelection();
var crossover = new OnePointCrossover(0);
var mutation = new UniformMutation(true);
var fitness = new TeamFitness(players, startTeamValue);
var chromosome = new TeamChromosome(15, players.Count);
var population = new Population(players.Count, players.Count, chromosome);
var ga = new GeneticAlgorithm(population, fitness, selection, crossover, mutation)
{
Termination = new GenerationNumberTermination(100)
};
ga.Start();
var bestChromosome = ga.BestChromosome as TeamChromosome;
var team = new List<Player>();
if (bestChromosome != null)
{
for (int i = 0; i < bestChromosome.Length; i++)
{
team.Add(players[(int) bestChromosome.GetGene(i).Value]);
}
// Remove assigned player to avoid duplicate assignment
players.RemoveAll(p => team.Contains(p));
return Result<List<Player>>.Ok(team);
}
return Result<List<Player>>.Error("Chromosome was null!");
There is a fitness method which handles the logic to get the best result.
class TeamFitness : IFitness
{
private readonly List<Player> _players;
private readonly int _startTeamValue;
private List<Player> _selected;
public TeamFitness(List<Player> players, int startTeamValue)
{
_players = players;
_startTeamValue = startTeamValue;
}
public double Evaluate(IChromosome chromosome)
{
double f1 = 9;
_selected = new List<Player>();
var indexes = new List<int>();
foreach (var gene in chromosome.GetGenes())
{
indexes.Add((int)gene.Value);
_selected.Add(_players[(int)gene.Value]);
}
if (indexes.Distinct().Count() < chromosome.Length)
{
return int.MinValue;
}
var sumMarketValue = _selected.Sum(s => s.MarketValue);
var targetValue = _startTeamValue;
if (sumMarketValue < targetValue)
{
f1 = targetValue - sumMarketValue;
}else if (sumMarketValue > targetValue)
{
f1 = sumMarketValue - targetValue;
}
else
{
f1 = 0;
}
var keeperCount = _selected.Count(s => s.PlayerPosition == PlayerPosition.Keeper);
var strikerCount = _selected.Count(s => s.PlayerPosition == PlayerPosition.Striker);
var defCount = _selected.Count(s => s.PlayerPosition == PlayerPosition.Defender);
var middleCount = _selected.Count(s => s.PlayerPosition == PlayerPosition.Midfield);
var factor = 0;
var penaltyMoney = 10000000;
if (keeperCount > 2)
{
factor += (keeperCount - 2) * penaltyMoney;
}
if (keeperCount == 0)
{
factor += penaltyMoney;
}
if (strikerCount < 2)
{
factor += (2 - strikerCount) * penaltyMoney;
}
if (middleCount < 4)
{
factor += (4 - middleCount) * penaltyMoney;
}
if (defCount < 4)
{
factor += (4 - defCount) * penaltyMoney;
}
return 1.0 - (f1 + factor);
}
}

c# range lookup within a collection

I have searched through the StackOverflow yet couldn't find my question.
I have some data like this:
object a -> min:3 max:13
object b -> min:11 max:20
object c -> min:16 max:21
...
z-> min:200 max:250
For a specified interval, I expect a, b, c or other objects or a list.
For example if (6,8) is passed then I want to have "a", if (12,13) is passed I want to have a list of "a and b", if (17, 20) is passed I want to have a list of "b and c" and if (3,250) then I want to have a list of all.
I don't know in which type of collection I should store the values (3, 13, object a) and others.
Can you name the collection and give an example?
Thanks in advance...
p.s. sorry if I couldn't describe well because of my English and thank you all
So you want to find objects where the min value is smaller/equal the passed min-value and the max value is larger/equal the passed max-value.
var query = objects.Where(obj=> obj.MinVal <= minVal && obj.MaxVal >= maxVal);
Can you name the collection and give an example?
So you don't have a collection? You should fill a List<Range> where Range is a custom class with at least two properties MinVal and MaxVal.
void Main()
{
var input = new List<Interval>
{
new Interval { Name = "a", Min = 3, Max = 13 },
new Interval { Name = "b", Min = 11, Max = 20 },
new Interval { Name = "c", Min = 16, Max = 21 },
new Interval { Name = "z", Min = 200, Max = 250 }
};
var interval = new Interval { Name = "search", Min = 12, Max = 13 };
// Don't forget the third case when the interval
// you're looking for is inside your input intervals
// Min = 210, Max = 220 should return "z"
var result = input.Where(i => (interval.Min <= i.Min && i.Min <= interval.Max) ||
(interval.Min <= i.Max && i.Max <= interval.Max) ||
(i.Min <= interval.Min && interval.Max <= i.Max));
}
class Interval
{
public string Name;
public int Min;
public int Max;
}
You can either create your own type or you can use Tuple<int, int> to represent one object. Then you can create and populate a List of these objects to store your entire collection. After that you can use LINQ to query for the desired objects:
List<Tuple<int, int>> YourCollection = new List<Tuple<int, int>>();
YourCollection.Add(new Tuple<int, int>(3, 13));
YourCollection.Add(new Tuple<int, int>(11, 20));
YourCollection.Add(new Tuple<int, int>(16, 21));
var Results = YourCollection.Where(x => x.Item1 <= MAX && MIN <= x.Item2);
where MIN and MAX define the range that you're interested in. Note that the condition above looks for overlapping (intersection) as appears to be what is needed.
sing a fork of #aush code it will be bettler if you can inherint the class System.Collections.CollectionBase then you can make an easy foreach implementing this class.
structure Interval{
public string Name;
public int Min;
public int Max;
public Interval(string Name,int Min,int Max){
this.Name = Name;
this.Min = Min;
this.Max = Max;
}
public bool IsInsideOfRange(int value){
if(value >= this.Min && value <= this.Max){
return true;
}else{
return false;
}
}
public overrides ToString(){
return this.Name;
}
}
class IntervalCollection : System.Collections.CollectionBase {
public void Add(string Name,int Min,int Max){
Interval Item = new Interval(Name,Min,Max);
this.List.Add(Item);
}
public void Add(Interval Item){
this.List.Add(Item);
}
public string Encode(param int[] values){
string EcodedText = "";
foreach(int iValue in values){
foreach(Interval Item in this){
if(Item.IsInsideOfRange(iValue)){
EncodedText +=Item.ToString();
}
}
}
return EcodedText;
}
}
you can implement this class like this
IntervalCollection Intervals = new IntervalCollection();
string EncodeText = "";
Intervals.Add(new Interval { Name = "a", Min = 3, Max = 13 });
Intervals.Add(new Interval { Name = "b", Min = 11, Max = 20 });
Intervals.Add(new Interval { Name = "c", Min = 16, Max = 21 });
Intervals.Add( "z", 200, 250 }); //you can add item in this way too.
EncodeText = Intervals.Encode(6,8,12,13,17,20);

Determining value jumps in List<T>

I have a class:
public class ShipmentInformation
{
public string OuterNo { get; set; }
public long Start { get; set; }
public long End { get; set; }
}
I have a List<ShipmentInformation> variable called Results.
I then do:
List<ShipmentInformation> FinalResults = new List<ShipmentInformation>();
var OuterNumbers = Results.GroupBy(x => x.OuterNo);
foreach(var item in OuterNumbers)
{
var orderedData = item.OrderBy(x => x.Start);
ShipmentInformation shipment = new ShipmentInformation();
shipment.OuterNo = item.Key;
shipment.Start = orderedData.First().Start;
shipment.End = orderedData.Last().End;
FinalResults.Add(shipment);
}
The issue I have now is that within each grouped item I have various ShipmentInformation but the Start number may not be sequential by x. x can be 300 or 200 based on a incoming parameter. To illustrate I could have
Start = 1, End = 300
Start = 301, End = 600
Start = 601, End = 900
Start = 1201, End = 1500
Start = 1501, End = 1800
Because I have this jump I cannot use the above loop to create an instance of ShipmentInformation and take the first and last item in orderedData to use their data to populate that instance.
I would like some way of identifying a jump by 300 or 200 and creating an instance of ShipmentInformation to add to FinalResults where the data is sequnetial.
Using the above example I would have 2 instances of ShipmentInformation with a Start of 1 and an End of 900 and another with a Start of 1201 and End of 1800
Try the following:
private static IEnumerable<ShipmentInformation> Compress(IEnumerable<ShipmentInformation> shipments)
{
var orderedData = shipments.OrderBy(s => s.OuterNo).ThenBy(s => s.Start);
using (var enumerator = orderedData.GetEnumerator())
{
ShipmentInformation compressed = null;
while (enumerator.MoveNext())
{
var current = enumerator.Current;
if (compressed == null)
{
compressed = current;
continue;
}
if (compressed.OuterNo != current.OuterNo || compressed.End < current.Start - 1)
{
yield return compressed;
compressed = current;
continue;
}
compressed.End = current.End;
}
if (compressed != null)
{
yield return compressed;
}
}
}
Useable like so:
var finalResults = Results.SelectMany(Compress).ToList();
If you want something that probably has terrible performance and is impossible to understand, but only uses out-of-the box LINQ, I think this might do it.
var orderedData = item.OrderBy(x => x.Start);
orderedData
.SelectMany(x =>
Enumerable
.Range(x.Start, 1 + x.End - x.Start)
.Select(n => new { time = n, info = x))
.Select((x, i) => new { index = i, time = x.time, info = x.info } )
.GroupBy(t => t.time - t.info)
.Select(g => new ShipmentInformation {
OuterNo = g.First().Key,
Start = g.First().Start(),
End = g.Last().End });
My brain hurts.
(Edit for clarity: this just replaces what goes inside your foreach loop. You can make it even more horrible by putting this inside a Select statement to replace the foreach loop, like in rich's answer.)
How about this?
List<ShipmentInfo> si = new List<ShipmentInfo>();
si.Add(new ShipmentInfo(orderedData.First()));
for (int index = 1; index < orderedData.Count(); ++index)
{
if (orderedData.ElementAt(index).Start ==
(si.ElementAt(si.Count() - 1).End + 1))
{
si[si.Count() - 1].End = orderedData.ElementAt(index).End;
}
else
{
si.Add(new ShipmentInfo(orderedData.ElementAt(index)));
}
}
FinalResults.AddRange(si);
Another LINQ solution would be to use the Except extension method.
EDIT: Rewritten in C#, includes composing the missing points back into Ranges:
class Program
{
static void Main(string[] args)
{
Range[] l_ranges = new Range[] {
new Range() { Start = 10, End = 19 },
new Range() { Start = 20, End = 29 },
new Range() { Start = 40, End = 49 },
new Range() { Start = 50, End = 59 }
};
var l_flattenedRanges =
from l_range in l_ranges
from l_point in Enumerable.Range(l_range.Start, 1 + l_range.End - l_range.Start)
select l_point;
var l_min = 0;
var l_max = l_flattenedRanges.Max();
var l_allPoints =
Enumerable.Range(l_min, 1 + l_max - l_min);
var l_missingPoints =
l_allPoints.Except(l_flattenedRanges);
var l_lastRange = new Range() { Start = l_missingPoints.Min(), End = l_missingPoints.Min() };
var l_missingRanges = new List<Range>();
l_missingPoints.ToList<int>().ForEach(delegate(int i)
{
if (i > l_lastRange.End + 1)
{
l_missingRanges.Add(l_lastRange);
l_lastRange = new Range() { Start = i, End = i };
}
else
{
l_lastRange.End = i;
}
});
l_missingRanges.Add(l_lastRange);
foreach (Range l_missingRange in l_missingRanges) {
Console.WriteLine("Start = " + l_missingRange.Start + " End = " + l_missingRange.End);
}
Console.ReadKey(true);
}
}
class Range
{
public int Start { get; set; }
public int End { get; set; }
}

What is your solution to the "Escape from Zurg" puzzle in C#?

found this puzzle HERE... I made a brute force solution and I would like to know how you woul solve it...
Buzz, Woody, Rex, and Hamm have to escape from Zurg(a) They merely have to cross
one last bridge before they are free. However, the bridge is fragile and can hold at most
two of them at the same time. Moreover, to cross the bridge a flashlight is needed to
avoid traps and broken parts. The problem is that our friends have only one flashlight
with one battery that lasts for only 60 minutes (this is not a typo: sixty). The toys need
different times to cross the bridge (in either direction):
TOY TIME
Buzz 5 minutes
Woody 10 minutes
Rex 20 minutes
Hamm 25 minutes
Since there can be only two toys on the bridge at the same time, they cannot cross the
bridge all at once. Since they need the flashlight to cross the bridge, whenever two have
crossed the bridge, somebody has to go back and bring the flashlight to those toys on
the other side that still have to cross the bridge.
The problem now is: In which order can the four toys cross the bridge in time (that
is, in 60 minutes) to be saved from Zurg?
//(a) These are characters from the animation movie β€œToy Story 2”.
here is my solution:
public Form1()
{
InitializeComponent();
List<toy> toys = new List<toy>(){
new toy { name = "buzz", time = 5 } ,
new toy { name = "woody", time = 10 } ,
new toy { name = "rex", time = 20 } ,
new toy { name = "ham", time = 25 } ,
};
var posibles = Combinaciones(toys, 4).ToList(); //all permutations
List<Tuple<string, int>> solutions = new List<Tuple<string, int>>();
foreach (var pointA in posibles)
{
string order = "";
int flashlight = 60;
List<toy> pointB = new List<toy>();
do
{
var fastestInA = pointA.Take(2).ToList();
flashlight -= fastestInA.Max(t => t.time);
order += "go " + String.Join(",", fastestInA.Select(t => t.name)) + "\n";
fastestInA.ForEach(t => pointA.Remove(t));
pointB.AddRange(fastestInA);
if (pointB.Count < 4)
{
var fastestInB = pointB.Take(1).ToList();
flashlight -= fastestInB.Max(t => t.time);
order += "return " + String.Join(",", fastestInB.Select(t => t.name).ToArray()) + "\n";
fastestInB.ForEach(t => pointB.Remove(t));
pointA.AddRange(fastestInB);
}
} while (pointB.Count != 4);
solutions.Add(new Tuple<string, int>(order, flashlight));
}
var optimal = solutions.Where(s => s.Item2 == solutions.Max(t => t.Item2)).ToList();
optimal.ForEach(s => Console.Write("Order:\n" + s.Item1 + "TimeLeft:" + s.Item2 + "\n\n"));
}
public class toy
{
public int time { get; set; }
public string name { get; set; }
}
// this is to do permutations
public static List<List<toy>> Combinaciones(List<toy> list, int take)
{
List<List<toy>> combs = new List<List<toy>>();
foreach (var item in list)
{
var newlist = list.Where(i => !i.Equals(item)).ToList();
var returnlist = take <= 1 ? new List<List<toy>> { new List<toy>() } : Combinaciones(newlist, take - 1);
foreach (var l in returnlist)
{
l.Add(item);
}
combs.AddRange(returnlist);
}
return combs.ToList();
}
}
Recursive solution using LINQ:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Zurg
{
class Program
{
static readonly Toy[] toys = new Toy[]{
new Toy("Buzz", 5),
new Toy("Woody", 10),
new Toy("Rex", 20),
new Toy("Ham", 25),
};
static readonly int totalTorch = 60;
static void Main()
{
Console.WriteLine(Go(new State(toys, new Toy[0], totalTorch, "")).Message);
}
static State Go(State original)
{
var final = (from first in original.Start
from second in original.Start
where first != second
let pair = new Toy[]
{
first,
second
}
let flashlight = original.Flashlight - pair.Max(t => t.Time)
select Return(new State(
original.Start.Except(pair),
original.Finish.Concat(pair),
flashlight,
original.Message + string.Format(
"Go {0}. {1} min remaining.\n",
string.Join(", ", pair.Select(t => t.Name)),
flashlight)))
).Aggregate((oldmax, cur) => cur.Flashlight > oldmax.Flashlight ? cur : oldmax);
return final;
}
static State Return(State original)
{
if (!original.Start.Any())
return original;
var final = (from toy in original.Finish
let flashlight = original.Flashlight - toy.Time
let toyColl = new Toy[] { toy }
select Go(new State(
original.Start.Concat(toyColl),
original.Finish.Except(toyColl),
flashlight,
original.Message + string.Format(
"Return {0}. {1} min remaining.\n",
toy.Name,
flashlight)))
).Aggregate((oldmax, cur) => cur.Flashlight > oldmax.Flashlight ? cur : oldmax);
return final;
}
}
public class Toy
{
public string Name { get; set; }
public int Time { get; set; }
public Toy(string name, int time)
{
Name = name;
Time = time;
}
}
public class State
{
public Toy[] Start { get; private set; }
public Toy[] Finish { get; private set; }
public int Flashlight { get; private set; }
public string Message { get; private set; }
public State(IEnumerable<Toy> start, IEnumerable<Toy> finish, int flashlight, string message)
{
Start = start.ToArray();
Finish = finish.ToArray();
Flashlight = flashlight;
Message = message;
}
}
}
The only two solutions are:
* Buzz and Woody go right
* Buzz goes left
* Hamm and Rex go right
* Woody goes left
* Woody and Buzz go right
and
* Buzz and Woody go right
* Woody goes left
* Hamm and Rex go right
* Buzz goes left
* Woody and Buzz go right
Use them to check your problem is giving the right results.
You just made me find out how terribly out of shape I am with AI algorithms :(
I always returned with the fastest guy... bit of a cheat but I'm too tired now to make it work for all combinations. Here's my solution using BFS.
class Program
{
private class Toy
{
public string Name { get; set; }
public int Time { get; set; }
}
private class Node : IEquatable<Node>
{
public Node()
{
Start = new List<Toy>();
End = new List<Toy>();
}
public Node Clone()
{
return new Node
{
Start = new List<Toy>(Start),
End = new List<Toy>(End),
Time = Time,
Previous = this
};
}
public int Time { get; set; }
public List<Toy> Start { get; set; }
public List<Toy> End { get; set; }
public Node Previous { get; set; }
public Toy Go1 { get; set; }
public Toy Go2 { get; set; }
public Toy Return { get; set; }
public bool Equals(Node other)
{
return Start.TrueForAll(t => other.Start.Contains(t)) &&
End.TrueForAll(t => other.End.Contains(t)) &&
Time == other.Time;
}
}
private static void GenerateNodes(Node node, Queue<Node> open, List<Node> closed)
{
foreach(var toy1 in node.Start)
{
foreach(var toy2 in node.Start.Where(t => t != toy1))
{
var newNode = node.Clone();
newNode.Start.Remove(toy1);
newNode.Start.Remove(toy2);
newNode.End.Add(toy1);
newNode.End.Add(toy2);
newNode.Go1 = toy1;
newNode.Go2 = toy2;
newNode.Time += Math.Max(toy1.Time, toy2.Time);
if(newNode.Time <= 60 && !closed.Contains(newNode) && !open.Contains(newNode))
{
open.Enqueue(newNode);
}
}
}
}
static void Main(string[] args)
{
var open = new Queue<Node>();
var closed = new List<Node>();
var initial = new Node
{
Start = new List<Toy>
{
new Toy {Name = "Buzz", Time = 5},
new Toy {Name = "Woody", Time = 10},
new Toy {Name = "Rex", Time = 20},
new Toy {Name = "Ham", Time = 25}
}
};
open.Enqueue(initial);
var resultNodes = new List<Node>();
while(open.Count != 0)
{
var current = open.Dequeue();
closed.Add(current);
if(current.End.Count == 4)
{
resultNodes.Add(current);
continue;
}
if(current.End.Count != 0)
{
var fastest = current.End.OrderBy(t => t.Time).First();
current.End.Remove(fastest);
current.Start.Add(fastest);
current.Time += fastest.Time;
current.Return = fastest;
}
GenerateNodes(current, open, closed);
}
foreach(var result in resultNodes)
{
var path = new List<Node>();
var node = result;
while(true)
{
if(node.Previous == null) break;
path.Insert(0, node);
node = node.Previous;
}
path.ForEach(n => Console.WriteLine("Went: {0} {1}, Came back: {2}, Time: {3}", n.Go1.Name, n.Go2.Name, n.Return != null ? n.Return.Name : "nobody", n.Time));
Console.WriteLine(Environment.NewLine);
}
Console.ReadLine();
}
}
I don't have implementation but here how the solution works:
You always send the fastest pair you got to the other side and return the fastest on the other side, but you never send the same one 2 times(unless everyone was sent 2 times and then you only send fastest that went max 2 times) by marking him(incresing his time by hell).
This can be done with 2 Priority Queues(O(n log) n solution time).
The solution for your case:
P-Q 1 P-Q 2
Buzz
Woody
Rex
Hamm
Buzz + Woody go = 10 min
P-Q 1 P-Q 2
Rex Buzz
Hamm Woody
Buzz goes back = 5 min
P-Q 1 P-Q 2
Hamm Woody
Rex
* Buzz
Hamm + Rex go = 25 min
P-Q 1 P-Q 2
* Buzz Woody
Hamm
Rex
Woody comes back = 10 min
P-Q 1 P-Q 2
* Buzz Hamm
* Woody Rex
Woddy + Buzz go = 10 min
---------------------------
Total: 60 mins
For example for 6 variation you will do:
1 - fastest
2 - after fastest
3 - you got it
4
5
6 - slowest
1 + 2 go
1 goes back
3 + 4 go
2 goes back
5 + 6 go
3 goes back
1 + 2 go
1 goes back
1 + 3 go

Categories