Help Sorting Dictionary

I am being present data as such:

{
	u'Key1': 
		[
		{'parent': True, 'label': u'myLabel1', 'status': ''}, 
		{'parent': True, 'label': u'myLabel2', 'status': ''}
		], 
	'Key2': 
		[
		{'parent': False, 'label': u'myLabel1', 'status': ''}, 
		{'parent': False, 'label': u'myLabel2', 'status': ''}
		]
}

Hoping for a little advice from the Python gurus. First, is this considered a dictionary of list of dictionaries? If not, what is the correct terminology?

Second, I would like to sort the data in two ways. One, the keys (currently Key1/2 sorted based on parent, it should always be first. Then, then the list of dictionaries under each of these keys sort based on label, alphabetical.

What is the pythonish way to accomplish this.

Is using sorted() an option?

Jython dictionaries (and most java dictionaries) are unsortable. That is, they "sort" by the hash function used for accelerated lookup. On top of that, if you assign an actual ordered/sorted dictionary to a Perspective property, it will be remapped into an unordered dictionary. :frowning_face:

So, how do you intend to use this?

FWIW, as long as you don't assign to Perspective, you can use instances of ConcurrentSkipListMap in place of your dictionaries and they will stay sorted.

I was going to "shove" this into a flex repeater. So I guess this is an example of the XY question.

I have faceplates for specific devices like valves, motors, etc. I like to show an alarm list on the faceplates, this is easy enough. But, there are times where multiple devices are "grouped" together, so I want to show alarms for all that belong to each other. This is easy enough.

The issue for me is I don't want all alarms to be listed together, I want them grouped by type. I kind of have that working except for sorting. I can get the alarms to sort alphabetical, but the groups I can't.

Here is what I am after (minus Main should be first),

image

Here is my mess of code so far that I'm playing with (be gentle :slight_smile: ):

from com.inductiveautomation.ignition.common.alarming.config import CommonAlarmProperties
from com.inductiveautomation.ignition.common import QualifiedPathUtils
from collections import defaultdict

if tagPath:

	source = "*" + shared.tags.general.tagPath(tagPath) + "*"
	state = ["Active, Unacknowledged", "Active, Acknowledged", "Cleared, Unacknowledged"]
	alarms = system.alarm.queryStatus(source=source)
	
	alarmDict = defaultdict(list) #Used to create a dictionary of dictionaries, to separate alarms by UDT
	for alarm in alarms:
		
		#Create a useful key, also used as a header for the flex repeater instances
		tag = shared.tags.general.tagParent(QualifiedPathUtils.toTagPathNew(alarm.getSource()).toStringFull(), tp=True)
		meta = system.tag.readBlocking(tag + ".Documentation")[0].value
		key = meta + " - " + tag.rsplit("/")[-1] if meta else tag.rsplit("/")[-1]
		
		#Read IsParent, this should display first and use the heading label "Main"
		parent = system.tag.readBlocking(tag + "/Cfg_IsParent")[0].value
	
		alarmData = {
			'hasAlarmPath': tag + "/Cfg_Has" + alarm.name.split("_")[-1] + "Alm",
			'label': alarm.name, 
			'status': alarm.state if str(alarm.state) in state else "",
			'parent': parent
		}
		if alarmData not in alarmDict[key if not parent else "Main"]:
			alarmDict[key if not parent else "Main"].append(alarmData)
	
	alarms = []
	for alarm in alarmDict:
		alarmData = {
			"alarms": sorted(alarmDict[alarm], key = lambda d: d['label'].lower()), #Sort alphabetically case insensitive
			"device": alarm if len(alarmDict) > 1 else "" #If empty then we hide
		}
		alarms.append(alarmData)

	return alarms

Fast dirty way to adjust your current code to sort:
Add import operator to the top of your code
After your for loop that adds the items to alarms put:

alarms.sort(key=operator.itemgetter('device'))

That will run through your alarms list and sort by the item key 'device'. That will get you working 'for now'™

Some side notes:

  • You are making multiple single tag calls to system.tag.readBlocking, It's more efficient to do a single call with multiple tag paths.

  • You first define alarms as the result from a call to system.alarm.queryStatus, but then later define it as a list that you are putting data into. Avoid reusing the same variable name in the same function, instead choose different variable names that can still clearly articulate what data it holds. In this case the second alarms could be something like repeaterAlarmList or similar (I suck with variable names too)

2 Likes

Thanks Ryan. this is a work in progress. I'm aware of the reads and variable names.
I'm usually very lazy with names and reads in the beginning. I typically go back through and cleanup.

Just a bad habit of mine.

1 Like

Nah, make it work, then make it pretty is as good an approach as any as long as it doesn't make it into production code.

2 Likes

Getting this

Traceback (most recent call last):
  File "<input>", line 41, in <module>
TypeError: an integer is required

Line 41 is this

print alarms.sort(operator.itemgetter('device'))

I added the import and replaced the return with print (testing from script console), using the code snippet supplied. Any ideas?

Ah typo on my part, it should be alarms.sort(key=operator.itemgetter('device')), I've corrected original.

Thanks. I still wasn't able to get what you copied to work. This did though,

sorted(alarms, key=operator.itemgetter('device'))
2 Likes

Strange that it doesn't let you do .sort but yeah, sorted will return the sorted list so they more or less do the same thing.

I checked the code that I was referencing and I'm calling .sort on a list with no issues, so maybe a typecasting thing?