How to get value by name with Erlang struct? - struct

Newbie of Erlang here.
Have Json like this:
{
"ReadCardResultResult":{
"amount":"0",
"balance":"9400",
"Status":1,
"Commands":[
],
"message":"0000000000000000",
"ret":{
"code":0,
"desc":"SUCCESS",
"subReturn":null
},
"transactionId":103979,
"txnInfo":[
{
"infoId":101,
"infoName":"TestName1",
"infoValue":"04432FBAA53080"
},
{
"infoId":102,
"infoName":"TestName2",
"infoValue":""
},
{
"infoId":103,
"infoName":"TestName3",
"infoValue":"9400"
},
{
"infoId":104,
"infoName":"TestName4",
"infoValue":"5"
}
]
}
}
My task is to get specific infoValue out of txnInfo according to infoName. For example: I need to get infoValue with "TestName3", that would be "9400".
So far I narrowed the Json with proplists:get_value(<<"txnInfo">>, ReadCardResultResult). and now I have this:
[{struct,[{<<"infoId">>,101},
{<<"infoName">>,<<"TestName1">>},
{<<"infoValue">>,<<"043A2FBAA53080">>}]},
{struct,[{<<"infoId">>,108},
{<<"infoName">>,<<"TestName2">>},
{<<"infoValue">>,<<"772">>}]},
{struct,[{<<"infoId">>,108},
{<<"infoName">>,<<"TestName3">>},
{<<"infoValue">>,<<"772">>}]},
{struct,[{<<"infoId">>,125},
{<<"infoName">>,<<"TestName4">>},
{<<"infoValue">>,<<>>}]}]
Now, where do I go from here? I'm really stuck on this. Any help would be appreciated.

To efficiently get the first item of a list matching a predicate, you can invert the predicate and use lists:dropwhile/2 (see this answer for more info about that). Other than that, it's just some pattern matching and a case expression:
-module(a).
-compile([export_all]).
main() ->
TxnInfo = [{struct,[{<<"infoId">>,101},
{<<"infoName">>,<<"TestName1">>},
{<<"infoValue">>,<<"043A2FBAA53080">>}]},
{struct,[{<<"infoId">>,108},
{<<"infoName">>,<<"TestName2">>},
{<<"infoValue">>,<<"772">>}]},
{struct,[{<<"infoId">>,108},
{<<"infoName">>,<<"TestName3">>},
{<<"infoValue">>,<<"9400">>}]},
{struct,[{<<"infoId">>,125},
{<<"infoName">>,<<"TestName4">>},
{<<"infoValue">>,<<>>}]}],
WantName = <<"TestName3">>,
case lists:dropwhile(fun({struct, PropList}) -> proplists:get_value(<<"infoName">>, PropList) /= WantName end, TxnInfo) of
[] ->
io:format("no matches~n");
[{struct, PropList} | _] ->
io:format("first match: ~p~n", [proplists:get_value(<<"infoValue">>, PropList)])
end.
Output:
first match: <<"9400">>
If you only care about the first result and want to crash if none is found, you can replace the case with just:
[{struct, PropList} | _] = lists:dropwhile(...),

Related

get title from Zotero item

Still very new to Rust, trying to understand how to extract the title of a JournalArticle using the Zotero crate.
I've got this, and can confirm the item is retrieved successfully:
let zc = ZoteroCredentials::new();
let z = ZoteroInit::set_user(&zc.api_id, &zc.api_key);
let item = z.get_item(item_id, None).unwrap();
From here, I see that an item.data is an ItemType, specifically a JournalArticleData. But I'm fundamentally not quite understanding how to either a) serialize this to JSON, or b) access .title as a property.
For context, this would be the result of a Rocket GET route.
Any help would much appreciated!
It sounds like the part you're missing is how to use pattern matching on an enum. I'm not familiar with zotero so this is all based on the docs, with verbose type annotations to be explicit about what I think I'm doing:
use zotero::data_structure::item::{Item, ItemType, JournalArticleData};
let item: Item = z.get_item(item_id, None).unwrap();
// Now we must extract the JournalArticle from the ItemType, which is an enum
// and therefore requires pattern matching.
let article: JournalArticleData = match item.data {
ItemType::JournalArticle(a) => a,
something_else => todo!("handle wrong type of item"),
}
let title: String = article.title;
(The match could also be written as an if let just as well.)
You could also use pattern matching to go through the entire structure, rather than only the enum which requires it:
match z.get_item(item_id, None).unwrap() {
Item {
data: ItemType::JournalArticle(JournalArticleData {
title,
..
}),
..
} => {
// Use the `title` variable here
},
something_else => todo!("handle wrong type of item"),
}

Is it possible to combine two patterns, one with a match guard, in the same match arm?

