Find first index for value in List - c#

In text I have some identical words and I want to get position for each word.
Using such construction:
fullText = File.ReadAllText(fileName);
List<string> arr = fullText.Split(' ').ToList();
List<string> result = arr.
Where(x => string.Equals(x, "set", StringComparison.OrdinalIgnoreCase)).
ToList();
for (int i = 0; i < result.Count; i++)
{
Console.WriteLine(arr.IndexOf(result[i]));
}
I get only last position for each word.For example, I have:
**LOAD SUBCASE1 SUBTITLE2 LOAD SUBCASE3 SUBTITLE4 load Load Load**
and I must get
**LOAD : position 1
LOAD : position 4
load : position 7
Load: position 8
Load : position 8**

To get the index, try something like this;
List<string> result = arr.Select((s,rn) => new {position = rn+1, val = s})
.Where(s => string.Equals(s.val, "LOAD", StringComparison.OrdinalIgnoreCase))
.Select(s => s.val + " : position " + s.position.ToString())
.ToList();
Above query will not return **LOAD and Load**. To get your expected results with ** to the end, I think you could use s.val.Contains() as below;
List<string> result = arr.Select((s, rn) => new { position = rn + 1, val = s })
.Where(s => s.val.ToLower().Contains("load"))
.Select(s =>
s.val.EndsWith("**") ? s.val.Substring(0, s.val.Length - 2) +
" : position " + s.position.ToString() + "**" : s.val + " : position " +
s.position.ToString())
.ToList();

There is no "set" in your wordlist. .... I've mistake, instead must be
"Load"
You are using Equals to compare. That means you want to compare the whole word, not part of the word, hence "**LOAD" would be omitted. Is that desired? Otherwise use IndexOf.
However, you can use this query:
var words = fullText.Split().Select((w, i) => new{Word = w, Index = i});
var matches = words.Where(w => StringComparer.OrdinalIgnoreCase.Equals("load", w.Word));
foreach(var match in matches)
{
Console.WriteLine("Index: {0}", match.Index);
}
DEMO
Index: 4
Index: 7
Index: 8
The IndexOf approach would be:
var partialMatches = words.Where(w => w.Word.IndexOf("load", StringComparison.OrdinalIgnoreCase) != -1);
foreach (var partialMatch in partialMatches)
{
Console.WriteLine("Index: {0}", partialMatch.Index);
}
DEMO
Index: 0
Index: 4
Index: 7
Index: 8
Index: 9

Here's an extension method that does the job:
public static class Extensions
{
public static IEnumerable<int> IndecesOf(this string text, string pattern)
{
var items = text.Split(' ');
for(int i = 0; i < items.Count(); i++)
if(items[i].ToLower().Contains(pattern));
yield return i + 1;
}
}
And the usage:
var fullText = "**LOAD SUBCASE1 SUBTITLE2 LOAD SUBCASE3 SUBTITLE4 load Load Load**";
foreach(int i in fullText.IndecesOf("load"))
Console.WriteLine(i);
Output:
1 4 7 8 9
Please note that I removed the double space from your example-string, when using a double space the split will add an empty string to the array.

Related

String manipulation in alternating order

I have a string
string value = "123456789";
now I need to re-arrange the string in the following way:
123456789
1 right
12 left
312 right
3124 left
53124 right
...
975312468 result
Is there a fancy linq one liner solution to solve this?
My current (working but not so good looking) solution:
string items = "abcdefgij";
string result = string.Empty;
for (int i = 0; i < items.Length; i++)
{
if (i % 2 != 0)
{
result = result + items[i];
}
else
{
result = items[i] + result;
}
}
string value = "123456789";
bool b = true;
string result = value.Aggregate(string.Empty, (s, c) =>
{
b = !b;
return b ? (s + c) : (c + s);
});
I actually don't like local variables inside LINQ statements, but in this case b helps alternating the direction. (#klappvisor showed how to live without b).
You can use length of the res as variable to decide from which side to append
items.Aggregate(string.Empty, (res, c) => res.Length % 2 == 0 ? c + res : res + c);
Alternative solution would be zipping with range
items.Zip(Enumerable.Range(0, items.Length), (c, i) => new {C = c, I = i})
.Aggregate(string.Empty, (res, x) => x.I % 2 == 0 ? x.C + res : res + x.C)
EDIT: don't really needed ToCharArray...
Resulting string is chars in evens positions concatenated to chars in odds positions in reverse order:
string value = "123456789";
var evens = value.Where((c, i) => i % 2 == 1);
var odds = value.Where((c, i) => i % 2 == 0).Reverse();
var chars = odds.Concat(evens).ToArray();
var result = new string(chars);

