Print distinct characters in a string with count - c#

class Program
{
static void Main(string[] args)
{
string s = "Stack Overflows";
var x = from c in s.ToLower()
group c by c into a
select new { a.Key, Count = a.Count() };
Console.WriteLine(Convert.ToString(x));
Console.Read();
}
}
Output is system.linq.Enumable+
i want output like a 2 g 1 s 1 p 2 r 1

Console.WriteLine(String.Join(" ", x.Select(y=>y.Key + " " + y.Count)));
or using lambda syntax
string s = "Stack Overflows";
Console.WriteLine(String.Join(" ", s.GroupBy(c => c)
.Select(g => g.Key + " " + g.Count())));

You can also use aggregate function like this:
Console.WriteLine(x.Select(y => String.Format("{0} {1}", y.Key, y.Count)).Aggregate((y, z) => y + String.Format(" {0}", z)));
Aggregate function can be used for any types (not only strings)

Try this code instead of your code, I have modified #L.B code
string s = "Stack Overflows";
var x = String.Join("", (from c in s.ToLower()
group c by c into a
select new { a.Key, Count = a.Count() }).Select(y => y.Key + " " + y.Count));

Related

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);

C# Possible lambda in array's content declaration

I have such piece of code:
string suffix = "wallpapers\\";
string extenstion = ".jpg";
string[] wallpapers;
.....
void SetWallPapers()
{
wallpapers = new string[] {
suffix + "1" + extenstion,
suffix + "2" + extenstion,
suffix + "3" + extenstion,
suffix + "4" + extenstion,
};
}
Is there any variant to make lambda-declaretion in array content like:
( pseudo-code, idea only! )
wallpapers = new string[] { ( () => { for i = 1 till 4 -> suffix + i + extension; } ) }
Any suggestions?
string[] wallpapers = Enumerable.Range(1, 4)
.Select(i => suffix + i + extenstion)
.ToArray();
string template = "wallpapers\\{0}.jpg";
string[] wallpapers =
Enumerable.Range(1, 4)
.Select(i => string.Format(template, i))
.ToArray();
You can do it using Linq like so:
wallpapers =
Enumerable.Range(1, 4)
.Select(i => String.Concat(suffix, i, extenstion))
.ToArray();
No, there is not. If you're ok with generating your array at runtime, use
Enumerable.Range(1, 4).Select(i => string.Format("{0}{1}{2}", suffix, i, extension)).ToArray();
PS Your code can't work anyway, you can't add int to string.
Enumerable.Range(1, 10).Select(a => "string" + a).ToArray();

Using LINQ how to split string (not on character but on index)

I wanted to split a string
Input :
ABCDEFGHI
Output :
ABC, DEF, GHI
One way is by using For Loop.
string str = "ABCDEFGHI";
List<string> lst = new List<string>();
string temp = "";
for(int i = 0; i < str.Length; i++)
{
temp = str[i].Tostring();
if((i + 1) % 3 == 0)
{
lst.Add(temp);
temp = "";
}
}
string final_str = string.Join(", ", lst);
But how to do that using LINQ?
And another one (without MoreLinq):
var str = "ABCDEFGHI";
var tmp = str.Select((i, index) => new { i, index })
.GroupBy(g => g.index / 3, e => e.i)
.Select(g => String.Join("", g));
var final_string = String.Join(", ", tmp);
With the help of MoreLinq
List<string> lst = str.Batch(3).Select(s => String.Join("",s)).ToList();
using MoreLinq.Batch
var result = str.Batch(3);
type of result is IEnumerable>, ToArray can be used to make it IEnumerable< char[] >
EDIT I forgot last join statement in the first glance
var finalStr = String.Join(",",str.Batch(3).Select(x=>new String(x.ToArray())))
var str = "ABCDEFGHI";
var result = testStr.Select(s => testStr.IndexOf(s))
.Where(i => i%3 == 0)
.Select(i => testStr.Substring(i,3))
.Aggregate("", (a,s) => a += s + ",");
String.Join("", str.Select((x, i) => (i + 1)%3 == 0 ? x + " " : x.ToString()))

