Unable to find longest match of string within List<String> - c#

I am trying to find the longest match of strings in a list of strings if my List are:
- "1->2"
- "1->2->3"
- "1->2->3->4"
- "5->6"
- "5->6->7"
- "5->6->7->8"
So the output should be because this strings contain all of the matches within the same list, I want to discard the remaining matches which fall short:
"1->2->3->4"
"5->6->7->8"
Update:
As 1->2 and 1->2->3 are contained in 1->2->3->4, so I want to discard the less specific ones like 1->2 and 1->2->3 and take the longest match 1->2->3->4
The paths will always be in order like 1->2->3->4 and not 1->4 or 1->3.
I am trying like this, but I am getting enumeration yielded no results:
public class Program
{
public static void Main(string[] args)
{
List<(string, i)> flattenedPaths = new List<(string, i)>
{
("1->2", 0)
("1->2->3", 1)
("1->2->3->4", 2)
("5->6", 3)
("5->6->7", 4)
("5->6->7->8", 5)
};
IEnumerable<string> uniquePaths = GetUniquePaths(flattenedPaths);
}
public static IEnumerable<(string, int)> GetUniquePaths(List<(string, int)> Paths)
{
for (int i = 0; i < Paths.Count; i++)
{
bool doesMatchContain = Paths.Skip(i)
.Any(x => x.Item1.Contains(Paths[i].Item1));
if (!doesMatchContain)
yield return Paths[i];
}
}
}
Any help is appreciated.

Below is one approach you may wish to try.
betterData is your existing data, but projected into a more usable form - where the upper and lower bounds of the range are integers rather than strings.
The Substring and IndexOf and LastIndexOf code is brittle - but will work for your current sample data - feel free to harden it up with checks for -1 etc.
Once we have a list of data with those upper and lower bounds set, we use RemoveAll to delete any entries from the list which are within a 'wider' range (e.g. 2-3 is within 1-4).
Note also that betterData.ToList() is used to allow us to iterate over the list while modifying it. There are fancier ways of doing the same effect - but they are slightly more error prone so I've gone for the dumb but simple approach here.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
Console.WriteLine(string.Join("\r\n", uniquePaths.Select(z => z.Item1 + " " + z.Item2)));
Console.WriteLine("Done");
Console.ReadLine();
}
private static List<(string, int)> flattenedPaths = new List<(string, int)>
{
("1->2", 0),
("1->2->3", 1),
("1->2->3->4", 2),
("5->6", 3),
("5->6->7", 4),
("5->6->7->8", 5),
};
private static IEnumerable<(string, int)> uniquePaths = GetUniquePaths(flattenedPaths);
private static IEnumerable<(string, int)> GetUniquePaths(List<(string, int)> Paths)
{
var betterData = Paths
.Select(z => new
{
Number = z.Item2,
Value = z.Item1,
Lower = int.Parse(z.Item1.Substring(0, z.Item1.IndexOf("-"))),
Upper = int.Parse(z.Item1.Substring(z.Item1.LastIndexOf("-") + 2))
})
.OrderByDescending(z => z.Value.Length).ThenByDescending(z => z.Upper).ThenBy(z => z.Lower).ToList();
foreach (var entry in betterData.ToList())
{
betterData.RemoveAll(z => z != entry && z.Lower >= entry.Lower && z.Upper <= entry.Upper);
}
return betterData.Select(x => (x.Value, x.Number));
}
}

For your given list
List<string> list= new List<string>
{
"1->2",
"1->2->3",
"1->2->3->4",
"5->6",
"5->6->7",
"5->6->7->8"
};
The LINQ query would be
var result = list
.Where(s => !list.Any(s2 => s2 != s && s2.IndexOf(s) == 0))
.ToList();
result contains "1->2->3->4" and "5->6->7->8"

