Unfortunately, the degree.distribution() function of the igraph library returns the intensities of the distribution:

> g <- graph.ring(5)
> plot(g) 
> summary(g)
IGRAPH U--- 10 10 -- Ring graph
attr: name (g/c), mutual (g/x), circular (g/x)

So instead of having the number of elements, the density/intensities value is returned:

> degree.distribution(g)
[1] 0 0 1

You can easily verify this in the source code of the function:

> degree.distribution
function (graph, cumulative = FALSE, ...) 
{
    if (!is.igraph(graph)) {
        stop("Not a graph object")
    }
    cs <- degree(graph, ...)
    hi <- hist(cs, -1:max(cs), plot = FALSE)$intensities
    if (!cumulative) {
        res <- hi
    }
    else {
        res <- rev(cumsum(rev(hi)))
    }
    res
}


This caused me some minor issues, but the solution was easy. I simply created a new version of the function that is using $count instead of $intensities (BTW $intensities will be deprecated in R 3.0).

count.degree.distribution <- function (graph, cumulative = FALSE, ...) 
{
    if (!is.igraph(graph)) {
        stop("Not a graph object")
    }
    cs <- degree(graph, ...)
    hi <- hist(cs, -1:max(cs), plot = FALSE)$count
    if (!cumulative) {
        res <- hi
    }
    else {
        res <- rev(cumsum(rev(hi)))
    }
    res
}
Using it is identical to the original version:
> count.degree.distribution(g)
[1] 0 0 10
1

