/*
* Copyright (C) 2011 Laurent Caillette
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.novelang.common.tree;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import com.google.common.collect.Maps;
import org.novelang.logger.Logger;
import org.novelang.logger.LoggerFactory;
/**
* Calculates and prints statistics about a {@link Tree}'s population.
*
* This shows that, on human-written documents, 33 % of {@link Tree} objects have 0 child and,
* among all {@link Tree}s with one child or more, 50 % have a single child.
*
* @author Laurent Caillette
*/
public class Statistics {
private static final Logger LOGGER = LoggerFactory.getLogger( Statistics.class );
private Statistics() { }
/**
* Returns a {@code Map} between the number of children and the number of nodes with this
* number of children for the given {@link Tree}.
*
* @param root a non-null object.
* @return a non-null {@code Map} containing no null with zero-or-positive, contiguous keys.
* The underlying implementation is a {@code TreeMap} with sorted keys.
*/
public static Map< Integer, Integer > calculate( final Tree< ? extends Tree > root ) {
final TreeMap< Integer, Integer > treeMap = Maps.newTreeMap() ;
// Mysterious compilation bug, commenting code out until inspiration comes.
/*
upgrade( treeMap, 10 ) ;
final Traversal< ? extends Tree > traversal = Traversal.Preorder.create() ;
Treepath< ? extends Tree > current = Treepath.create( root ) ;
while( current != null ) {
final int childCount = current.getTreeAtEnd().getChildCount() ;
if( ! treeMap.containsKey( childCount ) ) {
upgrade( treeMap, childCount ) ;
}
final Integer total = treeMap.get( childCount ) ;
if( total == null ) {
treeMap.put( childCount, 1 ) ;
} else {
treeMap.put( childCount, total + 1 ) ;
}
current = traversal.next( current ) ;
}
*/
return Collections.unmodifiableMap( treeMap ) ;
}
private static final int MAX_BAR_LENGTH = 40 ;
public static void logStatistics( final Tree< ? > tree ) {
if( LOGGER.isDebugEnabled() ) {
final Map< Integer, Integer > map = calculate( tree ) ;
final StringBuilder stringBuilder = new StringBuilder( "\n" ) ;
int maximum = 0 ;
for( final Map.Entry< Integer, Integer > entry : map.entrySet() ) {
maximum = Math.max( maximum, entry.getValue() ) ;
}
int childCount = 0 ;
for( final Map.Entry< Integer, Integer > entry : map.entrySet() ) {
final Integer value = entry.getValue() ;
if( value > 0 ) {
final Integer population = entry.getKey() ;
stringBuilder.append( String.format( " %6d : %6d ", population, value ) ) ;
bar( stringBuilder, maximum, value ) ;
stringBuilder.append( "\n" ) ;
childCount += population * value ;
}
}
stringBuilder.append( "-----------------\n" ) ;
stringBuilder.append( String.format( " Child count: %d \n", childCount ) ) ;
LOGGER.debug( stringBuilder.toString() ) ;
}
}
private static void bar( final StringBuilder stringBuilder, final int maximum, final int value ) {
final int divider = ( maximum / MAX_BAR_LENGTH ) + 1 ; // Avoid division by 0.
final int correctedBarLength = value / divider + ( value > 0 ? 1 : 0 ) ;
for( int i = 0 ; i < correctedBarLength ; i ++ ) {
stringBuilder.append( '+' ) ;
}
}
}