Can I store LINQ query result in an array?

This is the code using LINQ, it stores the values into list
string StringRegex = "\"(?:[^\"\\\\]|\\\\.)*\"";
Dictionary<string, string> dictionaryofString = new Dictionary<string, string>()
{
{"String", StringRegex}
};
var matches = dictionaryofString.SelectMany(a => Regex.Matches(input,a.Value)
.Cast<Match>()
.Select(b =>
new
{
Index = b.Index,
Value = b.Value,
Token = a.Key
}))
.OrderBy(a => a.Index).ToList();
for (int i = 0; i < matches.Count; i++)
{
if (i + 1 < matches.Count)
{
int firstEndPos = (matches[i].Index + matches[i].Value.Length);
if (firstEndPos > matches[(i + 1)].Index)
{
matches.RemoveAt(i + 1);
i--;
}
}
}
foreach (var match in matches)
{
Console.WriteLine(match);
}
Can it not be stored into Array? Where I can display only item I want. Just like here the output is {Index=, Value=, Token=}
Meanwhile I want the output that be of just "Value" "Token" index not needed.
You can use ToArray instead. But List gives you the desired array functionality already, you can access an item by its index.
var exampleQuery = select c from componentsDemo;
var list = exampleQuery.ToList();
var secondElement = list[1]; // <- demo only, there could be an exception thrown, if there's less than two elements in the list
EDIT: as I can see from your comments, you need this:
foreach (var match in matches)
{
Console.WriteLine(match.Value + ", " + match.Token);
}

how to arrange the item of a list into series arrangement

