Locale-sensitive string sort in Dart - sorting

I need to sort a List<String>. Its elements might contain non-ASCII characters (think é, ž, Æ, ...). Is there a way to do this in Dart while being locale-aware?
Something like localeCompare in Javascript.
Some examples:
Czech (cs_CZ)
List<String> czechWords = ['čeština', 'cvrček', 'chleba',];
// expected after calling a localizedSort() or similar method
List<String> czechWordsSorted = ['cvrček', 'čeština', 'chleba',];

The Dart platform libraries do not currently have any locale-specific string sorting or comparison functionality.

If anyone still has the same problem, here is how I solved this (for Czech in the example).
Rationale
This works by substituting the diacritics chars away & then comparing on normal strings. The trick is to expand both the normalized & unnormalized strings, but so that the order is as You want it.
For example, I want to have u before ú which is before ů, so I can ensure this if I substitute all u for ua, ú for ub, and ů for uc.
Another thing is solving ch; this is done by substituting h for ha and ch for hb (so that each ch char is just after h).
Both cases need to be done in one pass, as they interact.
Solution
const Map<String, String> collTable = {
"a": "aa", "á": "ab", "A": "Aa", "Á": "Ab", "c": "ca", "č": "cb", "C": "Ca",
"Č": "Cb", "d": "da", "ď": "db", "D": "Da", "Ď": "Db", "e": "ea", "é": "eb",
"ě": "ec", "E": "Ea", "É": "Eb", "Ě": "Ec", "i": "ia", "í": "ib", "I": "Ia",
"Í": "Ib", "n": "na", "ň": "nb", "N": "Na", "Ň": "Nb", "o": "oa", "ó": "ob",
"O": "Oa", "Ó": "Ob", "r": "ra", "ř": "rb", "R": "Ra", "Ř": "Rb", "s": "sa",
"š": "sb", "S": "Sa", "Š": "Sb", "t": "ta", "ť": "tb", "T": "Ta", "Ť": "Tb",
"u": "ua", "ú": "ub", "ů": "uc", "U": "Ua", "Ú": "Ub", "Ů": "Uc", "y": "ya",
"ý": "yb", "Y": "Ya", "Ý": "Yb", "z": "za", "ž": "zb", "Z": "Za", "Ž": "Zb",
};
String collKeys = collTable.keys.join("");
final RegExp keysPat = RegExp(r"(cH|ch|CH|Ch|h|H)|([" + collKeys + r"])");
bool isUpperCase(String str){
return str.toUpperCase() == str;
}
String collStr(String str){
return str.replaceAllMapped(keysPat, (match){
assert((match[1] == null) != (match[2] == null));
if(match[1] != null) {
// ch group = either h or ch
String g = match[1]!;
if (g.length == 1) {
// h => ha, H => Ha
return "${g}a";
} else {
assert(g.length == 2);
// need to keep the case of the letters
String subc = isUpperCase(g[0]) ? 'H' : 'h';
// ch => hb, Ch => Hb, CH => Hb, cH => hb
return "${subc}b";
}
}
// diacritics
return collTable[match[2]]!;
});
}
int cmpColl(String a, String b){
return collStr(a).compareTo(collStr(b));
}
Usage
You can then use it like this:
List<String> czechWords = ['čeština', 'cvrček', 'děda', 'chleba', 'czechia', 'houska', 'imbecilní'];
for(var w in czechWords){
debugPrint("$w -> ${collStr(w)}");
}
czechWords.sort(cmpColl);
debugPrint(czechWords.toString());
Which prints:
čeština -> cbeasbtaianaaa
cvrček -> cavracbeak
děda -> daecdaaa
chleba -> hbleabaa
czechia -> cazaeahbiaaa
houska -> haoauasakaa
imbecilní -> iambeacaialnaib
[cvrček, czechia, čeština, děda, houska, chleba, imbecilní]
Disclaimer
This definitely needs more testing :-)
Edit:
The code for comparing ch is still not perfect. It is not clear, how the order should really be with capitalization, e.g. Ch vs. CH, cH vs ch, etc. Some of these might not be a valid czech letter... Right now, the Ch and CH are both treated as capital ch, so order is the same (== resulting order depends on whether dart sort is stable).