if the input is in order and if you would like to find the longest sequence of that containing that string then follow below code,
class Program
{
string output;
public void initalize(string findlongestcontainof)
{
int length = 0;
List<string> inputs = new List<string>();
inputs.Add("1->2->3");
inputs.Add("1->2->3->4");
inputs.Add("1->2->3->4->5->6");
inputs.Add("1->2->3->4->5");
foreach(string s in inputs)
{
if(s.Contains(findlongestcontainof))
{
if(s.Length > length)
{
length = s.Length;
output = s;
}
}
}
}
static void Main(string[] args)
{
Program p = new Program();
p.initalize("1->2");
Console.WriteLine(p.output);
Console.ReadLine();
}
}

Related

How to split a string into an array of two letter substrings with C#

Problem
Given a sample string abcdef, i am trying to split that into an array of two character string elements that should results in ['ab','cd','ef'];
What i tried
I tried to iterate through the string while storing the substring in the current index in an array i declared inside the method, but am getting this output
['ab','bc','cd','de','ef']
Code I used
static string[] mymethod(string str)
{
string[] r= new string[str.Length];
for(int i=0; i<str.Length-1; i++)
{
r[i]=str.Substring(i,2);
}
return r;
}
Any solution to correct that with the code to return the correct output is really welcome, Thanks
your problem was that you incremented your index by 1 instead of 2 every time
var res = new List<string>();
for (int i = 0; i < x.Length - 1; i += 2)
{
res.Add(x.Substring(i, 2));
}
should work
EDIT:
because you ask for a default _ suffix in case of odd characters amount,
this should be the change:
var testString = "odd";
string workOn = testString.Length % 2 != 0
? testString + "_"
: testString;
var res = new List<string>();
for (int i = 0; i < workOn.Length - 1; i += 2)
{
res.Add(workOn.Substring(i, 2));
}
two notes to notice:
in .NET 6 Chunk() is available so you can use this as suggested in other answers
this solution might not be the best in case of a very long input
so it really depends on what are your inputs and expectations
.net 6 has an IEnumerable.Chunk() method that you can use to do this, as follows:
public static void Main()
{
string[] result =
"abcdef"
.Chunk(2)
.Select(chunk => new string(chunk)).ToArray();
Console.WriteLine(string.Join(", ", result)); // Prints "ab, cd, ef"
}
Before .net 6, you can use MoreLinq.Batch() to do the same thing.
[EDIT] In response the the request below:
MoreLinq is a set of Linq utilities originally written by Jon Skeet. You can find an implementation by going to Project | Manage NuGet Packages and then browsing for MoreLinq and installing it.
After installing it, add using MoreLinq.Extensions; and then you'll be able to use the MoreLinq.Batch extension like so:
public static void Main()
{
string[] result = "abcdef"
.Batch(2)
.Select(chunk => new string(chunk.ToArray())).ToArray();
Console.WriteLine(string.Join(", ", result)); // Prints "ab, cd, ef"
}
Note that there is no string constructor that accepts an IEnumerable<char>, hence the need for the chunk.ToArray() above.
I would say, though, that including the whole of MoreLinq just for one extension method is perhaps overkill. You could just write your own extension method for Enumerable.Chunk():
public static class MyBatch
{
public static IEnumerable<T[]> Chunk<T>(this IEnumerable<T> self, int size)
{
T[] bucket = null;
int count = 0;
foreach (var item in self)
{
if (bucket == null)
bucket = new T[size];
bucket[count++] = item;
if (count != size)
continue;
yield return bucket;
bucket = null;
count = 0;
}
if (bucket != null && count > 0)
yield return bucket.Take(count).ToArray();
}
}
If you are using latest .NET version i.e (.NET 6.0 RC 1), then you can try Chunk() method,
var strChunks = "abcdef".Chunk(2); //[['a', 'b'], ['c', 'd'], ['e', 'f']]
var result = strChunks.Select(x => string.Join('', x)).ToArray(); //["ab", "cd", "ef"]
Note: I am unable to test this on fiddle or my local machine due to latest version of .NET
With linq you can achieve it with the following way:
char[] word = "abcdefg".ToCharArray();
var evenCharacters = word.Where((_, idx) => idx % 2 == 0);
var oddCharacters = word.Where((_, idx) => idx % 2 == 1);
var twoCharacterLongSplits = evenCharacters
.Zip(oddCharacters)
.Select((pair) => new char[] { pair.First, pair.Second });
The trick is the following, we create two collections:
one where we have only those characters where the original index was even (% 2 == 0)
one where we have only those characters where the original index was odd (% 2 == 1)
Then we zip them. So, we create a tuple by taking one item from the even and one item from the odd collection. Then we create a new tuple by taking one item from the even and ...
And last we convert the tuples to arrays to have the desired output format.
You are on the right track but you need to increment by 2 not by one. You also need to check if the array has not ended before taking the second character else you risk running into an index out of bounds exception. Try this code I've written below. I've tried it and it works. Best!
public static List<string> splitstring(string str)
{
List<string> result = new List<string>();
int strlen = str.Length;
for(int i = 0; i<strlen; i+=2)
{
string currentstr = str[i].ToString();
if (i + 1 <= strlen-1)
{ currentstr += str[i + 1].ToString(); }
result.Add(currentstr);
}
return result;
}

