C# String Search with multiple substrings
So I'm working on a filter for a resource list and one of the filters is the Name property(string).
As a(dumb) example : resource name is "Big,Red/Square Table" and the filter is "Table Red", it should be a valid resource
This is what I could come up with using the little time I had:
static void ApplyNameFilter(ref ApplicationViewModel model, string filter)
{
if (string.IsNullOrEmpty(filter) || filter == "") return;
char separators = {' ', ',', '.', '/', '\', '|', '_', '-'};
var validResources = new List<ResourceModel>();
foreach (var resource in model.ResourcesViewModel.Resources)
{
var filterSubstrings =
filter
.ToLower()
.Split(separators)
.ToList();
var resourceSubstrings =
resource.Name
.ToLower()
.Split(separators)
.ToList();
resourceSubstrings.ForEach(substring => {
if (filterSubstrings.Contains(substring))
filterSubstrings.RemoveAll(sub => sub == substring);
});
if (filterSubstrings.Count == 0)
validResources.Add(resource);
}
model.ResourcesViewModel.Resources = validResources;
}
Should I take a different approach for this?
EDIT:
Ended up going with this until I figure out RegEx
static void ApplyNameFilter(ref ApplicationViewModel model, string filter)
{
if (string.IsNullOrEmpty(filter)) return;
char separators = {' ', ',', '.', '/', '\', '|', '_', '-'};
var filterSubstrings =
filter
.ToLower()
.Split(separators)
.ToList();
var validResources = model.ResourcesViewModel.Resources
.Where(resource => filterSubstrings.All(fs => resource.Name.ToLower().Contains(fs)))
.ToList();
model.ResourcesViewModel.Resources = validResources;
}
c# string search
|
show 2 more comments
So I'm working on a filter for a resource list and one of the filters is the Name property(string).
As a(dumb) example : resource name is "Big,Red/Square Table" and the filter is "Table Red", it should be a valid resource
This is what I could come up with using the little time I had:
static void ApplyNameFilter(ref ApplicationViewModel model, string filter)
{
if (string.IsNullOrEmpty(filter) || filter == "") return;
char separators = {' ', ',', '.', '/', '\', '|', '_', '-'};
var validResources = new List<ResourceModel>();
foreach (var resource in model.ResourcesViewModel.Resources)
{
var filterSubstrings =
filter
.ToLower()
.Split(separators)
.ToList();
var resourceSubstrings =
resource.Name
.ToLower()
.Split(separators)
.ToList();
resourceSubstrings.ForEach(substring => {
if (filterSubstrings.Contains(substring))
filterSubstrings.RemoveAll(sub => sub == substring);
});
if (filterSubstrings.Count == 0)
validResources.Add(resource);
}
model.ResourcesViewModel.Resources = validResources;
}
Should I take a different approach for this?
EDIT:
Ended up going with this until I figure out RegEx
static void ApplyNameFilter(ref ApplicationViewModel model, string filter)
{
if (string.IsNullOrEmpty(filter)) return;
char separators = {' ', ',', '.', '/', '\', '|', '_', '-'};
var filterSubstrings =
filter
.ToLower()
.Split(separators)
.ToList();
var validResources = model.ResourcesViewModel.Resources
.Where(resource => filterSubstrings.All(fs => resource.Name.ToLower().Contains(fs)))
.ToList();
model.ResourcesViewModel.Resources = validResources;
}
c# string search
3
Yeah. Definitely look towards aregex
solution - you'll definitely have more concise code. There are other ways to skin this cat, but you'd have to be digging back to your comp sci 101 past and thinking about dynamic programming (at least that's what's coming to my mind, LOL). Single responsibility principle based approach also might make your life a lesser hell, here, as well. In both theregex
and non-regex approaches, try to leverage an immutability based approach. You're going to really appreciate that you didn't change things in-place, later on.
– code4life
Nov 16 '18 at 18:49
I haven't used too much RegEx though, I know it's damn powerful. Is there a way to generate proper regex patterns based on ones input?
– Xeyth
Nov 16 '18 at 18:56
1
@Xeyth, To add on code4life excellent comment. I will also clarify, about specific point, if the method you are working with, only need the resource list, you do not want to let the method "know" all of your object and his hierarchy. some of the reasons for that is: in case the hierarchy will change, or you will want to use this logic in some other part of your application. you don't need to pass things that the method doesn't care, nor should you.
– Orel Eraki
Nov 16 '18 at 18:57
@Xeyth: absolutely. If you're using .NET 4.5 or higher, it gets even easier since you get to leverage string interpolation. For instance,$"{your variable here}regex expression here"
- that's just a very raw, off-the-cuff example. I strongly suggest googling regex, you are not going to need a very complex regex, for what you are trying to do.
– code4life
Nov 16 '18 at 18:59
1
@Xeyth, If you only wants to "beautify" your method, you can work with.Intersect
or.All
, which is immutable and also, almost gives you the solution. e.g:filterSubstrings.Intersect(resourceSubstrings).Count() == filterSubstrings.Count
ORfilterSubstrings.All(fs => resourceSubstrings.Contains(fs))
. Also you don't need to compute thefilterSubstring
in every iteration, they do not change. so put them out of your loop.
– Orel Eraki
Nov 16 '18 at 19:05
|
show 2 more comments
So I'm working on a filter for a resource list and one of the filters is the Name property(string).
As a(dumb) example : resource name is "Big,Red/Square Table" and the filter is "Table Red", it should be a valid resource
This is what I could come up with using the little time I had:
static void ApplyNameFilter(ref ApplicationViewModel model, string filter)
{
if (string.IsNullOrEmpty(filter) || filter == "") return;
char separators = {' ', ',', '.', '/', '\', '|', '_', '-'};
var validResources = new List<ResourceModel>();
foreach (var resource in model.ResourcesViewModel.Resources)
{
var filterSubstrings =
filter
.ToLower()
.Split(separators)
.ToList();
var resourceSubstrings =
resource.Name
.ToLower()
.Split(separators)
.ToList();
resourceSubstrings.ForEach(substring => {
if (filterSubstrings.Contains(substring))
filterSubstrings.RemoveAll(sub => sub == substring);
});
if (filterSubstrings.Count == 0)
validResources.Add(resource);
}
model.ResourcesViewModel.Resources = validResources;
}
Should I take a different approach for this?
EDIT:
Ended up going with this until I figure out RegEx
static void ApplyNameFilter(ref ApplicationViewModel model, string filter)
{
if (string.IsNullOrEmpty(filter)) return;
char separators = {' ', ',', '.', '/', '\', '|', '_', '-'};
var filterSubstrings =
filter
.ToLower()
.Split(separators)
.ToList();
var validResources = model.ResourcesViewModel.Resources
.Where(resource => filterSubstrings.All(fs => resource.Name.ToLower().Contains(fs)))
.ToList();
model.ResourcesViewModel.Resources = validResources;
}
c# string search
So I'm working on a filter for a resource list and one of the filters is the Name property(string).
As a(dumb) example : resource name is "Big,Red/Square Table" and the filter is "Table Red", it should be a valid resource
This is what I could come up with using the little time I had:
static void ApplyNameFilter(ref ApplicationViewModel model, string filter)
{
if (string.IsNullOrEmpty(filter) || filter == "") return;
char separators = {' ', ',', '.', '/', '\', '|', '_', '-'};
var validResources = new List<ResourceModel>();
foreach (var resource in model.ResourcesViewModel.Resources)
{
var filterSubstrings =
filter
.ToLower()
.Split(separators)
.ToList();
var resourceSubstrings =
resource.Name
.ToLower()
.Split(separators)
.ToList();
resourceSubstrings.ForEach(substring => {
if (filterSubstrings.Contains(substring))
filterSubstrings.RemoveAll(sub => sub == substring);
});
if (filterSubstrings.Count == 0)
validResources.Add(resource);
}
model.ResourcesViewModel.Resources = validResources;
}
Should I take a different approach for this?
EDIT:
Ended up going with this until I figure out RegEx
static void ApplyNameFilter(ref ApplicationViewModel model, string filter)
{
if (string.IsNullOrEmpty(filter)) return;
char separators = {' ', ',', '.', '/', '\', '|', '_', '-'};
var filterSubstrings =
filter
.ToLower()
.Split(separators)
.ToList();
var validResources = model.ResourcesViewModel.Resources
.Where(resource => filterSubstrings.All(fs => resource.Name.ToLower().Contains(fs)))
.ToList();
model.ResourcesViewModel.Resources = validResources;
}
c# string search
c# string search
edited Nov 20 '18 at 9:12
Xeyth
asked Nov 16 '18 at 18:41
XeythXeyth
606
606
3
Yeah. Definitely look towards aregex
solution - you'll definitely have more concise code. There are other ways to skin this cat, but you'd have to be digging back to your comp sci 101 past and thinking about dynamic programming (at least that's what's coming to my mind, LOL). Single responsibility principle based approach also might make your life a lesser hell, here, as well. In both theregex
and non-regex approaches, try to leverage an immutability based approach. You're going to really appreciate that you didn't change things in-place, later on.
– code4life
Nov 16 '18 at 18:49
I haven't used too much RegEx though, I know it's damn powerful. Is there a way to generate proper regex patterns based on ones input?
– Xeyth
Nov 16 '18 at 18:56
1
@Xeyth, To add on code4life excellent comment. I will also clarify, about specific point, if the method you are working with, only need the resource list, you do not want to let the method "know" all of your object and his hierarchy. some of the reasons for that is: in case the hierarchy will change, or you will want to use this logic in some other part of your application. you don't need to pass things that the method doesn't care, nor should you.
– Orel Eraki
Nov 16 '18 at 18:57
@Xeyth: absolutely. If you're using .NET 4.5 or higher, it gets even easier since you get to leverage string interpolation. For instance,$"{your variable here}regex expression here"
- that's just a very raw, off-the-cuff example. I strongly suggest googling regex, you are not going to need a very complex regex, for what you are trying to do.
– code4life
Nov 16 '18 at 18:59
1
@Xeyth, If you only wants to "beautify" your method, you can work with.Intersect
or.All
, which is immutable and also, almost gives you the solution. e.g:filterSubstrings.Intersect(resourceSubstrings).Count() == filterSubstrings.Count
ORfilterSubstrings.All(fs => resourceSubstrings.Contains(fs))
. Also you don't need to compute thefilterSubstring
in every iteration, they do not change. so put them out of your loop.
– Orel Eraki
Nov 16 '18 at 19:05
|
show 2 more comments
3
Yeah. Definitely look towards aregex
solution - you'll definitely have more concise code. There are other ways to skin this cat, but you'd have to be digging back to your comp sci 101 past and thinking about dynamic programming (at least that's what's coming to my mind, LOL). Single responsibility principle based approach also might make your life a lesser hell, here, as well. In both theregex
and non-regex approaches, try to leverage an immutability based approach. You're going to really appreciate that you didn't change things in-place, later on.
– code4life
Nov 16 '18 at 18:49
I haven't used too much RegEx though, I know it's damn powerful. Is there a way to generate proper regex patterns based on ones input?
– Xeyth
Nov 16 '18 at 18:56
1
@Xeyth, To add on code4life excellent comment. I will also clarify, about specific point, if the method you are working with, only need the resource list, you do not want to let the method "know" all of your object and his hierarchy. some of the reasons for that is: in case the hierarchy will change, or you will want to use this logic in some other part of your application. you don't need to pass things that the method doesn't care, nor should you.
– Orel Eraki
Nov 16 '18 at 18:57
@Xeyth: absolutely. If you're using .NET 4.5 or higher, it gets even easier since you get to leverage string interpolation. For instance,$"{your variable here}regex expression here"
- that's just a very raw, off-the-cuff example. I strongly suggest googling regex, you are not going to need a very complex regex, for what you are trying to do.
– code4life
Nov 16 '18 at 18:59
1
@Xeyth, If you only wants to "beautify" your method, you can work with.Intersect
or.All
, which is immutable and also, almost gives you the solution. e.g:filterSubstrings.Intersect(resourceSubstrings).Count() == filterSubstrings.Count
ORfilterSubstrings.All(fs => resourceSubstrings.Contains(fs))
. Also you don't need to compute thefilterSubstring
in every iteration, they do not change. so put them out of your loop.
– Orel Eraki
Nov 16 '18 at 19:05
3
3
Yeah. Definitely look towards a
regex
solution - you'll definitely have more concise code. There are other ways to skin this cat, but you'd have to be digging back to your comp sci 101 past and thinking about dynamic programming (at least that's what's coming to my mind, LOL). Single responsibility principle based approach also might make your life a lesser hell, here, as well. In both the regex
and non-regex approaches, try to leverage an immutability based approach. You're going to really appreciate that you didn't change things in-place, later on.– code4life
Nov 16 '18 at 18:49
Yeah. Definitely look towards a
regex
solution - you'll definitely have more concise code. There are other ways to skin this cat, but you'd have to be digging back to your comp sci 101 past and thinking about dynamic programming (at least that's what's coming to my mind, LOL). Single responsibility principle based approach also might make your life a lesser hell, here, as well. In both the regex
and non-regex approaches, try to leverage an immutability based approach. You're going to really appreciate that you didn't change things in-place, later on.– code4life
Nov 16 '18 at 18:49
I haven't used too much RegEx though, I know it's damn powerful. Is there a way to generate proper regex patterns based on ones input?
– Xeyth
Nov 16 '18 at 18:56
I haven't used too much RegEx though, I know it's damn powerful. Is there a way to generate proper regex patterns based on ones input?
– Xeyth
Nov 16 '18 at 18:56
1
1
@Xeyth, To add on code4life excellent comment. I will also clarify, about specific point, if the method you are working with, only need the resource list, you do not want to let the method "know" all of your object and his hierarchy. some of the reasons for that is: in case the hierarchy will change, or you will want to use this logic in some other part of your application. you don't need to pass things that the method doesn't care, nor should you.
– Orel Eraki
Nov 16 '18 at 18:57
@Xeyth, To add on code4life excellent comment. I will also clarify, about specific point, if the method you are working with, only need the resource list, you do not want to let the method "know" all of your object and his hierarchy. some of the reasons for that is: in case the hierarchy will change, or you will want to use this logic in some other part of your application. you don't need to pass things that the method doesn't care, nor should you.
– Orel Eraki
Nov 16 '18 at 18:57
@Xeyth: absolutely. If you're using .NET 4.5 or higher, it gets even easier since you get to leverage string interpolation. For instance,
$"{your variable here}regex expression here"
- that's just a very raw, off-the-cuff example. I strongly suggest googling regex, you are not going to need a very complex regex, for what you are trying to do.– code4life
Nov 16 '18 at 18:59
@Xeyth: absolutely. If you're using .NET 4.5 or higher, it gets even easier since you get to leverage string interpolation. For instance,
$"{your variable here}regex expression here"
- that's just a very raw, off-the-cuff example. I strongly suggest googling regex, you are not going to need a very complex regex, for what you are trying to do.– code4life
Nov 16 '18 at 18:59
1
1
@Xeyth, If you only wants to "beautify" your method, you can work with
.Intersect
or .All
, which is immutable and also, almost gives you the solution. e.g: filterSubstrings.Intersect(resourceSubstrings).Count() == filterSubstrings.Count
OR filterSubstrings.All(fs => resourceSubstrings.Contains(fs))
. Also you don't need to compute the filterSubstring
in every iteration, they do not change. so put them out of your loop.– Orel Eraki
Nov 16 '18 at 19:05
@Xeyth, If you only wants to "beautify" your method, you can work with
.Intersect
or .All
, which is immutable and also, almost gives you the solution. e.g: filterSubstrings.Intersect(resourceSubstrings).Count() == filterSubstrings.Count
OR filterSubstrings.All(fs => resourceSubstrings.Contains(fs))
. Also you don't need to compute the filterSubstring
in every iteration, they do not change. so put them out of your loop.– Orel Eraki
Nov 16 '18 at 19:05
|
show 2 more comments
1 Answer
1
active
oldest
votes
You can use LINQ to make this more concise (and probably faster, as you are creating List
s and removing elements over every resource unnecessarily, though a non-LINQ solution could be even faster).
var validResources = model.ResourcesViewModel.Resources
.Where(resource => {
var resourceSubstrings = resource.Name.ToLower().Split(separators).ToHashSet();
return filterSubstrings.All(fs => resourceSubstrings.Contains(fs));
})
.ToList();
If you are willing to accept all the filter substrings being inside the Name
regardless of the separators
, then you can simplify to just be:
var validResources = model.ResourcesViewModel.Resources
.Where(resource => filterSubstrings.All(fs => resource.Name.ToLower().Contains(fs)))
.ToList();
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53343639%2fc-sharp-string-search-with-multiple-substrings%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
You can use LINQ to make this more concise (and probably faster, as you are creating List
s and removing elements over every resource unnecessarily, though a non-LINQ solution could be even faster).
var validResources = model.ResourcesViewModel.Resources
.Where(resource => {
var resourceSubstrings = resource.Name.ToLower().Split(separators).ToHashSet();
return filterSubstrings.All(fs => resourceSubstrings.Contains(fs));
})
.ToList();
If you are willing to accept all the filter substrings being inside the Name
regardless of the separators
, then you can simplify to just be:
var validResources = model.ResourcesViewModel.Resources
.Where(resource => filterSubstrings.All(fs => resource.Name.ToLower().Contains(fs)))
.ToList();
add a comment |
You can use LINQ to make this more concise (and probably faster, as you are creating List
s and removing elements over every resource unnecessarily, though a non-LINQ solution could be even faster).
var validResources = model.ResourcesViewModel.Resources
.Where(resource => {
var resourceSubstrings = resource.Name.ToLower().Split(separators).ToHashSet();
return filterSubstrings.All(fs => resourceSubstrings.Contains(fs));
})
.ToList();
If you are willing to accept all the filter substrings being inside the Name
regardless of the separators
, then you can simplify to just be:
var validResources = model.ResourcesViewModel.Resources
.Where(resource => filterSubstrings.All(fs => resource.Name.ToLower().Contains(fs)))
.ToList();
add a comment |
You can use LINQ to make this more concise (and probably faster, as you are creating List
s and removing elements over every resource unnecessarily, though a non-LINQ solution could be even faster).
var validResources = model.ResourcesViewModel.Resources
.Where(resource => {
var resourceSubstrings = resource.Name.ToLower().Split(separators).ToHashSet();
return filterSubstrings.All(fs => resourceSubstrings.Contains(fs));
})
.ToList();
If you are willing to accept all the filter substrings being inside the Name
regardless of the separators
, then you can simplify to just be:
var validResources = model.ResourcesViewModel.Resources
.Where(resource => filterSubstrings.All(fs => resource.Name.ToLower().Contains(fs)))
.ToList();
You can use LINQ to make this more concise (and probably faster, as you are creating List
s and removing elements over every resource unnecessarily, though a non-LINQ solution could be even faster).
var validResources = model.ResourcesViewModel.Resources
.Where(resource => {
var resourceSubstrings = resource.Name.ToLower().Split(separators).ToHashSet();
return filterSubstrings.All(fs => resourceSubstrings.Contains(fs));
})
.ToList();
If you are willing to accept all the filter substrings being inside the Name
regardless of the separators
, then you can simplify to just be:
var validResources = model.ResourcesViewModel.Resources
.Where(resource => filterSubstrings.All(fs => resource.Name.ToLower().Contains(fs)))
.ToList();
answered Nov 16 '18 at 20:32
NetMageNetMage
13.3k11935
13.3k11935
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53343639%2fc-sharp-string-search-with-multiple-substrings%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
3
Yeah. Definitely look towards a
regex
solution - you'll definitely have more concise code. There are other ways to skin this cat, but you'd have to be digging back to your comp sci 101 past and thinking about dynamic programming (at least that's what's coming to my mind, LOL). Single responsibility principle based approach also might make your life a lesser hell, here, as well. In both theregex
and non-regex approaches, try to leverage an immutability based approach. You're going to really appreciate that you didn't change things in-place, later on.– code4life
Nov 16 '18 at 18:49
I haven't used too much RegEx though, I know it's damn powerful. Is there a way to generate proper regex patterns based on ones input?
– Xeyth
Nov 16 '18 at 18:56
1
@Xeyth, To add on code4life excellent comment. I will also clarify, about specific point, if the method you are working with, only need the resource list, you do not want to let the method "know" all of your object and his hierarchy. some of the reasons for that is: in case the hierarchy will change, or you will want to use this logic in some other part of your application. you don't need to pass things that the method doesn't care, nor should you.
– Orel Eraki
Nov 16 '18 at 18:57
@Xeyth: absolutely. If you're using .NET 4.5 or higher, it gets even easier since you get to leverage string interpolation. For instance,
$"{your variable here}regex expression here"
- that's just a very raw, off-the-cuff example. I strongly suggest googling regex, you are not going to need a very complex regex, for what you are trying to do.– code4life
Nov 16 '18 at 18:59
1
@Xeyth, If you only wants to "beautify" your method, you can work with
.Intersect
or.All
, which is immutable and also, almost gives you the solution. e.g:filterSubstrings.Intersect(resourceSubstrings).Count() == filterSubstrings.Count
ORfilterSubstrings.All(fs => resourceSubstrings.Contains(fs))
. Also you don't need to compute thefilterSubstring
in every iteration, they do not change. so put them out of your loop.– Orel Eraki
Nov 16 '18 at 19:05