Related

Transform a golang map into another structure of map

I need help for transforming this input map into the output map. I try with switch/case and for but I didn't succeed it. Thanks a lot !
Input :
Values{
"toto_voiture_brand": Ad{
"CITROEN": "CITROEN",
},
"toto_voiture_model": Ad{
"CITROEN_toto": "C3",
},
"toto_moto_brand": Ad{
"KAWASAKI": "KAWASAKI",
},
"toto_moto_model": Ad{
"KAWASAKI_tata": "Ninja 1000 SX",
},
"toto_camion_brand": Ad{
"RENAULT": "RENAULT",
"PEUGEOT": "PEUGEOT",
},
"toto_camion_model": Ad{
"RENAULT_toto": "J5",
"PEUGEOT_tata": "255",
},
},
}
Output
Values{
"toto_voiture_model": {
"Citroen": {
{Value: "C3"},
},
},
"toto_moto_model": {
"Kawasaki": {
{Value: "Ninja 1000 SX"},
},
},
"toto_camion_model": {
"RENAULT": {
{Value: "J5"},
},
"PEUGEOT": {
{Value: "255"},
},
},
}
I've tried with switch case and loop for and map. But I don't have the result attendee, I didn't found how to match every map, key and value. Thanks a lot
I should have managed what you need with the following code:
package main
import (
"encoding/json"
"fmt"
"strings"
)
type Output struct {
Value string `json:"Value"`
}
func main() {
// declare output
output := make(map[string]map[string]Output, 0)
// input
input := make(map[string]map[string]string, 0)
input["toto_voiture_brand"] = map[string]string{
"CITROEN": "CITROEN",
}
input["toto_voiture_model"] = map[string]string{
"CITROEN_toto": "C3",
}
input["toto_moto_model"] = map[string]string{
"KAWASAKI_tata": "Ninja 1000 SX",
}
input["toto_camion_model"] = map[string]string{
"RENAULT_toto": "J5",
"PEUGEOT_tata": "255",
}
// transformation
for k, v := range input {
if strings.HasSuffix(k, "_model") {
tempMap := make(map[string]Output, len(v))
for kk, vv := range v {
key := strings.Split(kk, "_")[0]
tempMap[key] = Output{
Value: vv,
}
}
output[k] = tempMap
}
}
data, _ := json.MarshalIndent(&output, "", "\t")
fmt.Println(string(data))
}
I put some comments within the code just to separate sections. The first two parts are only supposed to define your input and output variables.
The section starting with // transformation is a good candidate to become a function but I preferred to leave it within the main function for demo purposes. Let me recap what's happens in the loop:
You range over the entries of your input variable
If the key has the suffix _model, you take it into consideration
You define a locally-scoped map (called tempMap) of type map[string]Output with the right number of elements that we're gonna add
You range over the v variable (that's why we're dealing with nested maps)
For each item, you're gonna add an entry to the tempMap
At the end of the nested loop, you add an entry to the parent map (output)
The last part is only for printing a beautiful JSON that can be easily read and checked.
Note that this code is simplified just to show off how to achieve your goal, adjust it before putting it into production.
Let me know if this helps, thanks!

Null pointer exception while consuming streams