I want to check if a string contains '$' and if there is something after the '$':
I tried this code:
fn test(s: String) {
match s.find('$') {
None | (Some(pos) if pos == s.len() - 1) => {
expr1();
}
_ => { expr2(); }
}
}
But it doesn't compile:
error: expected one of `)` or `,`, found `if`
Is it impossible to combine None and Some in one match-arm?
If so, is there a simple way to not duplicate expr1() except moving it into a separate function?
It is impossible to have the match-guard (the if thingy) apply to only one pattern alternative (the things separated by | symbols). There is only one match-guard per arm and it applies to all patterns of that arm.
However, there are many solutions for your specific problem. For example:
if s.find('$').map(|i| i != s.len() - 1).unwrap_or(false) {
expr2();
} else {
expr1();
}

PHP-like string parsing

I'm writing a mini-console of sorts and I'm trying to figure out how to extract things from a link. For example, in PHP this is a request variable
so:
http://somelink.com/somephp.php?variable1=10&variable2=20
Then PHP figures out the url parameters and assigns them to a variable.
How would I parse something like this in Swift?
So, given the string I'd want to take: variable1=10 and variable2=20 etc, is there a simple way to do this? I tried googling around but didn't really know what I was searching for.
I have a really horrible hacky way of doing this but it's not really extendable.
You’d be wanting NSURLComponents:
import Foundation
let urlStr = "http://somelink.com/somephp.php?variable1=10&variable2=20"
let components = NSURLComponents(string: urlStr)
components?.queryItems?.first?.name // Optional("variable1")
components?.queryItems?.first?.value // Optional("10")
You might find it helpful to add a subscript operator for the query items:
extension NSURLComponents {
subscript(queryItemName: String) -> String? {
// of course, if you do this a lot,
// cache it in a dictionary instead
for item in self.queryItems ?? [] {
if item.name == queryItemName {
return item.value
}
}
return nil
}
}
if let components = NSURLComponents(string: urlStr) {
components["variable1"] ?? "No value"
}

How to check if substring of string's array match to patterns from other string array

I wonder if there is any groovy-way to check if substring of strings matches to patterns.
For example I have strings List (or array):
def Errors = ['File xyz cannot be created: No space left on device', 'File kjh has errors: some_error']
Then I have list of strings, for example def Patterns = ['Tests failed', 'No space left on device', 'Something goes wrong', ...some strings... ]
I would like to check if some elements of List Patterns are substrings of Errors elements .
In that example it should return true, because Patterns has No space left on device and Errors has 'File xyz cannot be created: No space left on device'.
I know how to write it very ulgy and not efficient by using two for loops and method contains, but I know that Groovy has much more powerfull built-in methods. I have tried with findAll(), but it doesnt worked at all.
Do you have any ideas? Is there any way to make it more clever?
Explicitly naming pattern and error:
patterns.find { pattern -> errors.find { error -> error.contains(pattern) } } // -> No space left on device
patterns.any { pattern -> errors.find { error -> error.contains(pattern) } } // -> true
depending on what/how many you want to find.
Or even shorter:
patterns.find { errors.find { error -> error.contains(it) } }
patterns.any { errors.find { error -> error.contains(it) } }

Groovier way to parse tsv file into map

I have a tsv file in the form of "key \t value", and I need to read into a map. Currently i do it like this:
referenceFile.eachLine { line ->
def (name, reference) = line.split(/\t/)
referencesMap[name.toLowerCase()] = reference
}
Is there a shorter/nicer way to do it?
It's already quite short. Two answers I can think of:
First one avoids the creation of a temporary map object:
referenceFile.inject([:]) { map, line ->
def (name, reference) = line.split(/\t/)
map[name.toLowerCase()] = reference
map
}
Second one is more functional:
referenceFile.collect { it.split(/\t/) }.inject([:]) { map, val -> map[val[0].toLowerCase()] = val[1]; map }
The only other way I can think of doing it would be with an Iterator like you'd find in Commons IO:
#Grab( 'commons-io:commons-io:2.4' )
import org.apache.commons.io.FileUtils
referencesMap = FileUtils.lineIterator( referenceFile, 'UTF-8' )
.collectEntries { line ->
line.tokenize( '\t' ).with { k, v ->
[ (k.toLowerCase()): v ]
}
}
Or with a CSV parser:
#Grab('com.xlson.groovycsv:groovycsv:1.0')
import static com.xlson.groovycsv.CsvParser.parseCsv
referencesMap = referenceFile.withReader { r ->
parseCsv( [ separator:'\t', readFirstLine:true ], r ).collectEntries {
[ (it[ 0 ].toLowerCase()): it[ 1 ] ]
}
}
But neither of them are shorter, and not necessarily nicer either...
Though I prefer option 2 as it can handle cases such as:
"key\twith\ttabs"\tvalue
As it deals with quoted strings
This is the comment tim_yates added to melix's answer, and I think it's the shortest/clearest answer:
referenceFile.collect { it.tokenize( '\t' ) }.collectEntries { k, v -> [ k.toLowerCase(), v ] }

Resources