How to sort large numbers stored in a string array?

I need to sort the large numbers stored as string in string array but my algorithm and .net built Array sort method doesn't work.
I tried to convert the number into long or ulong but that throw exception of overflow.
Here is the code that I tried:
string[] unsorted = { "1","2","100","12303479849857341718340192371",
"3084193741082937","3084193741082938","111","200" };
for (int index = 0; index < unsorted.Length - 1; index++)
{
for (int count = 0; count < unsorted.Length - index - 1; count++)
{
if (string.Compare(unsorted[count], unsorted[count + 1]) == 1)
{
string temp = unsorted[count];
unsorted[count] = unsorted[count + 1];
unsorted[count + 1] = temp;
}
}
}
Also used the following method:
Array.Sort(unsorted);
The array should be sorted correctly.
You could use BigInteger.Parse and use Linq's OrderBy on it. For example:
var sorted = unsorted.Select(BigInteger.Parse).OrderBy(e => e).ToArray();
If you need it back as string:
var sorted = unsorted.Select(BigInteger.Parse).OrderBy(e => e).Select(e => e.ToString()).ToArray();
This has a drawback in converting it first to a BigInteger, but probably you need it anyway. However compared to IO and Database access, this nothing adds to a typical application performance.
Pro: Readable
Contra: Probably not the most efficient solution.
Try following :
string[] unsorted = { "1","2","100","12303479849857341718340192371",
"3084193741082937","3084193741082938","111","200" };
var groups = unsorted.OrderBy(x => x.Length).GroupBy(x => x.Length).ToArray();
List<string> results = new List<string>();
foreach (var group in groups)
{
string[] numbers = group.ToArray();
for(int i = 0; i < numbers.Count() - 1; i++)
{
for(int j = i + 1; j < numbers.Count(); j++)
{
if(numbers[i].CompareTo(numbers[j]) == 1)
{
string temp = numbers[i];
numbers[i] = numbers[j];
numbers[j] = temp;
}
}
}
results.AddRange(numbers);
}
You don't have an array of numbers, you have an array of strings. So they're being sorted alpha-numerically.
One option would be to use the BigInteger class to store them as numbers:
BigInteger[] unsorted = {
BigInteger.Parse("1"),
BigInteger.Parse("2"),
BigInteger.Parse("100"),
BigInteger.Parse("12303479849857341718340192371"),
BigInteger.Parse("3084193741082937"),
BigInteger.Parse("3084193741082938"),
BigInteger.Parse("111"),
BigInteger.Parse("200")
};
Failing that, if you want to keep them as strings then you can left-pad them with zeros to make the lengths consistent so the alphanumeric sorting would work:
string[] unsorted = {
"00000000000000000000000000001",
"00000000000000000000000000002",
"00000000000000000000000000100",
"12303479849857341718340192371",
"00000000000003084193741082937",
"00000000000003084193741082938",
"00000000000000000000000000111",
"00000000000000000000000000200"
};
If you choose the former, just change the types in your if block to also be BigInteger as well.
If we want to sort very big numbers stored as strings, without changing string to BigInteger, it's better sort them according to it's length at first and then according to lexicographic order. We can see the sample code below:
using System;
using System.Linq;
public class Test
{
public static void Main()
{
string[] unsorted = { "1","2", "100","12303479849857341718340192371",
"3084193741082937","3084193741082938","111","200" };
unsorted.OrderBy(s => s.Length).ThenBy(s => s);
Console.WriteLine("Sorted numbers are:");
foreach (var x in unsorted) {
Console.WriteLine(x);
}
}
}
Note: In order to use OrderBy and ThenBy functionality , we have to include using System.Linq to our program.
For an elegant solution you can use linq, you will have a minimum of code with good performance.
var result = unsorted.Select(e => decimal.Parse(e)).OrderBy(e => e);
The system must be like following. You can transfer values in the array to an arraylist then you can parse them all to BigInteger by for loop. And finally you can sort the list :
BigInteger[] unsorted;
var bigIntegers = new List<System.Numerics.BigInteger>();
for (int index = 0; index < unsorted.Length - 1; index++)
{
bigIntegers[i] = BigInteger.Parse[i]
}
bigIntegers.sort();
Another variation:
static void Main(string[] args)
{
List<string> unsorted = new List<string>(new string[] {"1","2","100","12303479849857341718340192371",
"3084193741082938","3084193741082937", "111","200" });
unsorted.Sort((x, y) => (x.Length != y.Length ? x.Length.CompareTo(y.Length) : x.CompareTo(y)));
foreach(string number in unsorted)
{
Console.WriteLine(number);
}
Console.Write("Press Enter to quit");
Console.ReadLine();
}
Building upon #jdweng's answer, but further reducing it by eliminating the 'manual' bubble sort:
string[] result =
unsorted.OrderBy(x => x.Length)
.GroupBy(x => x.Length)
.SelectMany(x => x.OrderBy(y => y))
.ToArray();
Or some other variation of the same theme:
using System.Collections.Generic;
using System.Linq;
...
string[] result =
unsorted.OrderBy(x => x, Comparer<string>.Create((a, b) => a.Length == b.Length ? a.CompareTo(b) : a.Length - b.Length))
.GroupBy(x => x.Length)
.SelectMany(x => x)
.ToArray();
(As noted by #fester's comment underneath #jdweng's answer, this approach will not work reliably if some of the strings are numbers with prepended zeros, such as "00123" for example.)
package com.solution.sorting;
import java.util.Arrays;
import java.util.Comparator;
public class ArraySort {
public static void main(String[] args) {
String[] number = { "3", "2", "4", "10", "11", "6", "5", "8", "9", "7" };
String[] unsorted = { "8", "1", "2", "100", "12303479849857341718340192371", "3084193741082937",
"3084193741082938", "111", "200" };
Arrays.sort(number);
for (String s : number)
System.out.println("number="+s);
//
Arrays.sort(unsorted, new Comparator<String>() {
#Override
public int compare(String o1, String o2) {
return compareStrings(o1,o2);//Integer.valueOf(o1).compareTo(Integer.valueOf(o2));
}
});
for (String s : unsorted)
System.out.println("number1=" + s);
}
private static int compareStrings(String s1, String s2) {
//
if (s1.length() < s2.length()) {
return -1;
} else if (s1.length() > s2.length()) {
return 1;
}
for (int i = 0; i < s1.length(); i++) {
if ((int) s1.charAt(i) < (int) s2.charAt(i))
return -1;
if ((int) s1.charAt(i) > (int) s2.charAt(i))
return 1;
}
return 0;
}
}
For extremely large numbers where Primitive Data Types might fail, create a Comparator that first sorts the String by length and then sorts the String by the value of the same length.
Arrays.sort(array, new Comparator<String>() {
#Override
public int compare(String o1, String o2) {
/** Sort by Length */
if (o1.length() < o2.length()) {
return -1;
}
if (o1.length() > o2.length()) {
return 1;
}
/** Sort by the value of the same Length */
return o1.compareTo(o2);
}
});

Algorithm for shortest list of words

The issue is as follows: the user provides a StartWord and EndWord string of X letters together with a list of strings that are also of length X (lets make it 4 but probably more)
static void Main(string[] args)
{
string StartWord = "Spot";
string EndWord = "Spin";
List<string> providedList = new List<string>
{
"Spin", "Spit", "Spat", "Spot", "Span"
};
List<string> result = MyFunc(StartWord, EndWord, providedList);
}
public List<string> MyFunc(string startWord, string endWord, List<string> input)
{
???
}
From the provided parameters I need to display to the user a result that comprises of the SHORTEST list of 4 letter words, starting with StartWord and ending with EndWord with a number of intermediate words that are to be found in the list, where each word differs from the previous word by PRECISELY one letter.
For example the above code should return a list of strings containing these elements:
Spot(as FirstWord),
Spit(only one letter is different from previous word),
Spin (as EndWord)
A bad exapmle would be: Spot, Spat, Span, Spin (as it takes 3 changes compared to the above 2)
I have been looking at some matching algorithms and recursion, but I am not able to figure out how to go about this.
Thank you for any kind of help in advance.
Create a graph where the vertices are words, and an edge connects any two words that differ by one letter.
Do a breadth-first search, starting at the StartWord, looking for the shortest path to the EndWord.
Here is sample code for this solution in a different language (Python). That may give you an even better pointer. :-)
def shortestWordPath (startWord, endWord, words):
graph = {}
for word in words:
graph[word] = {"connected": []}
for word in words:
for otherWord in words:
if 1 == wordDistance(word, otherWord):
graph[word]['connected'].append(otherWord)
todo = [(startWord,0)]
while len(todo):
(thisWord, fromWord) = todo.pop(0)
if thisWord == endWord:
answer = [thisWord, fromWord]
while graph[ answer[-1] ]["from"] != 0:
answer.append(graph[ answer[-1] ]["from"])
answer.reverse()
return answer
elif "from" in graph[thisWord]:
pass # We have already processed this.
else:
graph[thisWord]["from"] = fromWord
for nextWord in graph[thisWord]["connected"]:
todo.append([nextWord, thisWord])
return None
def wordDistance (word1, word2):
return len(differentPositions(word1, word2))
def differentPositions(word1, word2):
answer = []
for i in range(0, min(len(word1), len(word2))):
if word1[i] != word2[i]:
answer.append(i)
for i in range(min(len(word1), len(word2)),
max(len(word1), len(word2))):
answer.append(i)
return answer
print shortestWordPath("Spot", "Spin",
["Spin", "Spit", "Spat", "Spot", "Span"])
This is what I ended up using(please feel free to comment on the up and down side of it):
private List<List<string>> allWordSteps;
private string[] allWords;
public List<string> WordLadder(string wordStart, string wordEnd, string[] allWordsInput)
{
var wordLadder = new List<string>() { wordStart };
this.allWordSteps = new List<List<string>>() { wordLadder };
allWords = allWordsInput;
do
{
wordLadder = this.IterateWordSteps(wordEnd);
}
while (wordLadder.Count() == 0);
return wordLadder;
}
private List<string> IterateWordSteps(string wordEnd)
{
List<List<string>> allWordStepsCopy = this.allWordSteps.ToList();
this.allWordSteps.Clear();
foreach (var wordSteps in allWordStepsCopy)
{
var adjacent = this.allWords.Where(
x => this.IsOneLetterDifferent(x, wordSteps.Last()) &&
!wordSteps.Contains(x));
if (adjacent.Contains(wordEnd))
{
wordSteps.Add(wordEnd);
return wordSteps;
}
foreach (var word in adjacent)
{
List<string> newWordStep = wordSteps.ToList();
newWordStep.Add(word);
this.allWordSteps.Add(newWordStep);
}
}
return new List<string>();
}
private bool IsOneLetterDifferent(string first, string second)
{
int differences = 0;
if (first.Length == second.Length)
{
for (int i = 0; i < first.Length; i++)
{
if (first[i] != second[i])
{
differences++;
}
}
}
return differences == 1;
}