{
"rules": [
{
"rank": 1,
"grades": [
{
"id": 100,
"hierarchyCode": 32
},
{
"id": 200,
"hierarchyCode": 33
}
]
},
{
"rank": 2,
"grades": []
}
]
}
I've a json like above and I'm using streams to return "hierarchyCode" based on some condition. For example if I pass "200" my result should print 33. So far I did something like this:
request.getRules().stream()
.flatMap(ruleDTO -> ruleDTO.getGrades().stream())
.map(gradeDTO -> gradeDTO.getHierarchyCode())
.forEach(hierarchyCode -> {
//I'm doing some business logic here
Optional<SomePojo> dsf = someList.stream()
.filter(pojo -> hierarchyCode.equals(pojo.getId())) // lets say pojo.getId() returns 200
.findFirst();
System.out.println(dsf.get().getCode());
});
So in the first iteration for the expected output it returns 33, but in the second iteration it is failing with Null pointer instead of just skipping the loop since "grades" array is empty this time. How do I handle the null pointer exception here?
You can use the below code snippet using Java 8:
int result;
int valueToFilter = 200;
List<Grade> gradeList = data.getRules().stream().map(Rule::getGrades).filter(x-> x!=null && !x.isEmpty()).flatMap(Collection::stream).collect(Collectors.toList())
Optional<Grade> optional = gradeList.stream().filter(x -> x.getId() == valueToFilter).findFirst();
if(optional.isPresent()){
result = optional.get().getHierarchyCode();
System.out.println(result);
}
I have created POJO's according to my code, you can try this approach with your code structure.
In case you need POJO's as per this code, i will share the same as well.
Thanks,
Girdhar

Get top level parent node for any node

Given the format at the end of the question, what's the best way to get the top-level name for a given item?
Top-level names are the ones with parentId = 1.
def getTopLevel(name: String): String = {
// Environment(150) -> Environment(150) - since its parentId is 1
// Assassination -> Security - since Assassination(12) -> Terrorism(10) -> Security(2)
}
Here's my current approach but is there something better?
unmapped = categories.size
Loop through this list until there are still unmapped items.
- build a Map(Int, String) for top levels.
- build a Map(Int, Int) - that maps an id to top level id.
- keep track of unmapped items
once loop exits, I can use both Maps to get the job done.
[
{
"name": "Destination Overview",
"id": 1,
"parentId": null
},
{
"name": "Environment",
"id": 150,
"parentId": 1
},
{
"name": "Security",
"id": 2,
"parentId": 1
},
{
"name": "Armed Conflict",
"id": 10223,
"parentId": 2
},
{
"name": "Civil Unrest",
"id": 21,
"parentId": 2
},
{
"name": "Terrorism",
"id": 10,
"parentId": 2
},
{
"name": "Assassination",
"id": 12,
"parentId": 10
}
]
This is actually two questions.
Parsing Json into a Scala collection and
Using that collection to trace items back to the top parent
For the first question, you can use play-json. The second part can be handled with a tail-recursive function. Here is the full program that solves both problems:
import play.api.libs.json.{Json, Reads}
case class Node(name: String, id: Int, parentId: Option[Int])
object JsonParentFinder {
def main(args: Array[String]): Unit = {
val s =
"""
|[
| {
| "name": "Destination Overview",
| "id": 1,
| "parentId": null
| },
| {
| "name": "Environment",
| "id": 150,
| "parentId": 1
| },
// rest of the json
|]
|""".stripMargin
implicit val NodeReads : Reads[Node] =Json.reads[Node]
val r = Json.parse(s).as[Seq[Node]]
.map(x => x.id -> x).toMap
println(getTopLevelNode(150, r))
println(getTopLevelNode(12, r))
}
def getTopLevelNode(itemId : Int, nodes: Map[Int, Node], path : List[Node] = List.empty[Node]) : List[Node] = {
if(nodes(itemId).id == 1)
nodes(itemId) +: path
else
getTopLevelNode(nodes(nodes(itemId).parentId.get).id, nodes, nodes(itemId) +: path)
}
}
Output will be:
List(Node(Destination Overview,1,None), Node(Environment,150,Some(1)))
List(Node(Destination Overview,1,None), Node(Security,2,Some(1)), Node(Terrorism,10,Some(2)), Node(Assassination,12,Some(10)))
A few notes:
I have not implemented comprehensive error-handling logic. The implicit assumption is that the only item with parentId==None is the root node. nodes(itemId).parentId.get could lead to failure.
Also, in creating the map, the assumption is that all items have unique ids.
Another assumption is that all nodes eventually have a path to the root node. If that is not the case, this will fail. But it should be straightforward to fix these cases by adding more stop conditions.
I am prepending items to the accumulator list(named path here) because prepend operation on Scala's lists takes constant time. You can just reverse the resulting list or use another data structure like Vector to efficiently build the path.