How do I specify "not in" in this lambda expression?

I have a quick question becuase my brain won't work with me...
Where do I specify that I want the user_id's in 'Users' that are NOT in 'Groups' ?
db.Users.Join(db.Groups, a => a.user_id, b => b.user_id, (a, b) => new SelectListItem
{
Value = a.user_id.ToString(),
Text = a.surname + " " + a.lastname
});
The following should work (assuming that I understood your question correctly):
db.Users
.Where(x => !db.Groups.Any(y => y.user_id == x.user_id))
.Select(a => new SelectListItem
{
Value = a.user_id.ToString(),
Text = a.surname + " " + a.lastname
});
you can try something like this:
var query = from u in db.Users
where !(from g in dc.Groups
select g.user_id)
.Contains(u.user_id)
select new SelectListItem {
Value = u.user_id.ToString(),
Text = u.surname + " " + u.lastname
};
Take a look here: http://introducinglinq.com/blogs/marcorusso/archive/2008/01/14/the-not-in-clause-in-linq-to-sql.aspx

LINQ list to sentence format (insert commas & "and")

I have a linq query that does something simple like:
var k = people.Select(x=>new{x.ID, x.Name});
I then want a function or linq lambda, or something that will output the names in sentence format using commas and "ands".
{1, John}
{2, Mark}
{3, George}
to
"1:John, 2:Mark and 3:George"
I'm fine with hardcoding the ID + ":" + Name part, but it could be a ToString() depending on the type of the linq query result. I'm just wondering if there is a neat way to do this with linq or String.Format().
public string ToPrettyCommas<T>(
List<T> source,
Func<T, string> stringSelector
)
{
int count = source.Count;
Func<int, string> prefixSelector = x =>
x == 0 ? "" :
x == count - 1 ? " and " :
", ";
StringBuilder sb = new StringBuilder();
for(int i = 0; i < count; i++)
{
sb.Append(prefixSelector(i));
sb.Append(stringSelector(source[i]));
}
string result = sb.ToString();
return result;
}
Called with:
string result = ToPrettyCommas(people, p => p.ID.ToString() + ":" + p.Name);
Why Linq?
StringBuilder sb = new StringBuilder();
for(int i=0;i<k.Count();i++)
{
sb.Append(String.Format("{0}:{1}", k[i].ID, k[i].Name);
if(i + 2 < k.Count())
sb.Append(", ");
else if(i + 1 < k.Count())
sb.Append(" and ");
}
Really, all Linq will let you do is hide the loop.
Also, make sure you do or do not want the "Oxford Comma"; this algorithm will not insert one, but a small change will (append the comma and space after every element except the last, and also append "and " after the next-to-last).
Just for fun, here’s something that really uses functional LINQ — no loop and no StringBuilder. Of course, it’s pretty slow.
var list = new[] { new { ID = 1, Name = "John" },
new { ID = 2, Name = "Mark" },
new { ID = 3, Name = "George" } };
var resultAggr = list
.Select(item => item.ID + ":" + item.Name)
.Aggregate(new { Sofar = "", Next = (string) null },
(agg, next) => new { Sofar = agg.Next == null ? "" :
agg.Sofar == "" ? agg.Next :
agg.Sofar + ", " + agg.Next,
Next = next });
var result = resultAggr.Sofar == "" ? resultAggr.Next :
resultAggr.Sofar + " and " + resultAggr.Next;
// Prints 1:John, 2:Mark and 3:George
Console.WriteLine(result);
Much like the rest, this isn't better than using a string builder, but you can go (ignoring the ID, you can add it in):
IEnumerable<string> names = new[] { "Tom", "Dick", "Harry", "Abe", "Bill" };
int count = names.Count();
string s = String.Join(", ", names.Take(count - 2)
.Concat(new [] {String.Join(" and ", names.Skip(count - 2))}));
This approach pretty much abuses Skip and Take's ability to take negative numbers, and String.Join's willingness to take a single parameter, so it works for one, two or more strings.
Using the Select operation that gives you an index, this can be written as a ONE LINE extension method:
public static string ToAndList<T>(this IEnumerable<T> list, Func<T, string> formatter)
{
return string.Join(" ", list.Select((x, i) => formatter(x) + (i < list.Count() - 2 ? ", " : (i < list.Count() - 1 ? " and" : ""))));
}
e.g.
var list = new[] { new { ID = 1, Name = "John" },
new { ID = 2, Name = "Mark" },
new { ID = 3, Name = "George" } }.ToList();
Console.WriteLine(list.ToAndList(x => (x.ID + ": " + x.Name)));
Improving(hopefully) on KeithS's answer:
string nextBit = "";
var sb = new StringBuilder();
foreach(Person person in list)
{
sb.Append(nextBit);
sb.Append(", ");
nextBit = String.Format("{0}:{1}", person.ID, person.Name);
}
sb.Remove(sb.Length - 3, 2);
sb.Append(" and ");
sb.Append(nextBit);
This is not pretty but will do the job using LINQ
string s = string.Join(",", k.TakeWhile(X => X != k.Last()).Select(X => X.Id + ":" + X.Name).ToArray()).TrimEnd(",".ToCharArray()) + " And " + k.Last().Id + ":" + k.Last().Name;
Y'all are making it too complicated:
var list = k.Select(x => x.ID + ":" + x.Name).ToList();
var str = list.LastOrDefault();
str = (list.Count >= 2 ? list[list.Count - 2] + " and " : null) + str;
str = string.Join(", ", list.Take(list.Count - 2).Concat(new[]{str}));
How about this?
var k = people.Select(x=>new{x.ID, x.Name});
var stringified = people
.Select(x => string.Format("{0} : {1}", x.ID, x.Name))
.ToList();
return string.Join(", ", stringified.Take(stringified.Count-1).ToArray())
+ " and " + stringified.Last();
I have refined my previous answer and I believe this is the most elegant solution yet.
However it would only work on reference types that don't repeat in the collection (or else we'd have to use different means for finding out if item is first/last).
Enjoy!
var firstGuy = guys.First();
var lastGuy = guys.Last();
var getSeparator = (Func<Guy, string>)
(guy => {
if (guy == firstGuy) return "";
if (guy == lastGuy) return " and ";
return ", ";
});
var formatGuy = (Func<Guy, string>)
(g => string.Format("{0}:{1}", g.Id, g.Name));
// 1:John, 2:Mark and 3:George
var summary = guys.Aggregate("",
(sum, guy) => sum + getSeparator(guy) + formatGuy(guy));
This can be the way you can achieve your goal
var list = new[] { new { ID = 1, Name = "John" },
new { ID = 2, Name = "Mark" },
new { ID = 3, Name = "George" }
}.ToList();
int i = 0;
string str = string.Empty;
var k = list.Select(x => x.ID.ToString() + ":" + x.Name + ", ").ToList();
k.ForEach(a => { if (i < k.Count() - 1) { str = str + a; } else { str = str.Substring(0, str.Length -2) + " and " + a.Replace("," , ""); } i++; });
Here's a method that doesn't use LINQ, but is probably as efficient as you can get:
public static string Join<T>(this IEnumerable<T> list,
string joiner,
string lastJoiner = null)
{
StringBuilder sb = new StringBuilder();
string sep = null, lastItem = null;
foreach (T item in list)
{
if (lastItem != null)
{
sb.Append(sep);
sb.Append(lastItem);
sep = joiner;
}
lastItem = item.ToString();
}
if (lastItem != null)
{
if (sep != null)
sb.Append(lastJoiner ?? joiner);
sb.Append(lastItem);
}
return sb.ToString();
}
Console.WriteLine(people.Select(x => x.ID + ":" + x.Name).Join(", ", " and "));
Since it never creates a list, looks at an element twice, or appends extra stuff to the StringBuilder, I don't think you can get more efficient. It also works for 0, 1, and 2 elements in the list (as well as more, obviously).
StringBuilder Approach
Here's an Aggregate with a StringBuilder. There's some position determinations that are made to clean up the string and insert the "and" but it's all done at the StringBuilder level.
var people = new[]
{
new { Id = 1, Name = "John" },
new { Id = 2, Name = "Mark" },
new { Id = 3, Name = "George" }
};
var sb = people.Aggregate(new StringBuilder(),
(s, p) => s.AppendFormat("{0}:{1}, ", p.Id, p.Name));
sb.Remove(sb.Length - 2, 2); // remove the trailing comma and space
var last = people.Last();
// index to last comma (-2 accounts for ":" and space prior to last name)
int indexComma = sb.Length - last.Id.ToString().Length - last.Name.Length - 2;
sb.Remove(indexComma - 1, 1); // remove last comma between last 2 names
sb.Insert(indexComma, "and ");
// 1:John, 2:Mark and 3:George
Console.WriteLine(sb.ToString());
A String.Join approach could have been used instead but the "and" insertion and comma removal would generate ~2 new strings.
Regex Approach
Here's another approach using regex that is quite understandable (nothing too cryptic).
var people = new[]
{
new { Id = 1, Name = "John" },
new { Id = 2, Name = "Mark" },
new { Id = 3, Name = "George" }
};
var joined = String.Join(", ", people.Select(p => p.Id + ":" + p.Name).ToArray());
Regex rx = new Regex(", ", RegexOptions.RightToLeft);
string result = rx.Replace(joined, " and ", 1); // make 1 replacement only
Console.WriteLine(result);
The pattern is simply ", ". The magic lies in the RegexOptions.RightToLeft which makes the match occur from the right and thereby makes the replacement occur at the last comma occurrence. There is no static Regex method that accepts the number of replacements with the RegexOptions, hence the instance usage.
Here's one using a slightly modified version of my answer to Eric Lippert's Challenge which is IMHO the most concise with easy to follow logic (if you're familiar with LINQ).
static string CommaQuibblingMod<T>(IEnumerable<T> items)
{
int count = items.Count();
var quibbled = items.Select((Item, index) => new { Item, Group = (count - index - 2) > 0})
.GroupBy(item => item.Group, item => item.Item)
.Select(g => g.Key
? String.Join(", ", g)
: String.Join(" and ", g));
return String.Join(", ", quibbled); //removed braces
}
//usage
var items = k.Select(item => String.Format("{0}:{1}", item.ID, item.Name));
string formatted = CommaQuibblingMod(items);
static public void Linq1()
{
var k = new[] { new[] { "1", "John" }, new[] { "2", "Mark" }, new[] { "3", "George" } };
Func<string[], string> showPerson = p => p[0] + ": " + p[1];
var res = k.Skip(1).Aggregate(new StringBuilder(showPerson(k.First())),
(acc, next) => acc.Append(next == k.Last() ? " and " : ", ").Append(showPerson(next)));
Console.WriteLine(res);
}
could be optimized by moving k.Last() computation to before the loop
public static string ToListingCommaFormat(this List<string> stringList)
{
switch(stringList.Count)
{
case 0:
return "";
case 1:
return stringList[0];
case 2:
return stringList[0] + " and " + stringList[1];
default:
return String.Join(", ", stringList.GetRange(0, stringList.Count-1))
+ ", and " + stringList[stringList.Count - 1];
}
}
This is the method is faster than the 'efficient' Join method posted by Gabe. For one and two items, it is many times faster, and for 5-6 strings, it is about 10% faster. There is no dependency on LINQ. String.Join is faster than StringBuilder for small arrays, which are typical for human-readable text. In grammar, these are called listing commas, and the last comma should always be included to avoid ambiguity. Here is the resulting code:
people.Select(x=> x.ID.ToString() + ":" + x.Name).ToList().ToListingCommaFormat();
There are ways to optimize this since it isn't very efficient, but something like this may work:
var k = people.Select(x => new {x.ID, x.Name}).ToList();
var last = k.Last();
k.Aggregate(new StringBuilder(), (sentence, item) => {
if (sentence.Length > 0)
{
if (item == last)
sentence.Append(" and ");
else
sentence.Append(", ");
}
sentence.Append(item.ID).Append(":").Append(item.Name);
return sentence;
});

Categories