Quickest way to filter a list of words

I have a list with several words. I want to filter out some of them, which don't match a specific pattern. Is it quicker to add all the matches to a temporary list and copy this list to the main list afterwards? Or is it quicker to remove all the mismatches from the main list?
I have to filter 10000 words as quickly as possible, so I'm looking forward to every little speed increasement.
Edit:
string characters = "aAbBcC";
// currentMatches contains all the words from the beginning
List<string> currentMatches = new List<string>();
List<string> newMatches = new List<string>();
foreach (string word in currentMatches)
{
if (characters.IndexOf(word[0]) > -1)
// word match
{
newMatches.Add(word);
}
}
currentMatches = newMatches;
The foreach loop should check whether word begins with one of the characters of characters. Here I copy every match to newMatches before I copy all the new matches to currentMatches.
Assuming a List<T> then you'll have to take in consideration the following:
If Count is less than Capacity, the Add method is an O(1) operation. If the capacity needs to be increased to accommodate the new element, this method becomes an O(n) operation, where n is Count;
The RemoveAt method is an O(n) operation, where n is (Count - index).
If you create the list to hold the matches with an initial capacity set to the total word count then Add will always be O(1) and faster. However you need to take in consideration the overhead of creating this new list with a capacity set to the total word count.
Bottom line, you need to test it and see what works better for your specific scenario.
Here is an example I threw together on how to time methods. There are many ways to do this, and I think you're going to have to try out a few. You can use information like in João Angelo's post to help direct you towards good approaches, but here are a few. Also, if you're willing to spend the time, you could put this all in a loop that would create a new list, run all of the tests, put the TimeSpan results into a collection instead of Console.WriteLine'ing them, and then give you an average of however many iterations of the test. That would help give you an average.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Test
{
public class Program
{
public static void Main(string[] args)
{
List<string> testList = CreateTestList();
const string filter = "abc";
TimeNewListMethod(FilterIntoNewListWithLinq, testList, filter);
TimeInPlaceMethod(FilterInPlaceWithLinq, testList, filter);
TimeNewListMethod(FilterIntoNewListWithForEach, testList, filter);
TimeInPlaceMethod(FilterInPlaceWithRemoveAll, testList, filter);
Console.Read();
}
public static void TimeInPlaceMethod(Action<List<string>, string> testMethod, List<string> toFilter, string filter)
{
List<string> toFilterCopy = new List<string>(toFilter);
DateTime time = DateTime.Now;
testMethod(toFilterCopy, filter);
Console.WriteLine(DateTime.Now - time);
}
public static void TimeNewListMethod(Func<List<string>, string, List<string>> testMethod, List<string> toFilter, string filter)
{
List<string> toFilterCopy = new List<string>(toFilter);
List<string> resultList;
DateTime time = DateTime.Now;
resultList = testMethod(toFilterCopy, filter);
Console.WriteLine(DateTime.Now - time);
}
public static List<string> FilterIntoNewListWithLinq(List<string> toFilter, string filter)
{
return toFilter.Where(element => element.IndexOf(filter) > -1).ToList();
}
public static void FilterInPlaceWithLinq(List<string> toFilter, string filter)
{
toFilter = toFilter.Where(element => element.IndexOf(filter) > -1).ToList();
}
public static List<string> FilterIntoNewListWithForEach(List<string> toFilter, string filter)
{
List<string> returnList = new List<string>(toFilter.Count);
foreach (string word in toFilter)
{
if (word.IndexOf(word[0]) > -1)
{
returnList.Add(word);
}
}
return returnList;
}
public static void FilterInPlaceWithRemoveAll(List<string> toFilter, string filter)
{
toFilter.RemoveAll(element => element.IndexOf(filter) == -1);
}
public static List<string> CreateTestList(int elements = 10000, int wordLength = 6)
{
List<string> returnList = new List<string>();
StringBuilder nextWord = new StringBuilder();
for (int i = 0; i < elements; i++)
{
for (int j = 0; j < wordLength; j++)
{
nextWord.Append(RandomCharacter());
}
returnList.Add(nextWord.ToString());
nextWord.Clear();
}
return returnList;
}
public static char RandomCharacter()
{
return (char)('a' + rand.Next(0, 25));
}
public static Random rand = new Random();
}
}
The whole
characters.IndexOf(word[0]) > -1
was a little unfamiliar to me and so I would go for something more readable and maintainable for future programmers. It took me a minute to figure out you are checking the first char in each string in the list looking for a match in the range of { a A, B, C, a, b, c }. It works, but to me, it was a little cryptic. I'm starting having taken the time to read through it, but I would do it like this:
foreach (string word in currentMatches)
{
if (Regex.IsMatch(word, "^([A-Ca-c])"))
{
newMatches.Add(word);
}
}
I would not worry about copying the temp list back to the initial list. You're already defined it filled it, go ahead and use it.

