#region License Information
/* HeuristicLab
* Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
*
* This file is part of HeuristicLab.
*
* HeuristicLab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* HeuristicLab is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with HeuristicLab. If not, see .
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using HeuristicLab.Common;
using HeuristicLab.Core;
using HeuristicLab.Data;
using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
namespace HeuristicLab.Encodings.LinearLinkageEncoding {
[Item("LinearLinkage", "Represents an LLE grouping of items.")]
[StorableClass]
public sealed class LinearLinkage : IntArray {
[StorableConstructor]
private LinearLinkage(bool deserializing) : base(deserializing) { }
private LinearLinkage(LinearLinkage original, Cloner cloner) : base(original, cloner) { }
public LinearLinkage() { }
private LinearLinkage(int length) : base(length) { }
private LinearLinkage(int[] elements) : base(elements) { }
///
/// Create a new LinearLinkage object where every element is in a seperate group.
///
public static LinearLinkage SingleElementGroups(int length) {
var elements = new int[length];
for (var i = 0; i < length; i++) {
elements[i] = i;
}
return new LinearLinkage(elements);
}
///
/// Create a new LinearLinkage object from an int[] in LLE
///
///
/// This operation checks if the argument is a well formed LLE
/// and throws an ArgumentException otherwise.
///
/// The LLE representation
public static LinearLinkage FromForwardLinks(int[] lle) {
if (!Validate(lle)) {
throw new ArgumentException("Array is malformed and does not represent a valid LLE forward encoding.", "elements");
}
return new LinearLinkage(lle);
}
///
/// Create a new LinearLinkage object by parsing an LLE-e representation
/// and modifing the underlying array so that it is in LLE representation.
///
///
/// This operation runs in O(n) time, but requires additional memory
/// in form of a int[].
///
/// The LLE-e representation
/// LinearLinkage
public static LinearLinkage FromEndLinks(int[] llee) {
var result = new LinearLinkage(llee.Length);
result.SetEndLinks(llee);
return result;
}
///
/// Create a new LinearLinkage object by translating
/// an enumeration of groups into the underlying array representation.
///
///
/// Throws an ArgumentException when there is an element assigned to
/// multiple groups or elements that are not assigned to any group.
///
/// The grouping of the elements, each element must
/// be part of exactly one group.
public static LinearLinkage FromGroups(int length, IEnumerable> grouping) {
var result = new LinearLinkage(length);
result.SetGroups(grouping);
return result;
}
public override IDeepCloneable Clone(Cloner cloner) {
return new LinearLinkage(this, cloner);
}
///
/// This method parses the encoded array and calculates the membership of
/// each element to the groups. It starts at the lowest element.
///
///
/// Runtime complexity of this method is O(n) where n is the length of the
/// array.
///
/// An enumeration of all groups.
public IEnumerable> GetGroups() {
var len = array.Length;
var used = new bool[len];
for (var i = 0; i < len; i++) {
if (used[i]) continue;
var curr = i;
var next = array[curr];
var group = new List { curr };
while (next > curr && next < len && !used[next]) {
used[curr] = true;
curr = next;
next = array[next];
group.Add(curr);
}
if (curr != next) throw new ArgumentException("Array is malformed and does not represent a valid LLE forward encoding.");
used[curr] = true;
yield return group;
}
/*
var len = array.Length;
var used = new bool[len];
// iterate from lowest to highest index
for (var i = 0; i < len; i++) {
if (used[i]) continue;
var group = new List { i };
used[i] = true;
var next = array[i];
if (next < i || next >= len) {
throw new ArgumentException("Array is malformed and does not represent a valid LLE forward encoding.");
}
while (next != array[next]) {
if (next < 0 || next >= len || used[next]) {
throw new ArgumentException("Array is malformed and does not represent a valid LLE forward encoding.");
}
group.Add(next);
used[next] = true;
next = array[next];
}
yield return group;
}*/
}
///
/// This method parses the encoded array and gathers all elements
/// that belong to the same group as element .
///
/// The element whose group should be returned.
///
/// The element at and all other
/// elements in the same group.
public IEnumerable GetGroup(int index) {
foreach (var n in GetGroupForward(index))
yield return n;
// the element index has already been yielded
foreach (var n in GetGroupBackward(index).Skip(1))
yield return n;
}
///
/// This method parses the encoded array and gathers the element
/// as well as subsequent elements that
/// belong to the same group.
///
/// The element from which subsequent (having a
/// larger number) elements in the group should be returned.
///
/// The element and all subsequent
/// elements in the same group.
public IEnumerable GetGroupForward(int index) {
yield return index;
var next = array[index];
if (next == index) yield break;
int prev;
do {
yield return next;
prev = next;
next = array[next];
} while (next != prev);
}
///
/// This method parses the encoded array and gathers the element
/// given as well as preceeding elements that
/// belong to the same group.
///
///
/// Warning, this code has performance O(index) as the array has to
/// be fully traversed backwards from the given index.
///
/// The element from which preceeding (having a
/// smaller number) elements in the group should be returned.
///
/// The element and all preceeding
/// elements in the same group.
public IEnumerable GetGroupBackward(int index) {
yield return index;
var next = array[index];
// return preceding elements in group
for (var prev = index - 1; prev >= 0; prev--) {
if (array[prev] != next) continue;
next = prev;
yield return next;
}
}
///
/// This method translates an enumeration of groups into the underlying
/// array representation.
///
///
/// Throws an ArgumentException when there is an element assigned to
/// multiple groups or elements that are not assigned to any group.
///
/// The grouping of the elements, each element must
/// be part of exactly one group.
public void SetGroups(IEnumerable> grouping) {
var len = array.Length;
var used = new bool[len];
foreach (var group in grouping) {
var prev = -1;
foreach (var g in group.OrderBy(x => x)) {
if (g < prev || g >= len) throw new ArgumentException(string.Format("Element {0} is bigger than {1} or smaller than 0.", g, len - 1), "grouping");
if (prev >= 0) array[prev] = g;
prev = g;
if (used[prev]) {
throw new ArgumentException(string.Format("Element {0} is contained at least twice.", prev), "grouping");
}
used[prev] = true;
}
array[prev] = prev;
}
if (!used.All(x => x))
throw new ArgumentException(string.Format("Elements are not assigned a group: {0}", string.Join(", ", used.Select((x, i) => new { x, i }).Where(x => !x.x).Select(x => x.i))));
}
///
/// Performs a check whether the array represents a valid LLE encoding.
///
///
/// The runtime complexity of this method is O(n) where n is the length of
/// the array.
///
/// True if the encoding is valid.
public bool Validate() {
return Validate(array);
}
private static bool Validate(int[] array) {
var len = array.Length;
var used = new bool[len];
for (var i = 0; i < len; i++) {
if (used[i]) continue;
var curr = i;
var next = array[curr];
while (next > curr && next < len && !used[next]) {
used[curr] = true;
curr = next;
next = array[next];
}
if (curr!=next) return false;
used[curr] = true;
}
return true;
}
///
/// This method flattens tree structures that may be present in groups.
/// These tree structures may be created by e.g. merging two groups by
/// linking one end node to the end node of another.
/// Consider following 1-based index array: 6, 6, 7, 5, 5, 8, 8, 8, 9.
/// This results in the following tree structure for group 8:
/// 8
/// / \
/// 6 7
/// / \ |
/// 1 2 3
/// After this operation the array will be 2, 3, 6, 5, 5, 7, 8, 8, 9.
/// Representing a tree with one branch: 1 -> 2 -> 3 -> 6 -> 7 -> 8
///
///
/// The method first converts the array to LLE-e format and then
/// linearizes the links. This requires two passes of the whole array
/// as well as a dictionary to hold the smallest index of each group.
/// The runtime complexity is O(n).
///
/// The method assumes that there are no back links present.
///
public void LinearizeTreeStructures() {
// Step 1: Convert the array into LLE-e
ToEndLinksInplace(array);
// Step 2: For all groups linearize the links
SetEndLinks(array);
}
///
/// Creates a copy of the underlying array and turns it into LLE-e.
///
///
/// LLE-e is a special format where each element points to the
/// ending item of a group.
/// The LLE representation 2, 3, 5, 6, 5, 7, 8, 8 would become
/// 5, 5, 5, 8, 5, 8, 8, 8 in LLE-e.
///
/// This operation runs in O(n) time.
///
/// An integer array in LLE-e representation
public int[] ToEndLinks() {
var result = (int[])array.Clone();
ToEndLinksInplace(result);
return result;
}
private static void ToEndLinksInplace(int[] array) {
var length = array.Length;
for (var i = length - 1; i >= 0; i--) {
var next = array[i];
if (next > i) {
array[i] = array[next];
} else if (next < i) {
throw new ArgumentException("Array is malformed and does not represent a valid LLE encoding.", "array");
}
}
}
///
/// Parses an LLE-e representation and modifies the underlying array
/// so that it is in LLE representation.
///
///
/// This operation runs in O(n) time, but requires additional memory
/// in form of a int[].
///
/// The LLE-e representation
public void SetEndLinks(int[] llee) {
var length = array.Length;
if (length != llee.Length) {
throw new ArgumentException(string.Format("Expected length {0} but length was {1}", length, llee.Length), "llee");
}
// If we are ok with mutating llee we can avoid this clone
var lookup = (int[])llee.Clone();
for (var i = length - 1; i >= 0; i--) {
var end = llee[i];
if (end == i) {
array[i] = end;
} else if (end > i && end < length) {
array[i] = lookup[end];
lookup[end] = i;
} else {
throw new ArgumentException("Array is malformed and does not represent a valid LLE end encoding.", "llee");
}
}
}
}
}