Posted in:

I'm very excited to announce that in October I'll be speaking on LINQ at Techorama Netherlands, sharing some of my best practices for becoming more effective with LINQ.

This means its time for another LINQ Challenge! These are short programming challenges, can be solved with a single LINQ expression. Of course, that might not always make for the most readable code, so feel free to solve these with or without the help of LINQ, and of course solutions in other languages are welcome. If you're using LINQ, then the MoreLINQ library often has extension methods that simplify the task.

This challenge is a little more tricky than the previous ones, so if you'd like a more gentle introduction, try some of the earlier challenges first (I've linked to the answers, but I recommend trying to solve them first before looking at the solutions)

Problem 1 - Longest Sequence

The following string contains number of sales made per day in a month:

"1,2,1,1,0,3,1,0,0,2,4,1,0,0,0,0,2,1,0,3,1,0,0,0,6,1,3,0,0,0"

How long is the longest sequence of days without a sale? (in this example it's 4)

Problem 2 - Full House

In poker a hand is a "full house" if it contains three cards of one value and two of another value. The following string defines five poker hands, separated by a semicolon:

"4♣ 5♦ 6♦ 7♠ 10♥;10♣ Q♥ 10♠ Q♠ 10♦;6♣ 6♥ 6♠ A♠ 6♦;2♣ 3♥ 3♠ 2♠ 2♦;2♣ 3♣ 4♣ 5♠ 6♠".

Write a LINQ expression that returns an sequence containing only the "full house" hands.

Problem 3 - Christmas Days

What day of the week is Christmas day (25th December) on for the next 10 years (starting with 2018)? The answer should be a string (or sequence of strings) starting: Tuesday,Wednesday,Friday,...

Problem 4 - Anagrams

From the following dictionary of words,

"parts,traps,arts,rats,starts,tarts,rat,art,tar,tars,stars,stray"

return all words that are an anagram of star (no leftover letters allowed).

Problem 5 - Initial Letters

From the following list of names

"Santi Cazorla, Per Mertesacker, Alan Smith, Thierry Henry, Alex Song, Paul Merson, Alexis Sánchez, Robert Pires, Dennis Bergkamp, Sol Campbell"

find any groups of people who share the same initials as each other.

Problem 6 - Video Editing

A video is two hours long exactly, and we want to make some edits, cutting out the following time ranges (expressed in H:MM:SS):

"0:00:00-0:00:05;0:55:12-1:05:02;1:37:47-1:37:51".

(You can assume that the input ranges are in order and contain no overlapping portions)

We would like to turn this into a sequence of time-ranges to keep. So in this example, the output should be:

"0:00:05-0:55:12;1:05:02-1:37:47;1:37:51-2:00:00"

Share your answers

I hope you have fun solving these. Why not your solutions in GitHub Gists, and share your approach with the rest of us in the comments below. Although I've already created LINQ solutions to each of these puzzles which I'll post later, every time I do this I find I learn something from the way other people have approached the problems.

Want to learn more about LINQ? Be sure to check out my Pluralsight course LINQ Best Practices.

Comments

Comment by James Curran

#3 seems fairly simple:
String.Join(",", Enumerable.Range(2018, 10).Select(y=>new DateTime(y, 12,25).DayOfWeek.ToString()))
(my office blocks github Gists -- but not the rest of github, from the office network)

James Curran
Comment by James Curran

#5 wasn't that difficult either, though this could use some tidying up, but I think I made good use of tuples in the Selects...
"Santi Cazorla, Per Mertesacker, Alan Smith, Thierry Henry, Alex Song, Paul Merson, Alexis Sánchez, Robert Pires, Dennis Bergkamp, Sol Campbell"
.Split(',')
.Select(n=>(parts:n.Trim().Split(' '), name:n))
.Select(tp=>(initial:tp.parts[0][0].ToString()+tp.parts[1][0].ToString(), tp.name))
.GroupBy(tp =>tp.initial, tp=>tp.name)
.Where(g=>g.Count() > 1)
.Select(g=> g.Key + " ==> " + String.Join(",", g));

James Curran
Comment by James Curran

IN two of your solutions (notably, the two I'm stuck on), you make use of a method called "Segment", which I can't find any info on.

James Curran
Comment by Mark Heath

nice work :)

Mark Heath
Comment by Mark Heath

yes, an easier one, intended to show off the use of Enumerable.Range

Mark Heath
Comment by Mark Heath

yes, its nice that tuples are available for use in LINQ - shame that deconstruction doesn't seem to be supported in the lambda syntax yet

Mark Heath
Comment by Mark Heath

nice, I like the approach to #1

Mark Heath
Comment by babbelnedd

I feel a little stupid reading your anagram solution. clever.

babbelnedd
Comment by Mark Jones

Thanks, I was pleased with that solution

Mark Jones
Comment by Leyu Sisay


//1
"1,2,1,1,0,3,1,0,0,2,4,1,0,0,0,0,2,1,0,3,1,0,0,0,6,1,3,0,0,0".
Split('0').
Max(s => s.Trim(',',' ').Split(',').Count());

//2
"4♣ 5♦ 6♦ 7♠ 10♥;10♣ Q♥ 10♠ Q♠ 10♦;6♣ 6♥ 6♠ A♠ 6♦;2♣ 3♥ 3♠ 2♠ 2♦;2♣ 3♣ 4♣ 5♠ 6♠".
Split(';').
Where(hand => hand.Split().
Select(card => card.Trim('♣', '♦', '♥', '♠')).
GroupBy(rank => rank).
Aggregate(true, (full, set) => full && (set.Count() == 2 || set.Count() == 3)));

//3
Enumerable.Range(2018, 10).
Select(year => new DateTime(year, 12, 25).DayOfWeek);

//4
"parts,traps,arts,rats,starts,tarts,rat,art,tar,tars,stars,stray".
Split(',').
Where(word => string.Concat(word.OrderBy(letter => letter)) == "arst");

//5
"Santi Cazorla, Per Mertesacker, Alan Smith, Thierry Henry, Alex Song, Paul Merson, Alexis Sánchez, Robert Pires, Dennis Bergkamp, Sol Campbell".
Split(',').
Select(name => name.Trim()).
GroupBy(name => name.Split().Aggregate((f,l)=>$"{f[0]}{l[0]}"),(initial, names) => names).
Where(names => names.Count() > 1);
//6
//Original
"0:00:00-0:00:05;0:55:12-1:05:02;1:37:47-1:37:51".
Split(';').
Select(cut => cut.Split('-')).
Aggregate("0:00:00", (keep, cuts) => keep == cuts[0] ? cuts[1] : $"{keep}-{cuts[0]};{cuts[1]}") + "-2:00:00";
//Works well even if the cut scene doesn't start at 0:00:00, might need to add Replace("2:00:00-2:00:00","") at the end, to avoid edge case

//Based on Mark Jones's solution; works for all edge cases
"0:00:00-0:00:05;0:55:12-1:05:02;1:37:47-1:37:51".
Split('-').
Select((r, i) => (r.Contains(';') ? r : i == 0 ? (r == "0:00:00" ? "" : $"0:00:00;{r}") : (r == "2:00:00" ? "" : $"{r};2:00:00")).Replace(";", "-")).
Where(r => r.Any()).
Aggregate((left,right) => $"{left};{right}");

Leyu Sisay
Comment by James Curran

SO, once I found MoreLinq, it became much easier...… (Actually, the most time consuming part --- trying to get the Gist page to work on Edge. The Add File button causes it to lock up)
https://gist.github.com/jam...

James Curran
Comment by James Curran

Your #5 only looks at the initial for the first name. It should be just those people with a common first & last initial. (The data masks the problem)

James Curran
Comment by Leyu Sisay

Fixed, thanks.

Leyu Sisay
Comment by Horia Toma

Coming a bit late to the party, but I had fun coding the solutions.
Here's the gist link: https://gist.github.com/hto...

Horia Toma
Comment by Bianca Ghiuruțan

Hey. Thanks for sharing your solution.
Just FYI, your solution for the full house problem is not foolproof, as it will catch pairs and three of a kind as well.

Bianca Ghiuruțan
Comment by David Burstin

For Puzzle 6, without using MoreLinq, but same number of lines and easier to understand IMHO:
"0:00:00-0:00:05;0:55:12-1:05:02;1:37:47-1:37:51"
.Split(';','-')
.Skip(1)
.Append("2:00:00")
.Select((s, i) => (time: s, index: i))
.GroupBy(p => p.index / 2)
.Select(g => g.Aggregate("", (agg, curr) => $"{agg}-{curr.time}").Substring(1))
.Aggregate((agg, curr) => $"{agg};{curr}")

David Burstin