Remove Element From Struct But Only For This One Function

So I have an Struct that holds data that has a AddedByUser which links to my User Struct.
What I want to be able to do it remove the UserLevel from the AddedByUser
Now I want to be able to do it from this function only, so using the json:"-" is not an option. That would remove it from all json output. I only want to remove it form this one function.
I should also say that these are Gorm models and when I have been trying to remove the 10 option (UserLevels) it only removes the outer data set not the UserLevel from all of the data.
{
"ID": 1,
"CreatedAt": "2019-01-08T16:33:09.514711Z",
"UpdatedAt": "2019-01-08T16:33:09.514711Z",
"DeletedAt": null,
"UUID": "00000000-0000-0000-0000-000000000000",
"Title": "title000",
"Information": "info999",
"EventDate": "2006-01-02T15:04:05Z",
"AddedByUser": {
"ID": 2,
"CreatedAt": "2019-01-08T15:27:52.435397Z",
"UpdatedAt": "2019-01-08T15:27:52.435397Z",
"DeletedAt": null,
"UUID": "b019df80-a7e4-4397-814a-795e7e84b4ca",
"Firstname": "Me",
"Surname": "admin",
"Password": "....",
"Email": "admin#email.co.uk",
"UserLevel": {
"ID": 0,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"LevelTitle": "",
"UserLevel": null
},
So this is what I have tried,
data := []models.MyData{}
data = append(data[0:2])
I have about 14 results, with out the append it loads all the results but with this is only loads two results. The idea was to remove either UpdateAt or Title. As I am not sure if the gorm model information is all 0 or if the slice sees them as 0,1,2,3,4 etc.
I have also tried to range over the slice of models, while I can access each of the sections, I can not seem to find a simple method to remove data by name from a struct? Maps seem to have that but not structs which I am not sure why?
Thanks.
UPDATE
This is the model I am using:
//Model
type MyData struct {
gorm.Model
UUID uuid.UUID
Title string
Information string
EventDate time.Time
AddedByUser Users `gorm:"ForeignKey:added_by_user_fk"`
AddedByUserFK uint
}
//Users Model
type Users struct {
gorm.Model
UUID uuid.UUID
Firstname string
Surname string
Password string
Email string
UserLevel UserLevels `gorm:"ForeignKey:user_level_fk" json:",omitempty"`
UserLevelFK uint
}
As mentioned in the comments, you cannot remove fields from a struct value, because that would yield a value of a different type.
However, you can set fields to their zero value. Combined with the omitempty JSON tag, you can exclude fields from the JSON encoding. To make this work properly, you have to change the UserLevel field to a pointer type (otherwise you end up with empty objects in the JSON document).
Types shortened for brevity:
package main
import (
"encoding/json"
"fmt"
)
type MyData struct {
Title string
AddedByUser Users
}
type Users struct {
ID int
UserLevel *UserLevels `json:",omitempty"` // pointer type with omitempty
}
type UserLevels struct {
LevelTitle string
}
func main() {
var x MyData
x.Title = "foo"
x.AddedByUser.ID = 2
x.AddedByUser.UserLevel = &UserLevels{}
f(x)
b, _ := json.MarshalIndent(x, "", " ")
fmt.Println("main:\n" + string(b))
}
func f(x MyData) {
// "unset" UserLevel. Since we are receiving a copy of MyData, this is
// invisible to the caller.
x.AddedByUser.UserLevel = nil
b, _ := json.MarshalIndent(x, "", " ")
fmt.Println("f:\n" + string(b))
}
// Output:
// f:
// {
// "Title": "foo",
// "AddedByUser": {
// "ID": 2
// }
// }
// main:
// {
// "Title": "foo",
// "AddedByUser": {
// "ID": 2,
// "UserLevel": {
// "LevelTitle": ""
// }
// }
// }
Try it on the playground: https://play.golang.org/p/trUgnYamVOA
Alternatively, you can define new types that exclude the AddedByUser field. However, since this field isn't at the top level, this is a lot of work, and it's easy to forget to update those types when new fields are added to the original types.
If the field were at the top level, the compiler would do most of the work for you, because types that only differ in their field tags can be directly converted to one another:
type MyData struct {
ID int
Title string
}
func main() {
var x MyData
x.ID = 1
x.Title = "foo"
f(x)
}
func f(x MyData) {
type data struct { // same as MyData, except the field tags
ID int
Title string `json:"-"`
}
b, _ := json.MarshalIndent(data(x), "", " ")
fmt.Println("main:\n" + string(b))
}

How do you quickly find the URL for a Win32 API on MSDN?

How do you quickly find the URL for a Win32 API on MSDN? It's easy for .NET methods -- just add the method name (for example, System.Byte.ToString) to http://msdn.microsoft.com/library/.
However, for Win32 APIs (say GetLongPathName), this doesn't work: http://msdn.microsoft.com/en-us/library/GetLongPathName.
I want to be able to use the URL in code comments or documentation. So the URL one gets with an MSDN or Google search (for example, http://msdn.microsoft.com/library/aa364980.aspx) isn't really what I'm looking for. I'd really like my code comments to look something like:
// blah blah blah. See http://msdn.microsoft.com/en-us/library/GetLongPathName for more information.
What's the magic pixie dust for Win32 APIs? Or does it only work for .NET methods?
Google might be your best bet. I know the msdn site search has time and again pointed me in the wrong direction, but a quick switch to Google ("GetLongPathName site:msdn.microsoft.com") never steers me wrong.
FWIW if you have the MSDN installed locally on your machine the Zeus editor has a feature to search the local copy of the MSDN.
For example, placing the cursor on the GetLongPathName word within a text document and using the Zeus Help, Quick Help, Current Word menu, the following MSDN page gets loaded:
ms-help://MS.VSCC.v80/MS.MSDN.vAug06.en/fileio/fs/getlongpathname.htm
I am using Linkify by cough me, which lets you link
// see msdn:GetLongPathName
to the google search japollock mentions.
You could use MSDN search.
http://social.msdn.microsoft.com/Search/en-US/?Refinement=86&Query=GetLongPathName
Refinement=86 stands for Win32 search.
MSDN GET api (I don't know how new this is)
"https://learn.microsoft.com/api/search?locale=en-us&scoringprofile=semantic-captions&%24top=1&search=" functionName
returns json like such:
{
"results": [
{
"title": "KeClearEvent function (wdm.h) - Windows drivers",
"url": "https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-keclearevent",
"displayUrl": {
"content": "/windows-hardware/drivers/ddi/wdm/nf-wdm-keclearevent",
"hitHighlights": [
{
"start": 41,
"length": 12
}
]
},
"description": "The KeClearEvent routine sets an event to a not-signaled state.",
"descriptions": [
{
"content": "KeClearEvent function (wdm.h) Article 04/18/2022 2 minutes to read In this article The KeClearEvent routine sets an event to a not-signaled state.",
"hitHighlights": [
{
"start": 0,
"length": 12
},
{
"start": 87,
"length": 12
}
]
},
{
"content": "For better performance, use KeClearEvent unless the caller uses the value returned by KeResetEvent to determine what to do next.",
"hitHighlights": [
{
"start": 28,
"length": 12
}
]
}
],
"lastUpdatedDate": "2022-04-18T04:31:00+00:00",
"breadcrumbs": []
}
],
"spellingCorrection": [],
"scopeRemoved": false,
"count": 18,
"nextLink": "https://learn.microsoft.com/api/Search?locale=en-us\u0026search=KeClearEvent\u0026$skip=1\u0026$top=1",
"srcheng": "02"
}
if you want really fast, you can bind it to a hotkey
AutohotkeyV2
#SingleInstance force
ListLines 0
KeyHistory 0
SendMode "Input" ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir A_ScriptDir ; Ensures a consistent starting directory.
linkFromName(functionName) {
json:=downloadToVar("https://learn.microsoft.com/api/search?locale=en-us&scoringprofile=semantic-captions&%24top=1&search=" functionName)
obj:=JSON_parse(json)
if (obj.results.Length) {
RegExMatch(obj.results[1].title, ".*?(?=\s|$)", &OutputVar)
if (OutputVar.0 = functionName) {
validUrl:=obj.results[1].url
} else if (OutputVar.0 = functionName "W" || OutputVar.0 = functionName "A") {
validUrl:=SubStr(obj.results[1].url, 1, -1) "w"
}
; A_Clipboard:=validUrl
Run validUrl
}
}
; linkFromName("GetLongPathNameW") ;works
; linkFromName("GetLongPathName") ;works
linkFromName(A_Clipboard)
Exitapp
f3::Exitapp
downloadToVar(url) {
whr := ComObject("WinHttp.WinHttpRequest.5.1")
whr.Open("GET", url, true)
whr.SetRequestHeader("User-Agent", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)")
whr.Send()
; Using 'true' above and the call below allows the script to remain responsive.
whr.WaitForResponse()
return whr.ResponseText
}
JSON_parse(str) {
c_:=1
return JSON_value()
JSON_value() {
char_:=SubStr(str, c_, 1)
Switch char_ {
case "{":
obj_:=Map()
;object
c_++
loop {
skip_s()
if (SubStr(str, c_, 1) == "}") {
c_++
return obj_
}
; key_:=JSON_objKey()
; a or "a"
if (SubStr(str, c_, 1) == "`"") {
RegExMatch(str, "(?:\\.|.)*?(?=`")", &OutputVar, c_ + 1)
key_:=RegExReplace(OutputVar.0, "\\(.)", "$1")
c_+=OutputVar.Len
} else {
RegExMatch(str, ".*?(?=[\s:])", &OutputVar, c_)
key_:=OutputVar.0
c_+=OutputVar.Len
}
c_:=InStr(str, ":", true, c_) + 1
skip_s()
value_:=JSON_value()
obj_[key_]:=value_
obj_.DefineProp(key_, {Value: value_})
skip_s()
if (SubStr(str, c_, 1) == ",") {
c_++, skip_s()
}
}
case "[":
arr_:=[]
;array
c_++
loop {
skip_s()
if (SubStr(str, c_, 1) == "]") {
c_++
return arr_
}
value_:=JSON_value()
arr_.Push(value_)
skip_s()
char_:=SubStr(str, c_, 1)
if (char_ == ",") {
c_++, skip_s()
}
}
case "`"":
RegExMatch(str, "(?:\\.|.)*?(?=`")", &OutputVar, c_ + 1)
unquoted:=RegExReplace(OutputVar.0, "\\(.)", "$1")
c_+=OutputVar.Len + 2
return unquoted
case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
RegExMatch(str, "[0-9.]*", &OutputVar, c_)
c_+=OutputVar.Len
return Number(OutputVar.0)
case "t":
;"true"
c_+=4
return {a:1}
case "f":
;"false"
c_+=5
return {a:0}
case "n":
;"null"
c_+=4
return {a:-1}
}
}
skip_s() {
RegExMatch(str, "\s*", &OutputVar, c_)
c_+=OutputVar.Len
}
}

Resources