I have a list of data which contains of random data with combination of string and number:
List<String> Data1 = new List<String>()
{
"1001A",
"1002A",
"1003A",
"1004A",
"1015A",
"1016A",
"1007A",
"1008A",
"1009A",
};
I want this data to arrange into series like this:
1001A - 1004A, 1007A - 1009A, 1015A, 1016A
for every more than 2 counts of data series the output shall be have "-" between the first count and the last count of series, the other non series data will be just added to the last part and all together will separated by ",".
I'd already made some codes only to arrange the data series by the last char of it:
string get_REVISIONMARK = "A";
var raw_serries = arrange_REVISIONSERIES.Where(p => p[p.Length - 1].ToString() == get_REVISIONMARK) .OrderBy(p => p[p.Length - 1) .ThenBy(p => p.Substring(0, p.Length - 1)).ToList();
just ignore the last char I'd already have function for that, and my problem only about the arrangement of the numbers, the length of data is not fixed. for other example of output "1001A - 1005A, 301A, 32A"
I had another sample of my codes this works fine to me, but for me its so lazy code.
for (int c1 = 0; c1 < list_num.Count; c1++)
{
if (list_num[c1] != 0)
{
check1 = list_num[c1];
for (int c2 = 0; c2 < list_num.Count; c2++)
{
if (check1 == list_num[c2])
{
list_num[c2] = 0;
check1 += 1;
list_series.Add(arrange_REVISIONSERIES[c2]);
}
}
check1 = 0;
if (list_series.Count > 2)
{
res_series.Add(list_series[0] + " to " +list_series[list_series.Count - 1]);
list_series.Clear();
}
else
{
if (list_series.Count == 1)
{
res_series.Add(list_series[0]);
list_series.Clear();
}
else
{
res_series.Add(list_series[0] + "," + list_series[1]);
list_series.Clear();
}
}
}
}
var combine_res = String.Join(",", res_series);
MessageBox.Show(combine_res);
this codes work fine for the series number ...
A possible solution (working with current set of values), Please follow the steps below
Declare a class level string list as
public List<String> data_result = new List<string>();
Create a function to iterate through input string list (input string declared inside, named 'data')
public void ArrangeList()
{
List<String> data = new List<string>() { "1001A", "1002A", "1003A",
"1004A", "1015A", "1016A", "1007A", "1008A", "1009A", "1017A" };
List<int> data_int = data.Select(a => Convert.ToInt32(a.Substring(0,
a.Length - 1))).OrderBy(b => b).ToList();
int initializer = 0, counter = 0;
int finalizer = 0;
foreach (var item in data_int)
{
if (initializer == 0)
{ initializer = item; continue; }
else
{
counter++;
if (item == initializer + counter)
finalizer = item;
else
{
LogListing(initializer, finalizer);
initializer = item;
finalizer = item;
counter = 0;
}
}
}
LogListing(initializer, finalizer);
}
Create a function which just logs the result into data_result string list.
public void LogListing(int initializer, int finalizer)
{
if (initializer != finalizer)
{
if (finalizer == initializer + 1)
{
data_result.Add(initializer + "A");
data_result.Add(finalizer + "A");
}
else
data_result.Add(initializer + "A - " + finalizer + "A");
}
else
data_result.Add(initializer + "A");
}
It perfectly generates the result list as
Thumb-up if you like
A linqy solution:
char get_REVISIONMARK = 'A';
var res = arrange_REVISIONSERIES.Select(s => new { Rev = s[s.Length - 1], Value = int.Parse(s.Substring(0, s.Length - 1)), Org = s })
.Where(d => d.Rev == get_REVISIONMARK).OrderBy(d => d.Value)
.Select((val, ind) => new { Index = ind, Org = val.Org, Value = val.Value }).GroupBy(a => a.Value - a.Index)
.Select(gr=>gr.ToList()).OrderBy(l=>l.Count > 2 ? 0 : 1 ).Aggregate(new List<string>(), (list, sublist) =>
{
if (sublist.Count > 2)
list.Add(sublist[0].Org + " - " + sublist[sublist.Count - 1].Org);
else
list.AddRange(sublist.Select(a => a.Org));
return list;
});
The first lines are basically the same as the code you already have (filter on revision and sort), but with the difference that the subvalues are stored in an anonymous type. You could do the same on the pre ordered list, but since splitting the string would be done twice I've included it in the total.
Then a select with index (.Select((val, ind) =>) is made to get value/index pairs. This is done to be able to get the sequences based on an old t-sql row_number trick: for each 'group' the difference between value and index is the same .GroupBy(a => a.Value - a.Index)
After that, normally you'd be as good as done, but since you only want to make sequences of 2 and longer, we make sublists out of the groupby values and do the ordering beforehand to make sure the ranges come for the eventual single elements .Select(gr=>gr.ToList()).OrderBy(l=>l.Count > 2 ? 0 : 1 )
Finally, the list is created of the groups. Several options, but I like to use Aggregate for that. The seed is the resulting list, and the aggregate simply adds to that (where subranges > 2 are cummulated and for single elements and pairs, the single elements are added)
I'm making two assumptions:
The list is already ordered
The non-numeric characters can be ignored
You will get the results in the results variable:
void Main()
{
List<String> Data1 = new List<String>()
{
"1001A",
"1002A",
"1003A",
"1004A",
"1015A",
"1016A",
"1007A",
"1008A",
"1009A",
};
var accu = new List<List<Tuple<int, string>>>();
foreach (var data in Data1)
{
if (accu.Any(t => t.Any(d => d.Item1 == (ToInt(data) - 1))))
{
var item = accu.First(t => t.Any(d => d.Item1 == (ToInt(data) - 1)));
item.Add(new Tuple<int, string>(ToInt(data), data));
}
else
{
accu.Add(new List<Tuple<int, string>>{ new Tuple <int, string>(ToInt(data), data)});
}
}
var results = new List<string>();
results.AddRange(accu.Where(g => g.Count > 2).Select(g => string.Format("{0} - {1}", g.First().Item2, g.Last().Item2)));
results.AddRange(accu.Where(g => g.Count <= 2).Aggregate(new List<string>(), (total, current) => { total.AddRange(current.Select(i => i.Item2)); return total; } ));
}
private static Regex digitsOnly = new Regex(#"[^\d]");
public static int ToInt(string literal)
{
int i;
int.TryParse(digitsOnly.Replace(literal, ""), out i);
return i;
}
So given your starting data:
List<String> arrange_REVISIONSERIES = new List<String>()
{
"1001A",
"1002A",
"1003A",
"1004A",
"1015A",
"1016A",
"1007A",
"1008A",
"1009A",
};
I do this first:
var splits =
arrange_REVISIONSERIES
.Select(datum => new
{
value = int.Parse(datum.Substring(0, datum.Length - 1)),
suffix = datum.Substring(datum.Length - 1, 1),
})
.OrderBy(split => split.suffix)
.ThenBy(split => split.value)
.ToArray();
That's basically the same as your raw_serries, but orders the number part as a number. It seems to me that you need it as a number to make the range part work.
I then do this to compute the groupings:
var results =
splits
.Skip(1)
.Aggregate(
new[]
{
new
{
start = splits[0].value,
end = splits[0].value,
suffix = splits[0].suffix
}
}.ToList(),
(a, s) =>
{
if (a.Last().suffix == s.suffix && a.Last().end + 1 == s.value)
{
a[a.Count - 1] = new
{
start = a.Last().start,
end = s.value,
suffix = s.suffix
};
}
else
{
a.Add(new
{
start = s.value,
end = s.value,
suffix = s.suffix
});
}
return a;
})
.Select(r => r.start == r.end
? String.Format("{0}{1}", r.end, r.suffix)
: (r.start + 1 == r.end
? String.Format("{0}{2}, {1}{2}", r.start, r.end, r.suffix)
: String.Format("{0}{2} - {1}{2}", r.start, r.end, r.suffix)))
.ToArray();
And finally, this to create a single string:
var result = String.Join(", ", results);
That gives me:
1001A - 1004A, 1007A - 1009A, 1015A, 1016A
This code nicely works with data containing different suffixes.

How to modify linq index number

How do I modify the index number from an array to have a preceding 0 for number 1 - 9.
However, I would like numbers 10 on up to remain the same.
This is the raw data from debugging when getting my data from the tb1.text
"1ABC\r\n2ABC\r\3ABC\r\4ABC\r\n5ABC"
This is how I would like to store the data in my localDB.
"01ABC\r\n02ABC\r\03ABC\r\04ABC\r\n...10ABC"
Here is what I have so far.
var lines = tb1.Text.Split('\n').Select((line, index) => "YRZ"+(index + 01) + line).ToArray();
var res = string.Join("\n", lines);
Since the indexes are already part of the data entered, you need to either read it from there (and use that index) or remove it from there (and use the index you can get while Selecting). You can parse it using a regular expression. Once you have the index isolated, you can use .ToString("00") to add a leading zero.
var regex = new Regex(#"^(\d+)(.*)$");
var result = string.Join("\r\n",
tb1.Text.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x =>
{
var m = regex.Match(x);
return int.Parse(m.Groups[1].Value).ToString("00") + m.Groups[2].Value;
}));
Debug.Assert("01ABC\r\n02ABC\r\n03ABC\r\n04ABC\r\n10ABC" == result);
If you only want 0 in the string why not updating it as a string:
var text = "1ABC\r\n2ABC\r\n3ABC\r\n4ABC\r\n5ABC";
var lines = text.Split('\n').ToList();
var withZero = lines.Select(
(line, i) =>
{
var newVal = i < 9 ? string.Format("0{0}", line) : line;
return newVal;
});
var result = string.Join("\n", withZero);
Or in a more concise form:
var result = string.Join("\n", text.Split('\n').Select(
(line, i) =>
{
var newVal = i < 9 ? string.Format("0{0}", line) : line;
return newVal;
}));

GroupBy in LINQ and set property

I have such class:
public class Foo
{
public string Regn{get;set;}
public string DocName{get;set;}
...
}
In the my application this class uses with IEnumerable:
IEnumerable<Foo> items;
How to get new IEnumerable, where for all items with the same Regn and DocName property DocName sets like this(only if objects with same DocName >1):
item.DocName=item.DocName+".1";//+"2",etc.
[UPDATE]
Input sample:
Regn DocName
1 1
1 2
1 2
2 5
2 5
2 6
Output:
Regn DocName
1 1
1 2.1
1 2.2
2 5.1
2 5.2
2 6
If you have a default constructor for Foo try to use this:
var newItems = items.
GroupBy(f => Tuple.Create(f.Regn, f.DocName)).
SelectMany(gr => gr.Count()<=1 ? gr : gr.Select((f, i) => new Foo
{
Regn = f.Regn,
DocName = f.DocName + "." + (i + 1)
}));
You can group with LINQ and cast out groups that only have one item, then iterate over the items in each group to set the DocName:
// Group and filter
var groups = items.GroupBy(i => new { i.Regn, i.DocName })
.Where(g => g.Count() > 1);
// Iterate over each group with many items
foreach (var g in groups) {
var itemsInGroup = g.ToArray();
// Iterate over the items and set DocName
for (var i = 0; i < itemsInGroup.Length; ++i) {
itemsInGroup[i].DocName = g.Key + "." + (i + 1);
}
}
All in one statement, just for fun.
var query = items.GroupBy(i => new { i.DocName, i.Regn })
.SelectMany(group =>
{
int itemNum = 0;
return group.Select(item =>
{
var suffix = itemNum > 0 ? ("." + itemNum) : "";
var newDocName = item.DocName + suffix;
itemNum++;
return new { item, NewDocName = newDocName };
});
});
Or use a LINQ statement to create a new result set like:
var fixedSet = from entry in existing
group entry by entry.DocName + entry.Regn into groupedEntries
let groupedEntriesAsArray = groupedEntries.ToArray()
from groupedEntry in groupedEntriesAsArray
let index = Array.IndexOf(groupedEntriesAsArray, groupedEntry)
select new Foo
{
DocName =
string.Format("{0}.{1}", groupedEntry.DocName, index + 1),
Regn =
string.Format("{0}.{1}", groupedEntry.Regn, index + 1)
};

Categories

Resources