Make every string unique by appending counter - c#

I have a list of string and I want to make every string in that list unique by appending a number at the end of it. Also, it is case insensitive, so "apple" should be assumed the SAME as "Apple" or "apPlE"
For example:
List<string> input = new List<string>();
input.Add("apple");
input.Add("ball");
input.Add("apple");
input.Add("Apple");
input.Add("car");
input.Add("ball");
input.Add("BALL");
Expected output:
"apple", "ball", "apple2", "Apple3", "car", "ball2", "BALL3"
I need help to develop the logic to produce the output. Thank you.
Edited: I CANNOT have 0 and 1, the repeated string must start with 2, 3, 4...

var newList = input.GroupBy(x => x.ToUpper())
.SelectMany(g => g.Select((s, i) => i == 0 ? s : s + (i+1)))
.ToList();
var str = String.Join(", ", newList);
EDIT
var newList = input.Select((s, i) => new { str = s, orginx = i })
.GroupBy(x => x.str.ToUpper())
.Select(g => g.Select((s, j) => new { s = j == 0 ? s.str : s.str + (j + 1), s.orginx }))
.SelectMany(x => x)
.OrderBy(x => x.orginx)
.Select(x => x.s)
.ToList();

Related

linq - list of string - order by letter only, and then by numbers

For example :
this array:
a 2
a 10
a
should be after sort :
a
a 2
a 10
I try this, but it doesn't not work: the order is wrong.
...
.OrderBy(s => s.name)
.ThenBy(s => {
var stringNumber = Regex.Match(s.name, #"\d+").Value;
return string.IsNullOrEmpty(stringNumber)
? 0
: int.Parse(stringNumber);
});
I suggest extracting criterium explicitly: name and number groups in the regular expression:
var list = new List<string>() {
"a",
"a 4",
"b 1",
"a 2",
"a 11"
};
var result = list
.Select(item => new {
value = item,
match = Regex.Match(item, #"^(?<name>.*?)\s*(?<number>[0-9]*)$"),
})
.OrderBy(item => item.match.Groups["name"].Value)
.ThenBy(item => item.match.Groups["number"].Value.Length)
.ThenBy(item => item.match.Groups["number"].Value)
.Select(item => item.value);
Test:
Console.WriteLine(string.Join(Environment.NewLine, result));
Outcome:
a
a 2
a 4
a 11
b 1
Since you said "letter" I assume you want to only sort by the first character, then by the number.
So don't OrderBy the whole string, but by the letter only:
var result = list
.OrderBy(s => s.FirstOrDefault()) // only first character
.ThenBy(s =>
{
var stringNumber = Regex.Match(s.name, #"\d+").Value;
return string.IsNullOrEmpty(stringNumber) ? 0 : int.Parse(stringNumber);
})
Your code sorted by the whole string at first, so ThenBy had no effect anymore.
You could try something like that;
var list = new List<string>()
{
"a ",
"a 4",
"b 1",
"a 2"
};
int value = 0;
list = list
.Select(x => new {
WholeString = x,
StringPart = x.Split(' ')[0],
IntPart = int.TryParse(x.Split(' ')[1], out value) ? value : 0
})
.OrderBy(x => x.StringPart)
.ThenBy(x => x.IntPart)
.Select(x => x.WholeString)
.ToList();
var test = new[] {"a 2", "a 10", "a"};
var sorted = test.OrderBy(s => new string(s.Where(char.IsLetter).ToArray())).ThenBy(s =>
{
var stringNumber = Regex.Match(s, #"\d+").Value;
return string.IsNullOrEmpty(stringNumber) ? 0 : int.Parse(stringNumber);
});
This should sort by letters first, then by numbers.

Create a unique dictionary and add auto increasing integer values

How can I add a unique number to each of the distinct keys?
In the end I want a "collection" of the distinct keys but each key should have also a value which is e.g. the current_index_of_collection + 1
elements.SelectMany(p => p.Properties.Keys).Distinct();
sample output:
Key value
a 1
b 2
c 3
d 4
Are you looking for Select((value, index) => ...)?
https://msdn.microsoft.com/en-us/library/bb534869(v=vs.110).aspx
var dictionary = elements
.SelectMany(p => p.Properties.Keys)
.Distinct()
.Select((key, index) => new {
key = key,
value = index + 1,
})
.ToDictionary(item => item.key, item => item.value);
Or
var array = elements
.SelectMany(p => p.Properties.Keys)
.Distinct()
.Select((key, index) => new KeyValuePair<MyKeyType, int>(key, index + 1))
.ToArray();
You can use the Select overload which has an index field:
string[][] elements = new string[][] { new string[] { "a", "b", "a" } };
var elementsWithIndex = elements.SelectMany(p => p)
.Distinct()
.Select((p, i) => new { Key = p, Value = i + 1 });
Or in your code:
var elementsWithIndex = elements.SelectMany(p => p.Properties.Keys)
.Distinct()
.Select((p, i) => new { Key = p, Value = i + 1 });
You can simply use this.
List<string> keys = new List<string>();
keys.Add("a");
keys.Add("b");
keys.Add("c");
keys.Add("d");
keys.Add("e");
keys.Add("f");
keys.Add("g");
var fields = keys.Distinct().Select ((t,val)=> new { Key= t, Value= (val + 1)});

How do I return a list of the three lowest values in another list

How do I return a list of the 3 lowest values in another list. For example, I want to get the 3 lowest values like this:
in_list = [2, 3, 4, 5, 6, 1]
To this:
out_list: [2, 3, n, n, n, 1]
Maybe a function like this:
out_list = function(in_list, 3)?
in_list and ouput list is declared like this:
List<string> in_list = new List<string>();
List<string> out_list = new List<string>();
Can you help me developing a C# code for this? Further explanation can be given.
If you really want those weird n, there's this simple solution:
public static List<string> Function(List<string> inputList, int max)
{
var inputIntegers = inputList
.Select(z => int.Parse(z))
.ToList();
var maxAuthorizedValue = inputIntegers
.OrderBy(z => z)
.Take(max)
.Last();
return inputIntegers
.Select(z => z <= maxAuthorizedValue ? z.ToString() : "n")
.ToList();
}
public static void Main(string[] args)
{
List<string> in_list = new List<string> { "2", "3", "4", "6", "1", "7" };
var res = Function(in_list, 3);
Console.Read();
}
For your new requirement about duplicates, you could limit the max number of integer your return:
public static List<string> Function(List<string> inputList, int max)
{
var inputIntegers = inputList.Select(z => int.Parse(z)).ToList();
var maxAuthorizedValue = inputIntegers
.OrderBy(z => z)
.Take(max)
.Last();
// I don't really like that kind of LINQ query (which modifies some variable
// inside the Select projection), so a good old for loop would probably
// be more appropriated
int returnedItems = 0;
return inputIntegers.Select(z =>
{
return (z <= maxAuthorizedValue && ++returnedItems <= max) ? z.ToString() : "n";
}).ToList();
}
You need two queries, one to determine the lowest items and one to fill the result-list. You can use a HashSet for faster loookups:
var lowest = new HashSet<String>(in_list
.Select(s => new { s, val = int.Parse(s) })
.OrderBy(x => x.val)
.Take(3)
.Select(x => x.s));
List<string> out_list = in_list.Select(s => lowest.Contains(s) ? s : "n").ToList();
If you actually only want 3 and duplicates are possible this is the best i've come up with:
var lowest = new HashSet<String>(in_list
.Select(s => new { s, val = int.Parse(s) })
.Distinct()
.OrderBy(x => x.val)
.Take(3)
.Select(x => x.s));
List<string> out_list = in_list
.Select((str, index) => new { str, index, value = int.Parse(str) })
.GroupBy(x => x.str)
.SelectMany(g => lowest.Contains(g.Key)
? g.Take(1).Concat(g.Skip(1).Select(x => new { str = "n", x.index, x.value }))
: g.Select(x => new { str = "n", x.index, x.value }))
.OrderBy(x => x.index)
.Select(x => x.str)
.ToList();
You could use Aggregate to grab a Dictionary of each element with its corresponding number of allowed occurrences which you could then use to grab your values from the input list:
public static List<string> GetList(List<string> in_list, int max)
{
Dictionary<string, int> occurrences = new Dictionary<string, int>();
int itemsAdded = 0;
in_list.OrderBy(x => x).Aggregate(occurrences, (list, aggr) =>
{
if (itemsAdded++ < max)
{
if (occurrences.ContainsKey(aggr))
occurrences[aggr]++;
else
occurrences.Add(aggr, 1);
}
return list;
});
//occurrences now contains only each required elements
//with the number of occurrences allowed of that element
List<string> out_list = in_list.Select(x =>
{
return (occurrences.ContainsKey(x) && occurrences[x]-- > 0 ? x : "n");
}).ToList();
return out_list;
}

In c#, how to combine strings and their frequency to a resulting string?

I know that we can find duplicate items like this:
var dublicateItems = itemStrings.GroupBy(x => x)
.Where(x => x.Count() > 1)
.ToDictionary(g => g.Key, g => g.Count());
And distinct items like this:
var distinctItems = itemStrings.Distinct();
But how to combine it to the following list of string:
input: a, b, b, c, d, d, d, d
output: a, b (2 times), c, d (4 times)
You're almost there:
var duplicateItems =
itemStrings
.GroupBy(i => i)
.Select(i => new { Key = i.Key, Count = i.Count() })
.Select(i => i.Key + (i.Count > 1 ? " (" + i.Count + " times)" : string.Empty));
If you want the result as a comma-separated string, you can then do this:
var result = string.Join(", ", duplicateItems);
You have already the solution with the first approach, remove the Where
var itemCounts = itemStrings.GroupBy(x => x)
.ToDictionary(g => g.Key, g => g.Count());
string result = String.Join(", ",
itemCounts.Select(kv => kv.Value > 1
? string.Format("{0} ({1} times)", kv.Key, kv.Value)
: kv.Key));
Another approach is using Enumerable.ToLookup instead of GroupBy:
var itemLookup = itemStrings.ToLookup(x => x);
string result = String.Join(", ",
itemLookup.Select(grp => grp.Count() > 1
? string.Format("{0} ({1} times)", grp.Key, grp.Count())
: grp.Key));
With something like:
string[] itemStrings = new[] { "a", "b", "b", "c", "d", "d", "d", "d" };
string[] duplicateItems = (from x in itemStrings.OrderBy(x => x).GroupBy(x => x)
let cnt = x.Count()
select cnt == 1 ?
x.Key :
string.Format("{0} ({1} times)", x.Key, cnt)
).ToArray();
I've added an OrderBy() because your list seems to be ordered, and I've overcomplicated it a little just to cache the x.Count() (the let cnt = x.Count()) .
If you then want a single big string, you can
string joined = string.Join(",", duplicateItems);

Append index number to duplicated string value in a list - by using Lambda

I have a IList<string>() which holds some string values, and there could be duplicated items in the list. What I want is to append a index number to end of the string to eliminate the duplication.
For example, I have these values in my list: StringA, StringB, StringC, StringA, StringA, StringB. And I want the result looks like: StringA1, StringB1, StringC, StringA2, StringA3, StringB2. I need to retain the original order in list.
Is there a way I can just use one Lambda expression?
You are looking for something like this:
yourList.GroupBy(x => x)
.SelectMany(g => g.Select((x,idx) => g.Count() == 1 ? x : x + idx))
.ToList();
Edit: If the element order matters, here is another solution:
var counts = yourList.GroupBy(x => x).ToDictionary(x => x.Key, x => x.Count());
var values = counts.ToDictionary(x => x.Key, x => 0);
var list = yourList.Select(x => counts[x] > 1 ? x + ++values[x] : x).ToList();
You can do:
List<string> list = new List<string> { "StringA", "StringB", "StringC", "StringA", "StringA", "StringB" };
var newList =
list.Select((r, i) => new { Value = r, Index = i })
.GroupBy(r => r.Value)
.Select(grp => grp.Count() > 1 ?
grp.Select((subItem, i) => new
{
Value = subItem.Value + (i + 1),
OriginalIndex = subItem.Index
})
: grp.Select(subItem => new
{
Value = subItem.Value,
OriginalIndex = subItem.Index
}))
.SelectMany(r => r)
.OrderBy(r => r.OriginalIndex)
.Select(r => r.Value)
.ToList();
and you will get:
StringA1,StringB1,StringC,StringA2,StringA3,StringB2
If you don't want to preserve order then you can do:
var newList = list.GroupBy(r => r)
.Select(grp => grp.Count() > 1 ?
grp.Select((subItem, i) => subItem + (i + 1))
: grp.Select(subItem => subItem))
.SelectMany(r => r)
.ToList();
This uses some lambda expressions and linq to do it, maintaining the order but I'd suggested a function with a foreach loop and yield return would be better.
var result = list.Aggregate(
new List<KeyValuePair<string, int>>(),
(cache, s) =>
{
var last = cache.Reverse().FirstOrDefault(p => p.Key == s);
if (last == null)
{
cache.Add(new KeyValuePair<string, int>(s, 0));
}
else
{
if (last.Value = 0)
{
last.Value = 1;
}
cache.Add(new KeyValuePair<string, int>(s, last.Value + 1));
}
return cache;
},
cache => cache.Select(p => p.Value == 0 ?
p.Key :
p.Key + p.Value.ToString()));

Categories