Derive the set of formal concepts from the objects and attributes

Property definitions

fca $ FormalContext :: formal_concepts
	# Derive the set of formal concepts from the objects and attributes
	fun formal_concepts: Set[FormalConcept[O, A]] do
		# black magic!

		var concepts = new HashSet[FormalConcept[O, A]]

		var extentsByAttr = new HashMap[Set[A], Set[O]]
		for attribute in attributes do
			var ka = new HashSet[A].from([attribute])
			extentsByAttr[ka] = new HashSet[O]
			for object in objects do
				if not objects_attributes[object].has(attribute) then continue
				extentsByAttr[ka].add(object)
			end
		end

		var nextents = new HashMap[Set[A], Set[O]]
		for k1, v1 in extentsByAttr do
			for k2, v2 in extentsByAttr do
				if k1 == k2 then continue
				var n = v1.intersection(v2)
				if extentsByAttr.values.has(n) then continue
				var ka = k1.union(k2)
				nextents[ka] = n
			end
		end
		extentsByAttr.add_all nextents

		var contained = true
		for k1, v1 in extentsByAttr do
			if not contained then break
			for k2, v2 in extentsByAttr do
				if k1 == k2 then continue
				var n = v1.intersection(v2)
				if extentsByAttr.values.has(n) then continue
				contained = false
				break
			end
		end

		if contained then
			extentsByAttr[new HashSet[A]] = new HashSet[O].from(objects)
		end

		var extents = new HashSet[Set[O]]
		for objects in extentsByAttr.values do
			extents.add objects
		end

		for extent in extents do
			var intents: Set[A] = new HashSet[A]
			var count = 0
			var cl = new FormalConcept[O, A]
			if extent.is_empty then
				intents.add_all(attributes)
			else
				for object in objects do
					if not extent.has(object) then continue
					var prev = objects_attributes[object]
					if count > 0 then
						intents = prev.intersection(intents)
					else
						intents = prev
					end
					count += 1
					cl.objects.add(object)
				end
			end
			cl.attributes.add_all intents
			concepts.add cl
		end

		return concepts
	end
lib/fca/fca.nit:106,2--179,4