View comments

  1. Recently I had a talk about one of my pet projects, https://ricsiboard.com/ at a local Java meetup.



    Yes yes, I know it's heresy to talk about JS on a Java event, but I think today being a pure Java dev is not really an option. There are too many disruptions, AI/Data comes with Python, full-stack comes with JS, Cloud comes with... well, pretty much anything.



    I wanted to have some fun, so I challenged myself to use something else instead of PowerPoint and... figured out a way how to create a presentation with vi. I got some questions about it, so thought a short post might be useful for the ppl of the internet.

    What you need to replicate that:

    1. An installation of Neovim. In an earlier post I've already mentioned why I prefer neovim over gvim and the others. 
    2. (Optional) A configured WSL.

    Aaand that's all.

    I pretty much wanted to follow convention over configuration - to do it in a way that even in vanilla vi it can be replicated. I wanted to avoid any additional plugin installations. The WSL is nice to have, as it handles custom emojis which is a nice addition to the "slides", zoom the content with Ctrl-+, and you can set up a transparency for the console.

    That's fun and can be a useful icebreaker if you have some funny background. My favourite for a long time is Jakub Rozalski's paintings. It's so unique that can spark a lot of talk even before the talk :-) 

    Well maybe not THIS much, but you get the point :D



    Creating the content

    I watched a few videos but honestly, most of the approaches were a pain to manage for my taste. E.g., most of the folks created 1 file per slide with numbers for the ordering and custom scripts for doing the maintenance/reordering (which reminded me sort of the C64 era, when we had to use addresses for GOTO statements haha). So I reverted to using 1 file only, because it is easier to navigate, move content around, reorder the "slides", share, get a grasp of the content on a quick scan, and you can change content with the usual buffer commands such as :ls, :bf, :bl, :bp, :bn. 

    I used the Markdown format for multiple reasons: it gets some highlight by default, Github renders it nicely, it is a common format, and paging is trivial with a simple macro (more on that later).

    Take a look on how it is rendered on Github, quite fancy:


    Useful commands, pagination

    To create the "slides" I just used some virtual space, while the titles were having an underline of ====.

    To create an underline for a header, you can use the combination yypVr=. This yanks the current line, duplicates it, selects the whole line, and replaces all characters with a = char.

    For the pagination, I created a simple macro with q2, and the key combination was /=====, Enter, nkztjj. Then you can execute the macro with @2, and simply use @@ afterwards to change page. What it does is it searches for the next underline of the next header, goes to the title text, makes it the top of the screen, and moves below the underline so the next search would jump to a new page.

    Some useful commands for adjusting the content is :center (to arrange the content of the current selection to the center of the screen), or using the >> and << commands to shift/decrease the indentation.

    And last but not least, using the gx combination will open up an URL in the text, which is useful to show some images, official docs, etc.

    Useful tools

    For creating ASCII art texts (e.g., for the opening slide or for the section breaks), set the default to the Doom one :-) Yup, I'm a that old haha.

    For creating minimal diagrams (in more complex cases, I'd fall back to a picture and a local URL and the gx command):
    For adding some nice visuals to the content:

    0

    Add a comment

  2. The Context

    Recently I created a new pet project. My wife was pregnant, and I was struggling with keeping up in which week of pregnancy we are in. I wasn't able to find one that suits my needs, they were all way too complex. I always wanted to look into PWAs, so I created one for myself. The problem came into the picture when I wanted to host it.

    As domain registration is quite expensive if you want to create a separated domain for all your projects, I ended up using my top-level domain.

    It was a week-long struggle though to make things work, so I thought sharing it might be useful for others.

    The Problem

    I own the domain rlegendi.com, and already host a small static website at https://guitar.rlegendi.com/.

    What I wanted:
    1. An additional subdomain of https://whichweekofpregnancy.rlegendi.com/ for a new static website.
    2. Both websites should be hosted with S3.
    3. CloudFront is necessary as I need https to make my PWA app installable.
    4. Keep Hosted Zones minimal, as each hosted zone costs like $0.5 at least per month. I know it's not a lot, but my monthly bill is pretty minimal, and I'd like to keep it that way :-) Most tutorials say the ultimate solution to this problem is to set up separate hosted zones, 1 for each domain, and then use NS records in the Hosted Zone of the root domain pointing to the name servers given in the Hosted Zones of the subdomain, but well... that becomes complicated quickly. 

    The Solution

    Ok, so here're the steps to make it work.

    Prerequisites

    What you need for this tutorial:

    1. You need a valid registered domain (I'm using AWS as a registrar here).
    2. A properly set up S3 bucket with the content of your site (e.g., it should allow public access).
    3. A created CloudFront distribution.

    CloudFront config

    Unfortunately, it is not enough to set up the DNS records in Route 53 to make this work. For some reasons (and I'm pretty sure it's well documented somewhere), you need to properly adjust your CDN config too.

    The important parts to double-check before moving forwards:
    1. Make sure the Alternate domain names is set to your new subdomain, e.g., whichweekofpregnancy.rlegendi.com in my case.
    2. Use the custom certificate of *.yourdomain.com, e.g., *.rlegendi.com in my case.



    Route 53 config

    1. Open Route 53 console.
    2. Go to Hosted Zones, and open up your current configuration.
      • If you don't have any, please refer to the articles here and here.
    3. At least you should have the following records in your config. The NS, SOA should be created by default. If you have an SSL certificate already set up, the CNAME entry will be there. We'll add the A record in the next step for the new subdomain.



    4. About the A record: the only thing you need to add is a simple Alias record for your new subdomain. You can use the wizard, and that will help you a lot in setting it up properly. Make sure you select:
      1. A as Record type.
      2. Alias to CloudFront distribution as Route traffic to.
      3. And your appropriate CloudFront distribution as a target.



    Aaand that's all! You can test it in your browser :-)

    Gotchas

    There are a couple of cases when things can go wrong. Here's a list to check.

    Preferably, your SSL certificate should cover *.yourdomain.com, as in my case, it's *.rlegendi.com. If it doesn't, ask a new certificate. It will be in Pending Validation state until you actually put it into use and assign it to your CloudFront distribution. 

    If you have multiple Alternate domain names, make sure the root domain is NOT set up as an alternative domain name. This will result in routing all the traffic of your subdomains to that specific CloudFront distribution. This took me a couple of days to figure out.

    If you get 403 Forbidden errors, make sure your Bucket has the appropriate Bucket policy for public read access:

    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "PublicReadGetObject",
                "Effect": "Allow",
                "Principal": "*",
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::subdomain.yourdomain.com/*"
            }
        ]
    }
    Also, don't forget that DNS caching might case some delays with checking the updated setting. A change might need up to 72 hours to synchronize, so expect a long duration in making this work.
    Have fun!
    0

    Add a comment

  3.  Weeee first blog bost after 10 years lol!

    Context

    This post will be about how to make Neovim + Cmder + wsl work together. Since I have spent a few hours fixing different issues, thought it would worth sharing. Might help others.

    Before you star questioning my mental health, and why have I spent so much time on this, let me tell you about my motivation :D Then we'll cover the config that worked for me, and some of the issues I encountered on the road just to save you some Googling if you run into them.

    Motivaiton and the main issues I had

    I'm revisiting the tool I'm using for immediate note taking. Many times I get an interrupt, or I'm on a call, but there's something important to look after and keep in mind. For these cases, I needed something where I can just drop in the info, revisit later and organize them to my huge pile of OneNote notes. That won't change, because the effectiveness of the visuals I can use there quickly is a life saver.

    I prefer Neovim over Vim nowadays, esp. after Vim9 script or what is it called. It comes with breaking backward compatibility changes, and practically with  a new language. Neovim on the other hand is using Lua for scripting which makes far more sense in my opinion. I find it more useful to learn a generic programming language for which there is a market need than something specific that you will never use in other cases.

    Running the default installation of Neovim is running in a Windows-shell, and I wanted to use bash commands. That is a problem for me, for instance I cannot use the usual commands like !!date to execute bash commands and att the output for the editor. This is a pretty neat feature I'm using a lot of times. Here's an example of the date command output in a Windows shell:

    I'm a fan of Cmder, it looks pretty neat with all that transparent background and stuff :-) I wanted to stick with that.


    Apart from executing commands as mentioned above, the other thing that drove me crazy was the broken gx functionality. It is another nice feature of newtr that even surprised some hardcore vim users on Twitter :-) It can open a URL under the cursor, which is pretty basic nowadays.

    How to make it work?

    Let me cut the x hours of headache into a 5 min task for you :-)

    Before we start, you need to make sure wsl is installed on your machine (instructions here).

    Then in Cmder, set up a new task as follows by cloning the {bash::bash} predefined version. The only thing you need to change is to use the "wsl.exe" directly.


    Pro hint: I set the default folder to one in my OneDrive, so that resolves the issue around having to backup my notes manually.

    After this, simply start a new task tab and you're done! Neovim will work like charm with all the functionalities I mentioned above :-)

    Troubleshooting

    I have spent most of my time in figuring out how to make things work with either MinGW, or mintty, or any other tools I had installed. In the end, I gave it up :D If you managed to get these things work, I would be pretty interested in your process, pls let me know :-)

    The usual error I got was something like this when executing external commands:
    :.!date
    E485 Can't read file C:\Users\<username>\AppData\Local\Temp\<random stuff>\3
    Press ENTER or type command to continue
    I have even reported a bug because I thought it's a vim vs. neovim issue, as I got different outputs in different machines I was working. Pro hint: this is a great way to sneakily install nvim on your wife's laptop :D

    The wsl installation did not work out-of-the-box, but the error messages were quite descriptive and pointed me to the proper docs to move fwd. I won't cover those.

    The only non-trivial thing was that I wasn't able to use any apt-related commands because timeouts. It took a while to update the /hosts/resolve.conf properly (the output of ipconfig /all was pretty useful here).

    To install neovim under wsl, I just grabbed the latest tarball and put it under /opt. I had to mess around with moving my init.vim file to ~/.config/nvim, but other than that, it was working perfectly. I also had some issues with my config that screwed up the shell settings a bit, nvim --clean /  nvim-qt -- --clean helped fixing those.

    Summary

    I am quite happy now that I was able to resolve some issues that were mocking me for a few weeks now. Hope it helped you too! :)

    0

    Add a comment

  4. Recently I wanted to introduce a new property in the portal-setup-wizard.properties file in Liferay 6.0.2. We have a few environments (nightly/test/demo/production) built directly from the CI with different setups, and I had to include an additional property for that.

    I tried the following obvious solution:

    boolean defaultValue = false;
    boolean myProperty = PrefsPropsUtil.getBoolean("my.property", defaultValue);
    

    But it cost me 5 or so hours to debug the Liferay internals and realize that the getBoolean() method cannot recognize any of the parameters (!). So what's the solution? Look it up as a simple String property and parse it by hand... Thanks for another wasted day, guys! I always ask myself why the Liferay dev team writes everything from scratch with full of trivial bugs instead of using something that is already available and simply works. Ah, enough from rants.

    boolean myProperty = noBooleanPropertiesWithPrefsPropsUtilSoWeHaveToParseItManually();
    ...
    
    private boolean noBooleanPropertiesWithPrefsPropsUtilSoWeHaveToParseItManually()
       throws SystemException {
     boolean defaultValue = false;
     String boolString = PrefsPropsUtil.getString("my.property", defaultValue);
     return Boolean.parseBoolean(boolString);
    }
    
    0

    Add a comment

  5. Recently a few exceptions like the one in the subject showed up. It turned out that this is a 3rd party library related error which is fixed in the master branch.

    The solution is until the update is to remove any special characters (like whitespaces, underscores and yes, even the '-' characters) from any of the <portlet-name> attributes. And yeah, you have to do that in hell lot of places such as the liferay-display.xml, liferay-portlet.xml and portlet.xml files.

    Just for the search engines, the header of the stack trace is something like this:
    Jul 09, 2013 9:11:30 AM org.apache.catalina.core.ApplicationDispatcher invoke
    SEVERE: Servlet.service() for servlet default threw exception
    java.io.FileNotFoundException: The requested resource (/my-project/my-portlet/invoke) is not available
     at org.apache.catalina.servlets.DefaultServlet.serveResource(DefaultServlet.java:776)
     at org.apache.catalina.servlets.DefaultServlet.doGet(DefaultServlet.java:411)
     at org.apache.catalina.servlets.DefaultServlet.doPost(DefaultServlet.java:493)
     at javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
     at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    
    1

    View comments

  6. Tracking associations with a value object listener is not that trivial. Unfortunately, I see another example here for inconsistency: I found no way to do this in a generic way, only through handling the different specific cases. Thought it may worth to post the example online about how to track association changes for a built-in service bean like User. Sorry for the formatting, it's the default Eclipse one.
    public class UserUpdateValueObjectListener extends
      BaseModelListener<User> {
     private static final class AssocDetailsEvaluator {
      final long userId;
      final String assocName;
      final String assocDescr;
    
      AssocDetailsEvaluator(final Object classPK,
        final String associationClassName,
        final Object associationClassPK) throws PortalException,
        SystemException {
       final User user = UserLocalServiceUtil.getUser((Long) classPK);
    
       String assocNameInit = "?";
       String assocDescrInit = "?";
    
       if (associationClassName.equals(Group.class.getName())
         || associationClassName.equals(Organization.class.getName())
         || associationClassName.equals(UserGroup.class.getName())) {
        Group group = null;
    
        if (associationClassName.equals(Group.class.getName())) {
         group = GroupLocalServiceUtil.getGroup((Long) associationClassPK);
         assocNameInit = "group";
        } else if (associationClassName.equals(Organization.class.getName())) {
         group = GroupLocalServiceUtil.getOrganizationGroup(user.getCompanyId(),
             (Long) associationClassPK);
         assocNameInit = "organization";
        } else if (associationClassName.equals(UserGroup.class.getName())) {
         group = GroupLocalServiceUtil.getUserGroupGroup(user.getCompanyId(),
             (Long) associationClassPK);
         assocNameInit = "user-group";
        }
    
        if (group != null) {
         assocDescrInit = group.getDescriptiveName();
        }
       } else if (associationClassName.equals(Role.class.getName())) {
        final Role role = RoleLocalServiceUtil.getRole((Long) associationClassPK);
    
        assocNameInit = "role";
        assocDescrInit = role.getTitle(LocaleUtil.getDefault());
       } else if (associationClassName.equals(Team.class.getName())) {
        Team team = TeamLocalServiceUtil.getTeam((Long) associationClassPK);
        
        assocNameInit = "team";
        assocDescrInit = team.getName();
       }
    
       this.userId = user.getUserId();
       this.assocName = assocNameInit;
       this.assocDescr = assocDescrInit;
      }
     }
    
     @Override
     public void onAfterAddAssociation(final Object classPK,
       final String associationClassName, final Object associationClassPK)
       throws ModelListenerException {
      super.onAfterAddAssociation(classPK, associationClassName, associationClassPK);
    
      try {
       final AssocDetailsEvaluator ade = new AssocDetailsEvaluator(classPK,
         associationClassName, associationClassPK, "-added");
    
       System.out.println("Association Added:" + ade.userId + " "
         + ade.assocName + " " + DataObject.of(ade.assocDescr));
      } catch (final NoSuchGroupException e) {
       log.error(e);
      } catch (final Exception e) {
       throw new ModelListenerException(e);
      }
     }
    
     // onAfterRemoveAssociation() can be done similarly
    }
    
    0

    Add a comment

  7. It cost me a whole day to sort this trivial stuff so I thought sharing it could come handy for a few of you out there.

    The Task: Add a category to an existing journal article programmatically.

    The Solution: Well, after making tons of experiment (thx guys for the documented codebase! :P) I came up with the following solutin:

    // The categories and tags to assign
    final String[] categoryNames = { "Category 1", "Category 2" };
    final String[] tagNames = { "Tag 1", "Tag 2" };
    
    final long[] assetCategoryIds = new long[categories.length];
    final String[] tagProperties = new String[0]; // Might be null too
    
    for (int i = 0; i < categories.size(); ++i) {
      final String name = categories[i];
    
      AssetTag assetTag = null;
      try {
        assetTag = AssetTagLocalServiceUtil.getTag(groupId, name);
      } catch (final NoSuchTagException e) {
        assetTag = AssetTagLocalServiceUtil.addTag(userId, name, tagProperties, serviceContext);
      }
    
      assetCategoryIds[i] = assetTag.getTagId();
    }
    
    AssetEntryLocalServiceUtil.updateEntry(userId, groupId,
      JournalArticle.class.getName(), journalArticle.getResourcePrimKey(),
      assetCategoryIds, tagNames);
    

    Some side comments: Actually, I was able to hunt down what I need only from diffing some database dumps after adding a category to an article. My problem is that even if they are under the Category section in the article editing portlet, they are handled as tags. Great, consistency über alles. Another twist was that I needed the AssetEntry that is linked to the JournalArticle. The third thing to note is that you need the  journalArticle.getResourcePrimKey() instead of the original primary key. Gosh.
    0

    Add a comment

  8. I hate how ill-documented are some of the features of Liferay, and sometimes the community comes up with questionable solutions.

    For example, I wanted to add a few new journal article types in the ext environment:

    journal.article.types=cat_1,cat_2,cat_3

    You can define new types whose name will be shown based on the value associated to it in the language files. Now the issue is where should be the language file for this? I definitely can't put it into one of the hooks (it would break consistency for one, I'd expect it to be in the same project).

    It turned out that under docroot/WEB-INF/ext-impl/src/content/Language-ext.properties would be the best place to do that:

    cat_1 = Category 1
    cat_2 = Category 2

    cat_3 = Category 3

    That's it, problem solved.

    My problem is that there are quite a lot of guys out there in the Liferay forums who don't have a clue what to do and there is no official staff to practically help them out so they remain helpless -- except for a few very rare cases: I'd suggest you taking a look on the Liferay JSF related questions, Neil Griffin is a superhero! The result is quite problematic and questionable solutions.

    For instance, take a look for this issue. Google wasn't able to find me the corresponding Liferay documentation. On the Liferay forums (example 1, example 2) the guys went to direction which I would discourage: they are hacking the Liferay source base (i.e., the portal source!) and the files under ROOT/WEB-INF/classes/... directly. The problem is that the next time you compile the contents of the classes directory is overwritten. Not to mention if someone redeploys the ext environment. Well, enough ranting for today.

    0

    Add a comment

  9. I had the luck to participate in a whole-day category theory seminar at my university recently. The seminar was recorded, so if you're interested, you can find the list of videos below.

    Unfortunately, we haven't covered all the topics we wanted to, but there'll be another event! So if you happen to be in Hungary, have a free day for it and would like to hear some details, just drop me a mail. The interesting things like adjunctions and monads are just to come!
    • Date: 2013.06.29. (Saturday), 10:00 - 17:00
    • Place: Eötvös Loránd University, Faculty of Informatics, Department of Programming Languages and Compilers, room D-2.702
    I also wrote a brief summary about the meeting (half of it is in Hungarian though).

    Now let's see the list of videos, which are probably the most interesting :-) Enjoy! (Unfortunately, embedding was disabled so I can give you only the links, guys. Also, English starts from the 6th minute of the second video).
    0

    Add a comment

  10. Today's Liferay bug: building ext in a clean container works perfectly from the Liferay IDE but on a Linux box (in our test environment) it fails with an error message:

    [java] java.lang.StringIndexOutOfBoundsException: String index out of range: -321
    [java]  at java.lang.String.substring(String.java:1911)
    [java]  at com.liferay.portal.tools.WebXMLBuilder.getCustomContent(WebXMLBuilder.java:139)
    [java]  at com.liferay.portal.tools.WebXMLBuilder.(WebXMLBuilder.java:91)
    [java]  at com.liferay.portal.tools.WebXMLBuilder.main(WebXMLBuilder.java:46)
    BUILD FAILED
    
    
    After heavy googling I found (an otherwise closed, heh) issue in the Liferay Jira:

    http://issues.liferay.com/browse/LPS-16233

    The issue boiled down to the fact that my ext-web/docroot/WEB-INF/web.xml file had an empty definition, and it wasn't closed by an explicit </web-app> tag. Gosh, matching XML by regexp patterns...

    Many thanks to Christophe Cariou who spent his time on debugging the LR codebase and posted a workaround for his own problem.
    1

    View comments

Blog Archive
My Blog List
My Blog List
Loading