Permutations using same letters

I am current working on a project where I need to generate all possible permutations from a given set of characters. I am currently using this code:
public static IEnumerable<string> AllPermutations(this IEnumerable<char> s)
{
return s.SelectMany(x =>
{
var index = Array.IndexOf(s.ToArray(), x);
return s.Where((y, i) => i != index).AllPermutations().Select(y => new string(new[] { x }.Concat(y).ToArray())).Union(new[] { new string(new[] { x }) });
}).Distinct();
}
From this answer.
The problem I have is that it won't generate permuations that use the same letter more than once.
For example if I used abcde as the input I need it to generate combinations like aaaaa and dcc etc.
I'm not experienced enough with LINQ to understand where the code is stopping duplicate letters. Any help is greatly appreciated.
This might work, but I'm sure it could be done more efficiently (taking the counting prompt from PeskyGnat):
static IEnumerable<string> GetVariations(string s)
{
int[] indexes = new int[s.Length];
StringBuilder sb = new StringBuilder();
while (IncrementIndexes(indexes, s.Length))
{
sb.Clear();
for (int i = 0; i < indexes.Length; i++)
{
if (indexes[i] != 0)
{
sb.Append(s[indexes[i]-1]);
}
}
yield return sb.ToString();
}
}
static bool IncrementIndexes(int[] indexes, int limit)
{
for (int i = 0; i < indexes.Length; i++)
{
indexes[i]++;
if (indexes[i] > limit)
{
indexes[i] = 1;
}
else
{
return true;
}
}
return false;
}
Edit: Changed to use yield return as per Rawlings suggestion. Much better memory usage if you don't need to keep all the results and you can start using the results before they've all been generated.
I'm amazed this works. It basically goes "make a list of strings from the characters. Then to each string taken from the list, add each character again, and add the resulting strings to the list. Repeat until you've got the right length."
public static IEnumerable<string> BuildStrings(this IEnumerable<char> alphabet)
{
var strings = alphabet.Select(c => c.ToString());
for (int i = 1; i < alphabet.Count(); i++)
{
strings = strings.Union(strings.SelectMany(s => alphabet.Select(c => s + c.ToString())));
}
return strings;
}
A funny one using only recursive lambdas via a fixpoint operator (thx #Rawling for the SelectMany)
// Fix point operator
public static Func<T, TResult> Fix<T, TResult>(Func<Func<T, TResult>, Func<T, TResult>> f)
{
return t => f(Fix<T, TResult>(f))(t);
}
And then
var chars = new[] {'a','b','c','d','e'}.Select(c=>c.ToString()) ;
var result = Fix<int,IEnumerable<string>>(
f =>
x =>
x == 1
? chars
: chars.Union(f(x - 1).SelectMany(s => chars.Select(c => s + c))))(chars.Count());

Categories

Resources