I have 343 observations and am trying the following:
forvalues in 1/343 {
replace `ido'="BRA" if `v2'=="F_3idoXidd_2_*"
replace `ido'="AUS" if `v2'=="F_3idoXidd_3_*"
}
The * in F_3idoXidd_2_* is because I have 30 observations for each country, so I want to do all in one time.
You generally don't need a loop to replace values of variables. In general, loops in Stata are not used to iterate over observations, but rather lists of some sort (varlist, numlist, etc.).
Also, your use of the wildcard won't function as you expect. Wildcards would also typically be used when specifying a varlist (drop var*, sum gdp*, etc.).
What you can do here instead is use strpos to search for the specified string in the variable, and replace its value conditional on the result of strpos. Example:
/* Create sample data */
clear *
input str15 v2
"F_03idoXidd_3_3"
"F_03idoXidd_2_3"
"F_03idoXidd_3_2"
"F_03idoXidd_2_2"
end
expand 50
gen ido = ""
replace ido = "AUS" if strpos(v2,"F_03idoXidd_3_")
replace ido = "BRA" if strpos(v2,"F_03idoXidd_2_")
or, a one line solution:
replace ido = cond(strpos(v2,"F_03idoXidd_3_"), "AUS", cond(strpos(v2,"F_03idoXidd_2_"),"BRA",""))
strpos returns 0 if the specified string is not found, and the position which it is first found otherwise. Used following the if qualifier, it is evaluated as true if > 0, and false otherwise. In this case, you could search v2 for F_3idoXidd_3_ and replace with AUS.
Of course this is just one approach and might not be ideal if you have many replacement values.
EDIT
Based on the comments to this answer, OP needs to create a second variable conditional on the value of the last integer in the string.
One method to do this relies on substr and assumes the F_3idoXidd_ portion of the string does not change across observations in such a way that different values (for example, F_4idoXidd_3_2) would have a different meaning than F_3idoXidd_3_2.
gen idd = ""
replace idd = "AUS" if substr(v2, -2,.) == "_3"
replace idd = "BRA" if substr(v2, -2,.) == "_2"
or again, a one line solution using substr and cond:
gen idd = cond(substr(v2,-2,.) == "_3", "AUS", cond(substr(v2,-2,.) == "_2", "BRA",""))
Again, this is only one way which springs to mind quickly. You may also be interested in looking at regexm and any number of the functions documented at help string_functions
Related
I am writing a small package manager in ruby, and while working on its' package searching functionality, I want to continue iterating over a list of matches, even if it has found a package or sting identical to the inputted string.
def makelist(jsn, searchterm)
len = jsn.length
n = 0
while n < len do
pkname = jsn[n]["Name"]
pkdesc = jsn[n]["Description"]
pkver = jsn[n]["Version"]
unless pkname != nil || pkdesc != nil
# skip
else
puts "#{fmt(fmt("aur/", 6),0)}#{fmt(pkname,0)} [#{fmt(pkver,8)}]\n #{pkdesc}"
n += 1
end
end
end
I have tried using an if statement, unless statement and a case statement in which I gave conditions for what it should do if it specifically finds a packages that matches searchterm but when I use this condition, it always skips all other conditions and ends the loop, printing only that result. This block of code works, but I want to use my fmt function to format the text of matches differently in the list. Anyone have any ideas of how I could accomplish this?
EDIT:
Based on some back and forth the desired behavior is to print matched results differently from unmatched results.
So, in Ruby you have access to "functional" patterns like select, map, reduce, etc. that can be useful for what you're doing here.
It's also advantageous to separate functionality into different methods (e.g. one that searches, one that turns them into a string, one that prints the output). This is just an example of how to break it down, but splitting up the responsibilities makes it much easier to test and see which function isn't doing what you want by examining the intermediate structures.
Also, I'm not sure how you want to "match" the search term, so I used String#include? but you can replace that with whatever matching algorithm you like.
But I think you're looking for something like this:
def makelist(jsn, searchterm)
jsn.select do |package|
package["Name"] && package["Description"]
end.map do |package|
if matches?(package, searchterm)
matched_package_to_string(package)
else
package_to_string(package)
end
end.join("\n")
end
def matches?(package, searchterm)
package['Name'].include?(searchterm) || package('Description').include?(searchterm)
end
def matched_package_to_string(package)
pkname = package["Name"]
pkdesc = package["Description"]
pkver = package["Version"]
"#{fmt(fmt("aur/", 6),0)}#{fmt(pkname,0)} [#{fmt(pkver,8)}]\n #{pkdesc}"
end
def package_to_string(package)
# Not sure how these should print
end
Looking for a one liner code either in java or cfm, where i do not need to loop over te array of structs to use te structfind to get the value from it.
right now looking at it,
Coldfusion - How to loop through an Array of Structure and print out dynamically all KEY values?
where i can loop over and get the value of the key match
but trying to check if something like this can be done
<cfset myvalue = structfindvaluefromAnything(myarrayofstruct,"infor")>
I like Sev's approach. I would change it slightly
<cfscript>
superheroes=[
{"name":"Iron Man","member":"Avengers"},
{"name":"Spider-Man","member":"Avengers"},
{"name":"Wonder Woman","member":"Justice League"},
{"name":"Hulk","member":"Avengers"},
{"name":"Thor","member":"Avengers"},
{"name":"Aquaman","member":"Justice League"}
];
avengers = superheroes.filter(function(item) {
return item.member == "Avengers";
});
writeDump(avengers);
</cfscript>
If you really want to do it in one line then you could use ArrayFilter() in combination with StructFindValue().
Adapting from the Adobe docs for ArrayFilter - https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-functions/functions-a-b/arrayfilter.html - something like this:
<cfscript>
superheroes=[
{"name":"Iron Man","member":"Avengers"},
{"name":"Wonder Woman","member":"Justice League"},
{"name":"Hulk","member":"Avengers"},
{"name":"Thor","member":"Avengers"},
{"name":"Aquaman","member":"Justice League"}
];
avengers=ArrayFilter(superheroes,function(item){
return ArrayLen(StructFindValue( item, "Avengers"));
});
writeDump(var=avengers, label="all matches");
writeDump(var=ArrayLen(avengers) ? avengers[1] : "Not found", label="first match only");
writeDump(var=structFindValue({"a":superheroes}, "Avengers", "all"), label="without arrayFilter");
</cfscript>
I believe the function available for this nearly exactly what you were hoping for...
StructFindValue(struct, value [, scope])
Searches recursively through a substructure of nested arrays, structures, and other elements for structures with values that match the search key in the value parameter.
Returns an array that contains structures keys whose values match the search key value. If none are found, returns an array of size 0.
Based on the gist you provided above (https://cffiddle.org/app/file?filepath=3e26c1ac-d5db-482f-9bb2-995e6cabe704/49b3e106-8db9-4411-a6d4-10deb3f8cb0e/24e44eba-45ef-4744-a6e6-53395c09a344.cfm), I think you've clarified your expectations a little bit.
In your gist, you say you want to be able to search an array of structs and find the row that has a "name" key with a value of "form". Then, you want to take the value of the "value" key that's associated with that struct in the array row. If there is no value then return 0.
You wanted to be able to do this in a single line of code, and the above answers do accomplish that. My answer essentially builds on those.
As demonstrated in the earlier answers, you still want to use closure functions to filter down your final output. Those are very quick and essentially built to do what you're trying to do.
The Fiddle that I worked with is here: https://cffiddle.org/app/file?filepath=b3507f1d-6ac2-4900-baed-fb3faf5a3b3a/e526afc2-bb85-4aea-ad0e-dcf38f52b642/75d88d2b-f990-44c1-9d9f-22931bf9d4d7.cfm
I've done two things with this.
First, I worked it as if you expected to encounter multiple records for your filtering value, and then turn those into a comma-delimited list. If you need another structure, the reduce() function in my code can be modified to handle this.
Second, I worked it as if you expected to encounter only one filtered record, returning only a single value.
The first thing I did, which is mostly the same in both methods, and which is essentially the same as the previous answers, is to filter your original array for just the value you want.
This is done like this:
myResult = originalArray.filter(
function(itm){
return itm?.name=="form"; /// ?. = safe-navigation operator.
}
)
I've broken it to multiple lines for clarity.
This will return a new array of structs consisting of your filtered rows.
But then you want to take those records and return the "value" from those rows (defaulting to 0 if no value. You can do this with a reduce().
commaDelimitedValue =
myResult.reduce(
function(prev,nxt) {
return prev.listappend( ( nxt.value.len() ? nxt.value : 0 ) ) ;
}
, "" /// Initialization value
) ;
Again, this can be written in one row, but I've included line breaks for clarity.
The reduce() function essentially just reduces your input to a single value. It follows the format of .reduce( function( previousValue, nextValue ){ return .... },<initializationValue>), where, on the first iterations, the initializationValue is substituted for previousValue, then previousValue becomes the result of that iteration. nextValue is actually the current iteration that you will derive a result from.
More at: https://coldfusion.adobe.com/2017/10/map-reduce-and-filter-functions-in-coldfusion/
In my assumption here, you could possibly have multiple rows returned from your filter(). You take those rows and append the value to a commma-delimited list. So you would end up with a result like 20,10,0,0 - representing 4 rows in your filtered results.
I also check for a length of the value and default it to 0 if it's an empty string. Above, I said that you could just use an Elvis Operator (:?) on that, but that doesn't work for a simple value like an empty string. Elvis works with NULLs, which the earlier array did have.
To put this back to one line, you can chain both of these functions. So you end up with:
myFinalResult =
myOriginalArray.filter(
function(itm){
return itm?.name=="form";
}
)
.reduce(
function(prev,nxt) {
return prev.listappend( ( nxt.value.trim().len() ? nxt.value : 0 ) ) ;
}
, ""
)
;
Again, that code is doing a lot, but it is still essentially one line. The final result from that would again be something like "20,10,0,0" for 4 rows with 2 defaulted to 0.
If you only expect your filter to return a single row, or if you only want a single value, you can simplify that a little bit.
myFinalResult = myOriginalArray.filter( function(itm){ return itm?.name=="fm" && (itm?.value.trim().len()>0) ; } )[1]["value"] ?: 0 ;
With this, I am back to using my previous trick with Elvis to default a row with no value, since I am filtering out the "form" struct with an empty-string "value". && is the same as AND. Technically this CAN filter more than one row from the original array, but the [1] will only pick the first row from the filtered rows. It also doesn't need to use a reduce(). If there's more than one row filtered, each iteration will just overwrite the previous one.
This will return a simple, single value with something like 42 - which is the last filtered value in the array, since it overwrites the previous row's value.
My Fiddle (https://cffiddle.org/app/file?filepath=b3507f1d-6ac2-4900-baed-fb3faf5a3b3a/e526afc2-bb85-4aea-ad0e-dcf38f52b642/75d88d2b-f990-44c1-9d9f-22931bf9d4d7.cfm) has some additional comments, and I set up a couple of edge cases that demonstrate the filtering and safe-navigation.
I would also like to reiterate that if this is Lucee 5+ or ACF2018+, you can shorten this further with Arrow Functions.
I have the File as following format
Name Number Position
A 1
B 2
C 3
D 4
Now on position A3 , I applied =IF(B2=1,"Goal Keeper",OR(IF(B2=2,"Defender",OR(IF(B2=3,"MidField","Striker"))))) But it giving me an error #value!
Looked up at google, and my formula is correct.
What i basically want it
1- Goalkeeper 2-Defender 3-Midfield 4-Striker
Yes the other way is to to just filter the number and copy paste the text
But I want to do it using formula and want to know where did I go wrong.
Your immediate problem lies with the expression (for example):
OR(IF(B2=3,"MidField","Striker"))
| \__/ \________/ \_______/ |
| bool string string |
\____________________________/
string
The OR function expects a series of boolean values (true or false) and you're giving it a string value from the inner IF.
You don't actually need the or bits in this specific case, the if is a full if-else. So you can just use:
=IF(B1=1,"Goal Keeper",IF(B2=2,"Defender",IF(B2=3,"MidField","Striker")))
This means that B1=1 will result in "Goal Keeper", otherwise it will evaluate IF(B2=2,"Defender",IF(B2=3,"MidField","Striker")).
Then that means that, if B2=2, it will result in "Defender", otherwise it will evaluate IF(B2=3,"MidField","Striker").
Finally, that means the B2=3 will result in "MidField", anything else will give "Striker".
The only situation I can envisage when OR would come in handy here would be when two different numbers were to generate the same string. Let's say both 1 and 4 should give "Goalie", you could use:
=IF(OR(B1=1,B1=4),"Goalie",IF(B2=2,"Defender","MidField"))
Keep in mind that a more general solution would be better implemented with the Excel lookup functions, ones that would search a table (on the spreadsheet somewhere) which mapped the integers to strings. Then, if the mapping needed to change, you would just update the table rather than going back and changing the formula in every single row.
If you are actually tasked with solving the problem by using the IF and OR function within the same equation, this is the only way I can see how:
=IF(OR(B1=1, B1 = 2, B1 = 3, B1 = 4),IF(B1 = 1, "Goal Keeper", IF(B1 = 2,"Defender",IF(B1 = 3,"MidField","Striker")))
If B1 does not equal 1-4, the OR function will return FALSE and completely bypass all of the nested IF statements.
I'm uploading a spreadsheet and mapping the spreadsheet column headings to those in my database. The email column is the only one that is required. In StringB below, the ,,, simply indicates that a column was skipped/ignored.
The meat of my question is this:
I have a string of text (StringA) comes from a spreadsheet that I need to find in another string of text (StringB) which matches my database (this is not the real values, just made it simple to illustrate my problem so hopefully this is clear).
StringA: YR,MNTH,ANNIVERSARIES,FIRSTNAME,LASTNAME,EMAIL,NOTES
StringB: ,YEAR,,MONTH,LastName,Email,Comments <-- this list is dynamic
MNTH and MONTH are intentionally different;
excelColumnList = 'YR,MNTH,ANNIV,FIRST NAME,LAST NAME,EMAIL,NOTES';
mappedColumnList= ',YEAR,,MONTH,,First Name,Last Name,Email,COMMENTS';
mappedColumn= 'Last Name';
local.index = ListFindNoCase(mappedColumnList, mappedColumn,',', true);
local.returnValue = "";
if ( local.index > 0 )
local.returnValue = ListGetAt(excelColumnList, local.index);
writedump(local.returnValue); // dumps "EMAIL" which is wrong
The problem I'm having is the index returned when StringB starts with a , returns the wrong index value which affects the mapping later. If StringB starts with a word, the process works perfectly. Is there a better way to to get the index when StringB starts with a ,?
I also tried using listtoarray and then arraytolist to clean it up but the index is still off and I cannot reliably just add +1 to the index to identify the correct item in the list.
On the other hand, I was considering this mappedColumnList = right(mappedColumnList,len(mappedColumnList)-1) to remove the leading , which still throws my index values off BUT I could account for that by adding 1 to the index and this appears to be reliably at first glance. Just concerned this is a sort of hack.
Any advice?
https://cfdocs.org/listfindnocase
Here is a cfgist: https://trycf.com/gist/4b087b40ae4cb4499c2b0ddf0727541b/lucee5?theme=monokai
UPDATED
I accepted the answer using EDIT #1. I also added a comment here: Finding specific instance in a list when the list starts with a comma
Identify and strip the "," off the list if it is the first character.
EDIT: Changed to a while loop to identify multiple leading ","s.
Try:
while(left(mappedColumnList,1) == ",") {
mappedColumnList = right( mappedColumnList,(len(mappedColumnList)-1) ) ;
}
https://trycf.com/gist/64287c72d5f54e1da294cc2c10b5ad86/acf2016?theme=monokai
EDIT 2: Or even better, if you don't mind dropping back into Java (and a little Regex), you can skip the loop completely. Super efficient.
mappedColumnList = mappedColumnList.replaceall("^(,*)","") ;
And then drop the while loop completely.
https://trycf.com/gist/346a005cdb72b844a83ca21eacb85035/acf2016?theme=monokai
<cfscript>
excelColumnList = 'YR,MNTH,ANNIV,FIRST NAME,LAST NAME,EMAIL,NOTES';
mappedColumnList= ',,,YEAR,MONTH,,First Name,Last Name,Email,COMMENTS';
mappedColumn= 'Last Name';
mappedColumnList = mappedColumnList.replaceall("^(,*)","") ;
local.index = ListFindNoCase(mappedColumnList, mappedColumn,',', true);
local.returnValue = ListGetAt(excelColumnList,local.index,",",true) ;
writeDump(local.returnValue);
</cfscript>
Explanation of the Regex ^(,*):
^ = Start at the beginning of the string.
() = Capture this group of characters
,* = A literal comma and all consecutive repeats.
So ^(,*) says, start at the beginning of the string and capture all consecutive commas until reaching the next non-matched character. Then the replaceall() just replaces that set of matched characters with an empty string.
EDIT 3: I fixed a typo in my original answer. I was only using one list.
writeOutput(arraytoList(listtoArray(mappedColumnList))) will get rid of your leading commas, but this is because it will drop empty elements before it becomes an array. This throws your indexing off because you have one empty element in your original mappedColumnList string. The later string functions will both read and index that empty element. So, to keep your indexes working like you see to, you'll either need to make sure that your Excel and db columns are always in the same order or you'll have to create some sort of mapping for each of the column names and then perform the ListGetAt() on the string you need to use.
By default many CF list functions ignore empty elements. A flag was added to these function so that you could disable this behavior. If you have string ,,1,2,3 by default listToArray would consider that 3 elements but listToArray(listVar, ",", true) will return 5 with first two as empty strings. ListGetAt has the same "includeEmptyValues" flag so your code should work consistently when that is set to true.
I've embedded Lua into my C application, and am trying to figure out why a table created in my C code via:
lua_createtable(L, 0, numObjects);
and returned to Lua, will produce a result of zero when I call the following:
print("Num entries", table.getn(data))
(Where "data" is the table created by lua_createtable above)
There's clearly data in the table, as I can walk over each entry (string : userdata) pair via:
for key, val in pairs(data) do
...
end
But why does table.getn(data) return zero? Do I need to insert something into the meta of the table when I create it with lua_createtable? I've been looking at examples of lua_createtable use, and I haven't seen this done anywhere....
table.getn (which you shouldn't be using in Lua 5.1+. Use the length operator #) returns the number of elements in the array part of the table.
The array part is every key that starts with the number 1 and increases up until the first value that is nil (not present). If all of your keys are strings, then the size of the array part of your table is 0.
Although it's a costly (O(n) vs O(1) for simple lists), you can also add a method to count the elements of your map :
>> function table.map_length(t)
local c = 0
for k,v in pairs(t) do
c = c+1
end
return c
end
>> a = {spam="data1",egg='data2'}
>> table.map_length(a)
2
If you have such requirements, and if your environment allows you to do so think about using penlight that provides that kind of features and much more.
the # operator (and table.getn) effectivly return the size of the array section (though when you have a holey table the semantics are more complex)
It does not count anything in the hash part of the table (eg, string keys)
for k,v in pairs(tbl) do count = count + 1 end